歡迎轉載,轉載請註明:http://blog.csdn.net/zhgxhuaa
說明
Android通過PackageManagerService(後面簡稱Pms)進行包管理,其主要功能包括:用戶ID分配、包解析、包的安裝卸載等。本文不對Pms進行分析,主要目的是探討一下包安裝。在本文中主要探討包安裝的相關操作,卸載作爲安裝的逆過程,實現類似,不再贅述。在應用安裝/卸載這裏主要有這麼幾個常見的功能點:
A. 靜默安裝/卸載
B. 秒裝/秒卸載
C. 卸載應用保存數據
D. 系統內置應用卸載
E. 卸載後清除殘留數據
下面就從這幾個方面做一下分析介紹。
Android中APK的安裝方式
在Android中APK的安裝有三種方式:
1、開機Pms初始化時,掃描包安裝目錄。
@/frameworks/base/services/java/com/android/server/SystemServer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public void initAndLoop()
{ ...... IPackageManager
pm = null ; ...... try { ...... pm
= PackageManagerService.main(context, installer, factoryTest
!= SystemServer.FACTORY_TEST_OFF, onlyCore); ...... } catch (RuntimeException
e) { Slog.e( "System" , "******************************************" ); Slog.e( "System" , "************
Failure starting core service" ,
e); } ...... } |
@/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java
1
2
3
4
5
6
7
|
public static final IPackageManager
main(Context context, Installer installer, boolean factoryTest, boolean onlyCore)
{ PackageManagerService
m = new PackageManagerService(context,
installer, factoryTest,
onlyCore); ServiceManager.addService( "package" ,
m); return m; } |
下面是Pms構造函數的實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
public PackageManagerService(Context
context, Installer installer, boolean
factoryTest, boolean onlyCore) { ...... synchronized
(mInstallLock) { //
writer synchronized
(mPackages) { ...... File
dataDir = Environment.getDataDirectory(); mAppDataDir
= new File(dataDir, "data" ); mAppInstallDir
= new File(dataDir, "app" ); mAppLibInstallDir
= new File(dataDir, "app-lib" ); mAsecInternalPath
= new File(dataDir, "app-asec" ).getPath(); mUserAppDataDir
= new File(dataDir, "user" ); mDrmAppPrivateInstallDir
= new File(dataDir, "app-private" ); ...... //
Find base frameworks (resource packages without code). mFrameworkInstallObserver
= new AppDirObserver( frameworkDir.getPath(),
OBSERVER_EVENTS, true , false ); mFrameworkInstallObserver.startWatching(); scanDirLI(frameworkDir,
PackageParser.PARSE_IS_SYSTEM |
PackageParser.PARSE_IS_SYSTEM_DIR, scanMode
| SCAN_NO_DEX, 0); //
Collected privileged system packages. File
privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app" ); mPrivilegedInstallObserver
= new AppDirObserver( privilegedAppDir.getPath(),
OBSERVER_EVENTS, true , true ); mPrivilegedInstallObserver.startWatching(); scanDirLI(privilegedAppDir,
PackageParser.PARSE_IS_SYSTEM |
PackageParser.PARSE_IS_SYSTEM_DIR |
PackageParser.PARSE_IS_PRIVILEGED, scanMode, 0); //
Collect ordinary system packages. File
systemAppDir = new File(Environment.getRootDirectory(), "app" ); mSystemInstallObserver
= new AppDirObserver( systemAppDir.getPath(),
OBSERVER_EVENTS, true , false ); mSystemInstallObserver.startWatching(); scanDirLI(systemAppDir,
PackageParser.PARSE_IS_SYSTEM |
PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0); //
Collect all vendor packages. File
vendorAppDir = new File( "/vendor/app" ); mVendorInstallObserver
= new AppDirObserver( vendorAppDir.getPath(),
OBSERVER_EVENTS, true , false ); mVendorInstallObserver.startWatching(); scanDirLI(vendorAppDir,
PackageParser.PARSE_IS_SYSTEM |
PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0); ...... if (!mOnlyCore)
{ EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START, SystemClock.uptimeMillis()); mAppInstallObserver
= new AppDirObserver( mAppInstallDir.getPath(),
OBSERVER_EVENTS, false , false ); mAppInstallObserver.startWatching(); scanDirLI(mAppInstallDir,
0, scanMode, 0); mDrmAppInstallObserver
= new AppDirObserver( mDrmAppPrivateInstallDir.getPath(),
OBSERVER_EVENTS, false , false ); mDrmAppInstallObserver.startWatching(); scanDirLI(mDrmAppPrivateInstallDir,
PackageParser.PARSE_FORWARD_LOCK, scanMode,
0); ...... } //
synchronized (mPackages) } //
synchronized (mInstallLock) } |
通過Pms的構造函數可以看出,Pms在初始化時會掃描/system/app、vender/app、/data/app、/data/app-private四個應用安裝目錄,然後調用sanDirLI方法進行安裝。Pms通過AppDirObserver對這四個應用安裝目錄進行監控,一旦發現APK格式的文件則會調用scanPackageLI進行安裝。
2、通過包安裝器PackageInstaller安裝
Android提供了一個默認的包安裝器,位於/package/app/PackageInstaller目錄。通過其Manifest文件可以看出,PackageInstaller會對我們安裝應用發出的Intent進行處理,這裏PackageInstaller提供了兩種處理方式,分別是:file方式和package方式。
@/package/app/PackageInstaller/AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
< activity android:name = ".PackageInstallerActivity" android:configChanges = "orientation|keyboardHidden|screenSize" android:excludeFromRecents = "true" > < intent-filter > < action android:name = "android.intent.action.VIEW" /> < action android:name = "android.intent.action.INSTALL_PACKAGE" /> < category android:name = "android.intent.category.DEFAULT" /> < data android:scheme = "file" /> < data android:mimeType = "application/vnd.android.package-archive" /> </ intent-filter > < intent-filter > < action android:name = "android.intent.action.INSTALL_PACKAGE" /> < category android:name = "android.intent.category.DEFAULT" /> < data android:scheme = "file" /> < data android:scheme = "package" /> </ intent-filter > </ activity > |
@/package/app/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
1
2
3
4
5
6
7
8
|
@Override protected void onCreate(Bundle
icicle) { super .onCreate(icicle); ...... initiateInstall(); } |
1
2
3
4
5
|
private void initiateInstall()
{ ...... startInstallConfirm(); } |
在startInstallConfirm方法中點擊“確認”後,會發出一個Intent,接收者爲InstallAppProgress。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public void onClick(View
v) { if (v
== mOk) { if (mOkCanInstall
|| mScrollView == null )
{ //
Start subactivity to actually install the application mInstallFlowAnalytics.setInstallButtonClicked(); Intent
newIntent = new Intent(); newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo); newIntent.setData(mPackageURI); newIntent.setClass( this ,
InstallAppProgress. class ); ...... startActivity(newIntent); finish(); } else { mScrollView.pageScroll(View.FOCUS_DOWN); } } else if (v
== mCancel) { ...... } } |
@/package/app/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public void initView()
{ setContentView(R.layout.op_progress); int installFlags
= 0 ; PackageManager
pm = getPackageManager(); ...... String
installerPackageName = getIntent().getStringExtra( Intent.EXTRA_INSTALLER_PACKAGE_NAME); Uri
originatingURI = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); Uri
referrer = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER); int originatingUid
= getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID, VerificationParams.NO_UID); ManifestDigest
manifestDigest = getIntent().getParcelableExtra(EXTRA_MANIFEST_DIGEST); VerificationParams
verificationParams = new VerificationParams( null ,
originatingURI, referrer,
originatingUid, manifestDigest); PackageInstallObserver
observer = new PackageInstallObserver(); if ( "package" .equals(mPackageURI.getScheme()))
{ try { pm.installExistingPackage(mAppInfo.packageName); observer.packageInstalled(mAppInfo.packageName, PackageManager.INSTALL_SUCCEEDED); } catch (PackageManager.NameNotFoundException
e) { observer.packageInstalled(mAppInfo.packageName, PackageManager.INSTALL_FAILED_INVALID_APK); } } else { pm.installPackageWithVerificationAndEncryption(mPackageURI,
observer, installFlags, installerPackageName,
verificationParams, null ); } } |
InstallAppProgress即應用安裝過程中的進度條界面。通過上面的代碼可以看到在initView方法的最後會調用Pms的installPackageWithVerificationAndEncryption方法進行安裝。
3、通過adb命令安裝
adb命令pm是Pms的Shell客戶端,通過pm可以進行包相關的一些操作,包括安裝和卸載。pm命令的用法如下:
pm的代碼實現在Pm.java中,如下:
@/frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public static void main(String[]
args) { new Pm().run(args); } public void run(String[]
args) { ...... mPm
= IPackageManager.Stub.asInterface(ServiceManager.getService( "package" )); ...... if ( "install" .equals(op))
{ runInstall(); return ; } if ( "uninstall" .equals(op))
{ runUninstall(); return ; } ...... } |
在run方法中初始化了一個Pms的客戶端代理對象mPm,後續的相關操作將有mPm完成。下面看一下Pm中負責安裝的方法runInstall的代碼實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
private void runInstall()
{ int installFlags
= PackageManager.INSTALL_ALL_USERS; ...... while ((opt=nextOption())
!= null )
{ if (opt.equals( "-l" ))
{ installFlags
|= PackageManager.INSTALL_FORWARD_LOCK; } else if (opt.equals( "-r" ))
{ installFlags
|= PackageManager.INSTALL_REPLACE_EXISTING; } else if (opt.equals( "-i" ))
{ installerPackageName
= nextOptionData(); if (installerPackageName
== null )
{ System.err.println( "Error:
no value specified for -i" ); return ; } } else if (opt.equals( "-t" ))
{ installFlags
|= PackageManager.INSTALL_ALLOW_TEST; } else if (opt.equals( "-s" ))
{ //
Override if -s option is specified. installFlags
|= PackageManager.INSTALL_EXTERNAL; } else if (opt.equals( "-f" ))
{ //
Override if -s option is specified. installFlags
|= PackageManager.INSTALL_INTERNAL; } else if (opt.equals( "-d" ))
{ installFlags
|= PackageManager.INSTALL_ALLOW_DOWNGRADE; ...... PackageInstallObserver
obs = new PackageInstallObserver(); try { VerificationParams
verificationParams = new VerificationParams(verificationURI, originatingURI,
referrerURI, VerificationParams.NO_UID, null ); mPm.installPackageWithVerificationAndEncryption(apkURI,
obs, installFlags, installerPackageName,
verificationParams, encryptionParams); synchronized (obs)
{ while (!obs.finished)
{ try { obs.wait(); } catch (InterruptedException
e) { } } if (obs.result
== PackageManager.INSTALL_SUCCEEDED) { System.out.println( "Success" ); } else { System.err.println( "Failure
[" +
installFailureToString(obs.result) + "]" ); } } } catch (RemoteException
e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); } } |
可以看出runInstall最終會調用Pms的installPackageWithVerificationAndEncryption方法進行安裝。通過pm安裝時,安裝成功的返回信息爲“Success”,安裝失敗的返回信息爲”Failure[失敗信息]"。
靜默安裝實現
在瞭解了Android中包安裝的方式後,接下來探討一些如何實現”靜默安裝“。所謂靜默安裝即跳過安裝界面和進度條,在不被用戶察覺的情況下載後臺安裝。下面針對上面的三種安裝方式分別來分析如何實現靜默安裝。
1、push安裝包到應用安裝目錄的方式
在Pms初始化時安裝包的流程中,我們知道Pms會監控/system/app、vender/app、/data/app、/data/app-private這四個應用安裝目錄。因此如果能夠將APK文件push進應用安裝目錄不就可以觸發AppDirObserver中的包安裝邏輯了了嗎?所以這種思路理論上是行得通的,但有兩個需要注意的點:
-
第一點:如下圖所示,/system/app的訪問權限爲root,這就要求在push到/system/app目錄時必須具有root權限。
而/data/app的訪問權限爲system。要獲得system權限就要求使用這種方式的應用程序必須簽名爲platform並且sharedUserId制定爲“android.uid.system”。
-
第二點:系統應用(/system/app)與普通應用(/data/app)的安裝方式是不同的,對於系統應用,所有資源都包含在apk這個zip包中,而且其在/system/app不必以包名命名(理論上可以隨便起名)。
而對於普通應用安裝後,它的dex、lib、資源文件(安裝包)分別存放在不同的目錄,並且安裝後以packagename-x.apk的形式保存在/data/app目錄下。
那這種安裝方式是不是就沒有用了呢?非也。
網上有些電子市場或管家類軟件實現的”秒裝“功能應該就是安裝這個思路實現的,當然這裏只是猜測,需要進一步研究。
2、調用Pm隱藏API
Android實現了一個應用安裝器的APK負責包的安裝工作,在上面的分析中我們知道,PackageInstaller的工作實際上只是安裝界面、權限確認、進度顯示等,真正的安裝工作依然是調用Pms實現的。到這裏我們就有了第二種思路,能不能繞過安裝界面,直接調用Pms裏面的相應方法呢?當然可以,PackageManager類中就提供了這樣的方法:
@/frameworks/base/core/java/android/content/pm/PackageManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/** *
@hide * *
Install a package. Since this may take a little while, the result will *
be posted back to the given observer. An installation will fail if the calling context *
lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the *
package named in the package file's manifest is already installed, or if there's no space *
available on the device. * *
@param packageURI The location of the package file to install. This can be a 'file:' or a *
'content:' URI. *
@param observer An observer callback to get notified when the package installation is *
complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be *
called when that happens. observer may be null to indicate that no callback is desired. *
@param flags - possible values: {@link #INSTALL_FORWARD_LOCK}, *
{@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}. *
@param installerPackageName Optional package name of the application that is performing the *
installation. This identifies which market the package came from. */ public abstract void installPackage( Uri
packageURI, IPackageInstallObserver observer, int flags, String
installerPackageName); |
可以看出,這個方法是hide的,因此在應用開發時如果要使用,必須通過反射。
這裏的IPackageInstallObserver是installPackage方法的一個回調接口通知,其實現在IPackageInstallObserver.aidl中,如下:
@/frameworks/base/core/java/com/android/content/pm/IPackageInstallObserver.aidl
1
2
3
4
5
6
7
8
9
|
package android.content.pm; /** *
API for installation callbacks from the Package Manager. *
@hide */ oneway interface IPackageInstallObserver
{ void packageInstalled(in
String packageName, int returnCode); } |
使用Android內置未公開API有兩種方法:一種是通過反射的方式實現;另一種是在工程目錄下建立與所引用系統類相同的類和方法,這裏只要求類和方法名相同,不需要實現,只保證編譯時不報錯就可以了,根據Java的類加載機制,在運行時,會去加載系統類。下面是採用第二種方法時的兩段示例代碼:
實現接口回調的代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
class MyPakcageInstallObserver extends IPackageInstallObserver.Stub
{ Context
cxt; String
appName; String
filename; String
pkname; public MyPakcageInstallObserver(Context
c, String appName, String
filename,String packagename) { this .cxt
= c; this .appName
= appName; this .filename
= filename; this .pkname
= packagename; } @Override public void packageInstalled(String
packageName, int returnCode)
{ Log.i(TAG, "returnCode
= " +
returnCode); //
返回1代表安裝成功 if (returnCode
== 1 )
{ //TODO } Intent
it = new Intent(); it.setAction(CustomAction.INSTALL_ACTION); it.putExtra( "install_returnCode" ,
returnCode); it.putExtra( "install_packageName" ,
packageName); it.putExtra( "install_appName" ,
appName); cxt.sendBroadcast(it); } } |
調用PackageManager.java隱藏方法,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/** *
靜默安裝 *
*/ public static void autoInstallApk(Context
context, String fileName, String
packageName, String APPName) { Log.d(TAG, "jing
mo an zhuang:" +
packageName + ",fileName:" +
fileName); File
file = new File(fileName); int installFlags
= 0 ; if (!file.exists()) return ; installFlags
|= PackageManager.INSTALL_REPLACE_EXISTING; if (hasSdcard())
{ installFlags
|= PackageManager.INSTALL_EXTERNAL; } PackageManager
pm = context.getPackageManager(); try { IPackageInstallObserver
observer = new MyPakcageInstallObserver( context,
APPName, appId, fileName,packageName,type_name); Log.i(TAG, "########installFlags:" +
installFlags+ "packagename:" +packageName); pm.installPackage(Uri.fromFile(file),
observer, installFlags, packageName); } catch (Exception
e) { } } |
這種方法也有一定的限制:
首先,要在AndroidManifest.xml中聲明”android.permission.INSTALL_PACKAGES”權限;
其次,應用需要system權限。
3、調用pm命令進行安裝
在adb窗口通過pm install安裝包本來就是沒有安裝界面的,這不正是我們想要的嗎?通過pm的安裝方式需要取得root或system權限。
pm的安裝方式有兩種,一種需要root權限,示例代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
new Thread()
{ public void run()
{ Process
process = null ; OutputStream
out = null ; InputStream
in = null ; try { //
請求root process
= Runtime.getRuntime().exec( "su" ); out
= process.getOutputStream(); //
調用安裝 out.write(( "pm
install -r " +
currentTempFilePath + "\n" ).getBytes()); in
= process.getInputStream(); int len
= 0 ; byte []
bs = new byte [ 256 ]; while (- 1 !=
(len = in.read(bs))) { String
state = new String(bs, 0 ,
len); if (state.equals( "Success\n" ))
{ //安裝成功後的操作 } } } catch (IOException
e) { e.printStackTrace(); } catch (Exception
e) { e.printStackTrace(); } finally { try { if (out
!= null )
{ out.flush(); out.close(); } if (in
!= null )
{ in.close(); } } catch (IOException
e) { e.printStackTrace(); } } } }.start(); |
另一鍾需要system權限,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
new Thread()
{ public void run()
{ Process
process = null ; InputStream
in = null ; try { //
請求root process
= Runtime.getRuntime().exec( "pm
install -r " +
currentTempFilePath + "\n" ); in
= process.getInputStream(); int len
= 0 ; byte []
bs = new byte [ 256 ]; while (- 1 !=
(len = in.read(bs))) { String
state = new String(bs, 0 ,
len); if (state.equals( "Success\n" ))
{ //安裝成功後的操作 } } } catch (IOException
e) { e.printStackTrace(); } catch (Exception
e) { e.printStackTrace(); } finally { try { if (in
!= null )
{ in.close(); } } catch (IOException
e) { e.printStackTrace(); } } } }.start(); |
關於system權限的獲取在介紹push方式的安裝時已做介紹。上面的代碼只給出了比較核心的部分,在實際實現中,對返回結果的處理同樣重要。
快速秒裝實現
發現市面上有些手機管家和電子市場類管理軟件實現了所謂的”秒裝“功能。這個功能本身的實現原理非常簡單,實現思路爲:我們可以利用Android中第一種安裝方式,不經過Android應用安裝器,直接將應用push到/data/app目錄中,此時會觸發Pms中對/data/app的目錄監控機制,觸發安裝。
然而在實現次功能時有這麼幾點需要注意:
-
第一點:需要獲得root權限(或通過系統漏洞(MastKey等)繞過root,總之要提示權限。這裏沒有深入研究)。
-
第二點:由於push的方式會繞過Android的一些驗證機制,因此在push之前需要人爲進行校驗等保證,這裏只舉兩個比較普遍的例子:
1)現在很多應用都是用so庫,但是大部分應用往往只提供arm版本的so庫集成,那邊在push之前,就需要校驗當前手機平臺是否存在對應的so庫,如果不存在則不建議是用秒裝。
2)在對已存在的應用進行升級時,如果通過秒裝進行升級,那麼必須要人爲校驗保證新的apk與已安裝apk的簽名一致。
好了,到這裏,Android安裝就介紹完了,歡迎大家交流討論。
刪除系統內置應用
Google定義的存放系統內置應用的位置有兩個:/system/app和/vender/app,其中/system/app用於存放Android內置應用;/vender/app用於存放廠商內置應用。實際上在使用時,/vender/app目錄往往不使用。下面我們看一下/system/app目錄(下面是我小米2S手機/system/app目錄的截圖):
可以看出/system/app目錄下應用文件的權限爲644,用戶爲root。我們通過PackageManager是無法卸載系統內置應用的。要想刪除系統內置應用,需要在獲得root權限的前提下,通過rm命令刪除即可。
這種卸載方式有一些需要注意的問題,請結合5中卸載應用保存數據一起來看。
卸載應用保存數據
在有些手機管理軟件和電子市場(91手機助手)中有這麼一個功能“刪除應用保存數據”,那它是如何做到的呢?這裏顯然不能走Android默認的卸載流程。於是,我們想到了是不是可以通過rm直接刪除/system/app或者/data/app目錄下的apk文件就可以了呢?下面對這個想法做一個驗證。以應用寶爲例:
1)在/data/app目錄下找到應用寶安裝後的apk文件,如下圖:
2)通過rm命令將/data/app目錄下的應用寶apk文件刪除,刪除後/data/data目錄下的情況如下:
3) 看到這裏相信大部分人都會有這樣的疑問?
A. 重啓手機後/data/data目錄下的應用寶數據文件會不會被系統刪除?
B. 重新安裝應用寶後,新的應用寶還能訪問上次安裝未刪除的數據文件嗎?
帶着這些疑問,首先看一下第一個情況,在重啓手機後/data/data目錄如下:
可以看到應用寶的數據目錄依然是存在的。
4)在重新安裝應用寶後,我們發現也是可以使用之前的數據目錄的。
從上圖可以看出,重新安裝後應用寶的user爲app_18與上次安裝一樣。
對於這裏一些原理性的東西暫時先不做介紹。
5)這裏有幾個需要注意的問題,如下:
A. 在通過這種方式刪除應用時,要注意第三方Launcher上快捷方式(圖標)的處理。如下圖所示,在刪除應用寶後,在小米桌面上的圖標並未一起刪除。
B. 在刪除應用之前,最好先調用PackageManager中的foreStopPackage方法停止相關應用,然後再刪除,否則可能會有不友好的系統提示,如下圖:
應用安裝卸載相關的還有其他一些內容,比如說:清理應用卸載殘留、應用鎖、應用隱藏、應用安裝位置、遠程adb安裝等等。這些內容暫時不做介紹,放到其他篇目中研究。下一篇介紹系統垃圾清理。
OK,應用安裝卸載篇就到這裏,歡迎大家討論交流。