手机管理应用研究【1】—— 安装卸载篇
欢迎转载,转载请注明: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
|
@Overrideprotected 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,应用安装卸载篇就到这里,欢迎大家讨论交流。
浙公网安备 33010602011771号