手机管理应用研究【2】—— 安装卸载篇

欢迎转载,转载请注明: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();
         }
     }

你可能感兴趣的:(Android,技术专题,Android,技术专题)