欢迎转载,转载请注明:http://blog.csdn.net/zhgxhuaa
说明
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的Shellclient,通过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的client代理对象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有两种方法:一种是通过反射的方式实现;还有一种是在project文件夹下建立与所引用系统类同样的类和方法,这里仅仅要求类和方法名同样。不须要实现,仅仅保证编译时不报错就能够了,依据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方式的安装时已做介绍。
上面的代码仅仅给出了比較核心的部分,在实际实现中。对返回结果的处理相同重要。
高速秒装实现
发现市面上有些手机管家和电子市场类管理软件实现了所谓的”秒装“功能。这个功能本身的实现原理很easy,实现思路为:我们能够利用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,应用安装卸载篇就到这里,欢迎大家讨论交流。