Android-6.0之PMS解析中篇2

本文转载于:http://www.iloveandroid.net/2016/06/20/Android_PackageManagerService-2/


本篇文章主要针对上篇文章中两个未分析的方法进行分析。

APK文件的解析

上一篇中的scanPackageLI分析的开头

1
2
3
4
5
6
7
8
9
10
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
         long currentTime, UserHandle user) throws PackageManagerException {
  ..........................
     final PackageParser.Package pkg;
     try {
         pkg = pp.parsePackage(scanFile, parseFlags);
     } catch (PackageParserException e) {
         throw PackageManagerException.from(e);
     }
     ...........

我们并没有对PackageParser.parsePackage方法进行分析,现在着手分析它都做了什么。

1
2
3
4
5
6
7
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
      if (packageFile.isDirectory()) {
          return parseClusterPackage(packageFile, flags);
      } else {
          return parseMonolithicPackage(packageFile, flags);
      }
  }

parsePackage方法中要对传入File进行判断。

Android 5.0 之后默认支持一个应用关联多个APK的形式,当某个app是这类情况的话,这些APK就会放在一个文件夹中,那么此时就会调用parseClusterPackage处理。

当传入的file就是一个apk文件的话,调用parseMonolithicPackage来处理。

解析单个apk

首先分析file为一个apk文件的情况,即parseMonolithicPackage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
        if (mOnlyCoreApps) {//PMS初始化时,该变量一般为false,只有在data区加解密等特殊情况下,才为true
            final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
            if (!lite.coreApp) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                        "Not a coreApp: " + apkFile);
            }
        }

        final AssetManager assets = new AssetManager();//AssetManager是资源管理框架
        try {
            final Package pkg = parseBaseApk(apkFile, assets, flags);// 在parseBaseApk的时候,会把assets传人
            pkg.codePath = apkFile.getAbsolutePath();
            return pkg;
        } finally {
            IoUtils.closeQuietly(assets);
        }
    }

先创建一个资源管理框架的对象AssetManager,在将其和apkfile等一起传入parseBaseApk方法:

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
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
           throws PackageParserException {
       final String apkPath = apkFile.getAbsolutePath();// 返回apkFile绝对路径名字字符串

       String volumeUuid = null;
       if (apkPath.startsWith(MNT_EXPAND)) {
           final int end = apkPath.indexOf('/', MNT_EXPAND.length());
           volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
       }

       mParseError = PackageManager.INSTALL_SUCCEEDED;
       mArchiveSourcePath = apkFile.getAbsolutePath();

       if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);

       final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);

       Resources res = null;
       XmlResourceParser parser = null;
       try {
           res = new Resources(assets, mMetrics, null);
           /*
            Resources类构造方法中调用其成员函数updateConfiguration,
            首先是根据参数config和metrics来更新设备的当前配置信息,
            例如,屏幕大小和密码、国家地区和语言、键盘配置情况等等,
            接着再调用成员变量mAssets所指向的一个Java层的AssetManager对象的成员函数setConfiguration来将这些配置信息设置到与之关联的C++层的AssetManager对象中去。
           */
           assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                   Build.VERSION.RESOURCES_SDK_INT);
          //创建AndroidMainfest.xml的xml解析器
           parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);

           final String[] outError = new String[1];
           // 开始真正解析AndroidMainfest.xml
           final Package pkg = parseBaseApk(res, parser, flags, outError);
           if (pkg == null) {
               throw new PackageParserException(mParseError,
                       apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
           }

           pkg.volumeUuid = volumeUuid;
           pkg.baseCodePath = apkPath;
           pkg.mSignatures = null;

           return pkg;

       } catch (PackageParserException e) {
           throw e;
       } catch (Exception e) {
           throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                   "Failed to read manifest from " + apkPath, e);
       } finally {
           IoUtils.closeQuietly(parser);
       }
   }

该方法说白了,就是对AndroidMainfest.xml文件进行解析。真正解析这个xml的是其内部调用的parseBaseApk方法。该方法先调用以pkgName为参数的构造方法创建一个PackageParser.Package对象。

1
2
3
4
5
public Package(String packageName) {
           this.packageName = packageName;
           applicationInfo.packageName = packageName;
           applicationInfo.uid = -1;
       }

接下来就是将解析到的AndroidMainfest.xml相关信息,赋值到Package的相关属性字段中去。如xml中声明的四大组件了,该app的版本号了等等。

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
public final static class Package {

        public String packageName;

        .........................
        public String codePath;

        /** Path of base APK */
        public String baseCodePath;
        /** Paths of any split APKs, ordered by parsed splitName */
        public String[] splitCodePaths;

        ................................
        // For now we only support one application per package.
       public final ApplicationInfo applicationInfo = new ApplicationInfo();

       public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
       public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
       public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
       public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
       public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
       public final ArrayList<Service> services = new ArrayList<Service>(0);
       public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);
        .....................................
        // The version code declared for this package.
       public int mVersionCode;

       // The version name declared for this package.
       public String mVersionName;

       // The shared user id that this package wants to use.
       public String mSharedUserId;
       .................................

实际上解析apk的过程,就是解析xml,然后填充PackageParser.Package对象的过程。

时序图:

解析一个app有多个apk的情况

Split APK是Google为解决65536上限,以及APK安装包越来越大等问题,在Android L中引入的机制。

Split APK可以将一个庞大的APK,按屏幕密度,ABI等形式拆分成多个独立的APK,在应用程序更新时,不必下载整个APK,只需单独下载某个模块即可安装更新。

Split APK将原来一个APK中多个模块共享同一份资源的模型分离成多个APK使用各自的资源,并且可以继承Base APK中的资源,多个APK有相同的data,cache目录,多个dex文件,相同的进程,在Settings.apk中只显示一个APK,并且使用相同的包名。

可参考该文章,了解如何使用该机制。

当是这种情况时,会调用parseClusterPackage

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
private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
      // 获取应用目录的PackageLite对象,该对象中分开保存了该目录下的核心应用和非核心应用的名称。
       final PackageLite lite = parseClusterPackageLite(packageDir, 0);

       if (mOnlyCoreApps && !lite.coreApp) {
           throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                   "Not a coreApp: " + packageDir);
       }

       final AssetManager assets = new AssetManager();
       try {
           // Load the base and all splits into the AssetManager
           // so that resources can be overriden when parsing the manifests.
           // 先装载核心应用的资源
           loadApkIntoAssetManager(assets, lite.baseCodePath, flags);

          // 在装载其他非核心应用的资源
           if (!ArrayUtils.isEmpty(lite.splitCodePaths)) {
               for (String path : lite.splitCodePaths) {
                   loadApkIntoAssetManager(assets, path, flags);
               }
           }

           final File baseApk = new File(lite.baseCodePath);
           // 对核心应用调用parseBaseApk进行解析
           final Package pkg = parseBaseApk(baseApk, assets, flags);
           if (pkg == null) {
               throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                       "Failed to parse base APK: " + baseApk);
           }

           if (!ArrayUtils.isEmpty(lite.splitNames)) {
               final int num = lite.splitNames.length;
               pkg.splitNames = lite.splitNames;
               pkg.splitCodePaths = lite.splitCodePaths;
               pkg.splitRevisionCodes = lite.splitRevisionCodes;
               pkg.splitFlags = new int[num];
               pkg.splitPrivateFlags = new int[num];

               //对非核心应用调用parseSplitApk解析
               for (int i = 0; i < num; i++) {
                   parseSplitApk(pkg, i, assets, flags);
               }
           }

           pkg.codePath = packageDir.getAbsolutePath();
           return pkg;
       } finally {
           IoUtils.closeQuietly(assets);
       }
   }

parseClusterPackage首先调用parseClusterPackageLite对目录下的apk文件进行初步分析,主要是区别除核心应用和非核心应用。核心应用只有一个,非核心应用可以没有,也可以有多个。

对核心应用调用parseBaseApk解析,对非核心应用调用parseSplitApk,解析到的结果会保存在同一个PackageParser.Package对象中。

时序图:

scanPackageDirtyLI方法

前面分析的scanPackageLI方法的最后还会调用同但参数不同的scanPackageLI方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,
        int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
    boolean success = false;
    try {
        final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,
                currentTime, user);
        success = true;
        return res;
    } finally {
        if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
            removeDataDirsLI(pkg.volumeUuid, pkg.packageName);
        }
    }
}

该方法内部调用 scanPackageDirtyLI来完成相应的工作.scanPackageLI方法也能被单独调用使用,我们这里只关心当它被前一个scanPackageLI方法调用时的逻辑。

Android系统中有这样的一个场景,当发出一个intent的时候,如果对话框中存在多个响应的activity,Android就会弹出一个对话框,让用户进行选择。典型的例子,就是设备中安装了多个可以打开的word文件的软件的时候,当打开word时,如过没有设置默认使用的软件,那么就会弹出一个对话框,让用户选择使用哪个app来打开word文件。这个对话框叫做ResolverActivity.因此该对话框,是存在与系统内部的对象,它保存在PMS的mResolveActivity变量。

ResolverActivity的代码位于framework-res.apk中,因此scanPackageLi扫描发现文件的包名是“Android”的时候,会从文件中提取ResolverActivity的信息,并创建mResolveActivity对象。如果系统中通过定义字符串”config_customResolverActivity”指定了替代的ResolverActivity,那么则会在找到了替代的包名后,在创建mResolveActivity对象,不在使用framework-res.apk中的ResolverActivity.

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
ivate PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {

              ...................
              if (mCustomResolverComponentName != null &&
                              mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) {

                          // 该方法中会将 mResolverReplaced 设置为true
                          setUpCustomResolverActivity(pkg);
                      }
              ...................
              if (pkg.packageName.equals("android")) {
            synchronized (mPackages) {
                if (mAndroidApplication != null) {
                    Slog.w(TAG, "*************************************************");
                    Slog.w(TAG, "Core android package being redefined.  Skipping.");
                    Slog.w(TAG, " file=" + scanFile);
                    Slog.w(TAG, "*************************************************");
                    throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
                            "Core android package being redefined.  Skipping.");
                }

                // Set up information for our fall-back user intent resolution activity.
                mPlatformPackage = pkg;
                pkg.mVersionCode = mSdkVersion;
                mAndroidApplication = pkg.applicationInfo;


                if (!mResolverReplaced) {
                    mResolveActivity.applicationInfo = mAndroidApplication;
                    mResolveActivity.name = ResolverActivity.class.getName();
                    mResolveActivity.packageName = mAndroidApplication.packageName;
                    mResolveActivity.processName = "system:ui";
                    mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
                    mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
                    mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
                    mResolveActivity.theme = R.style.Theme_Holo_Dialog_Alert;
                    mResolveActivity.exported = true;
                    mResolveActivity.enabled = true;
                    mResolveInfo.activityInfo = mResolveActivity;
                    mResolveInfo.priority = 0;
                    mResolveInfo.preferredOrder = 0;
                    mResolveInfo.match = 0;
                    mResolveComponentName = new ComponentName(
                            mAndroidApplication.packageName, mResolveActivity.name);
                }
            }
        }
        .....................
}

可能在其他目录中已经扫描并安装过一个同名的app了,所以抛出异常。

1
2
3
4
5
6
if (mPackages.containsKey(pkg.packageName)
             || mSharedLibraries.containsKey(pkg.packageName)) {
         throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
                 "Application package " + pkg.packageName
                 + " already installed.  Skipping duplicate.");
     }

接下来的代码不贴了太多了,间要说下余下代码都做了什么事情。

如果扫描应用中有标签,并且有升级包则将包的悉尼西加入到mSeetings的mRenamedPackages变量中,这些信息也会保存在packages.xml中标签下。

再次校验签名,并检查共享UID的app签名是否一致,不一致要报错。

利用fixProcessName()修改app将来运行时的进程名称,将来可以通过/proc/self/cmdline读取。

如果应用的沙箱目录已经存在,那么检查是否有错误,如uid是否一致等等,发现错误的话,删掉数据目录,重新建立。

安装应用中的动态,这里要注意系统app和第三方app的区别。系统app的so库,一般会放在/system/lib下。另外系统app的目录中会有一个连接文件指向/system/lib下的so库,这就导致系统app,无法通过分析目录文件确定其so库使用的abi.而第三方app,可以通过armeabi,armeabi-v7a等目录名字确定其abi。对于第三方app,其so库会放在

1
/data/app/包名/lib/(arm/arm64/x86)/

然后在沙箱目录中创建一个lib连接文件,指向上述目录。

如果检测到应用所依赖的动态库文件发生了改变,咋调用performDexOpt重新执行。

最后就是把app中Activity,Service,Provider,receiver,permission,Permission Group等信息全部提取出来,放到PMS相关成员变量中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值