Android包管理机制5 APK是如何被解析的

一 概述

在本系列的前面文章中,我们介绍了 PackageInstaller 的初始化和安装 APK 过程、PMS 处理 APK 的安装和 PMS 的创建过程,这些文章中经常会涉及到一个类,那就是 PackageParser,它用来在 APK 的安装过程中解析 APK,那么 APK 是如何被解析的呢?这篇文章会给你答案。

二 引入PackageParser

Android 系统中有很多包,比如应用程序的 APK,Android 运行环境的 JAR 包(比如 framework.jar)和组成 Android 系统的各种动态库 so 等等,由于包的种类和数量繁多,就需要进行包管理,但是包管理需要在内存中进行,而这些包都是以静态文件的形式存在的,就需要一个工具类将这些包转换为内存中的数据结构,这个工具就是包解析器 PackageParser。

Android包管理机制3 PMS处理APK的安装 这篇文章中,我们知道安装 APK 时需要调用 PMS 的 installPackageLI 方法:

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
    ...
    PackageParser pp = new PackageParser();//1
    pp.setSeparateProcesses(mSeparateProcesses);
    pp.setDisplayMetrics(mMetrics);
    pp.setCallback(mPackageParserCallback);
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
    final PackageParser.Package pkg;
    try {
        pkg = pp.parsePackage(tmpPackageFile, parseFlags);//2
    }
    ...
 }   

可以看到安装 APK 时,需要先在注释1处创建 PackageParser,然后在注释2处调用 PackageParser 的 parsePackage 方法来解析 APK。

三 PackageParser解析APK

Android5.0 引入了 Split APK 机制,这是为了解决 65536 上限以及 APK 安装包越来越大等问题。Split APK 机制可以将一个 APK,拆分成多个独立 APK。

在引入了 Split APK 机制后,APK 有两种分类:

  • Single APK:安装文件为一个完整的 APK,即 base APK。Android 称其为 Monolithic。
  • Mutiple APK:安装文件在一个文件目录中,其内部有多个被拆分的 APK,这些 APK 由一个 base APK 和一个或多个 split APK 组成。Android 称其为 Cluster。

了解了 APK,我们接着学习 PackageParser 解析 APK,查看 PackageParser 的 parsePackage 方法:

frameworks/base/core/java/android/content/pm/PackageParser.java

public Package parsePackage(File packageFile, int flags, boolean useCaches)
           throws PackageParserException {
       Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
       if (parsed != null) {
           return parsed;
       }
       if (packageFile.isDirectory()) {//1
           parsed = parseClusterPackage(packageFile, flags);
       } else {
           parsed = parseMonolithicPackage(packageFile, flags);
       }
       cacheResult(packageFile, flags, parsed);

       return parsed;
   }

注释1处,如果要解析的 packageFile 是一个目录,说明是 Mutiple APK,就需要调用 parseClusterPackage 方法来解析,如果是 Single APK 则调用 parseMonolithicPackage 方法来解析。这里以复杂的 parseClusterPackage 法为例,了解了这个方法,parseMonolithicPackage 方法自然也看的懂。
在这里插入图片描述
frameworks/base/core/java/android/content/pm/PackageParser.java

private Package parseClusterPackage(File packageDir, int flags)
     throws PackageParserException {
        final PackageLite lite = parseClusterPackageLite(packageDir, 0);//1
       if (mOnlyCoreApps && !lite.coreApp) {//2
           throw new PackageParserException(
           INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                   "Not a coreApp: " + packageDir);
       }
       ...
       try {
           final AssetManager assets = assetLoader.getBaseAssetManager();
           final File baseApk = new File(lite.baseCodePath);
           final Package pkg = parseBaseApk(baseApk, assets, flags);//3
           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;//4
               pkg.splitNames = lite.splitNames;
               pkg.splitCodePaths = lite.splitCodePaths;
               pkg.splitRevisionCodes = lite.splitRevisionCodes;
               pkg.splitFlags = new int[num];
               pkg.splitPrivateFlags = new int[num];
               pkg.applicationInfo.splitNames = pkg.splitNames;
               pkg.applicationInfo.splitDependencies = splitDependencies;
               for (int i = 0; i < num; i++) {
                   final AssetManager splitAssets = 
                   assetLoader.getSplitAssetManager(i);
                   parseSplitApk(pkg, i, splitAssets, flags);//5
               }
           }
           pkg.setCodePath(packageDir.getAbsolutePath());
           pkg.setUse32bitAbi(lite.use32bitAbi);
           return pkg;
       } finally {
           IoUtils.closeQuietly(assetLoader);
       }
   }

注释1处调用 parseClusterPackageLite 方法用于轻量级解析目录文件,之所以要轻量级解析是因为解析 APK 是一个复杂耗时的操作,这里的逻辑并不需要 APK 所有的信息。

parseClusterPackageLite 方法内部会通过 parseApkLite 方法解析每个 Mutiple APK,得到每个 Mutiple APK 对应的 ApkLite(轻量级 APK 信息),然后再将这些 ApkLite 封装为一个 PackageLite(轻量级包信息)并返回。

注释2处,mOnlyCoreApps 用来指示 PackageParser 是否只解析“核心”应用,“核心”应用指的是 AndroidManifest 中属性 coreApp 值为 true,只解析“核心”应用是为了创建一个极简的启动环境。mOnlyCoreApps 在创建 PMS 时就一路传递过来,如果我们加密了设备,mOnlyCoreApps 值就为 true,具体的见 Android包管理机制4 PMS的创建过程 这篇文章的第1小节。另外可以通过 PackageParser 的 setOnlyCoreApps 方法来设置 mOnlyCoreApps的值。

lite.coreApp 表示当前包是否包含“核心”应用,如果不满足注释2的条件就会抛出异常。

注释3处的 parseBaseApk 方法用于解析 base APK,注释4处获取 split APK 的数量,根据这个数量在注释5处遍历调用 parseSplitApk 来解析每个 split APK。这里主要查看 parseBaseApk 方法,如下所示。

frameworks/base/core/java/android/content/pm/PackageParser.java

private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
           throws PackageParserException {
       final String apkPath = apkFile.getAbsolutePath();
       String volumeUuid = null;
       if (apkPath.startsWith(MNT_EXPAND)) {
           final int end = apkPath.indexOf('/', MNT_EXPAND.length());
           volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);//1
       }
       ...
       Resources res = null;
       XmlResourceParser parser = null;
       try {
           res = new Resources(assets, mMetrics, null);
           parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
           final String[] outError = new String[1];
           final Package pkg = 
           parseBaseApk(apkPath, res, parser, flags, outError);//2
           if (pkg == null) {
               ......
           }
           pkg.setVolumeUuid(volumeUuid);//3
           pkg.setApplicationVolumeUuid(volumeUuid);//4
           pkg.setBaseCodePath(apkPath);
           pkg.setSignatures(null);
           return pkg;
       } catch (PackageParserException e) {
           throw e;
       }
       ...
   }

注释1处,如果 APK 的路径以 /mnt/expand/ 开头,就截取该路径获取 volumeUuid。

注释3处用于以后标识这个解析后的 Package,注释4处的用于标识该 App 所在的存储卷 UUID。

注释2处又调用了 parseBaseApk 的重载方法,可以看出当前的 parseBaseApk 方法主要是为了获取和设置 volumeUuid。parseBaseApk 的重载方法如下所示。

frameworks/base/core/java/android/content/pm/PackageParser.java

private Package parseBaseApk(String apkPath, Resources res, 
    XmlResourceParser parser, int flags,
    String[] outError) throws XmlPullParserException, IOException {
       ......
       final Package pkg = new Package(pkgName);//1
       // 从资源中提取自定义属性集com.android.internal.R.styleable.AndroidManifest
       // 得到TypedArray 
       TypedArray sa = res.obtainAttributes(parser,
               com.android.internal.R.styleable.AndroidManifest);//2
       //使用typedarray获取AndroidManifest中的versionCode赋值给Package的对应属性        
       pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger(
               com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
       pkg.baseRevisionCode = sa.getInteger(
               com.android.internal.R.styleable.AndroidManifest_revisionCode, 0);
       pkg.mVersionName = sa.getNonConfigurationString(
               com.android.internal.R.styleable.AndroidManifest_versionName, 0);
       if (pkg.mVersionName != null) {
           pkg.mVersionName = pkg.mVersionName.intern();
       }
       pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);//3
       //获取资源后要回收
       sa.recycle();
       return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
   }

注释1处创建了 Package 对象,注释2处从资源中提取自定义属性集 com.android.internal.R.styleable.AndroidManifest 得到 TypedArray ,这个属性集所在的源码位置为 frameworks/base/core/res/res/values/attrs_manifest.xml。接着用 TypedArray 读取 APK 的 AndroidManifest 中的 versionCode、revisionCode 和 versionName 的值赋值给 Package 的对应的属性。

注释3处读取 AP K的 AndroidManifest 中的 coreApp 的值。

最后会调用 parseBaseApkCommon 方法,这个方法非常长,主要用来解析 APK 的 AndroidManifest 中的各个标签,比如 application、permission、uses-sdk、feature-group 等等,其中四大组件的标签在 application 标签下,解析 application 标签的方法为 parseBaseApplication。

frameworks/base/core/java/android/content/pm/PackageParser.java

  private boolean parseBaseApplication(Package owner, Resources res,
            XmlResourceParser parser, int flags, String[] outError)
        throws XmlPullParserException, IOException {
        ...
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG ||
                 parser.getDepth() > innerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }
            String tagName = parser.getName();
            if (tagName.equals("activity")) {//1
                Activity a = parseActivity(owner, res, parser, flags, outError, false,
                        owner.baseHardwareAccelerated);//2
                if (a == null) {
                    mParseError =
                    PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }
                owner.activities.add(a);//3
            } else if (tagName.equals("receiver")) {
                Activity a = parseActivity(owner, res, parser, 
                    flags, outError, true, false);
                if (a == null) {
                    mParseError = 
                    PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }
                owner.receivers.add(a);
            } else if (tagName.equals("service")) {
                Service s = parseService(owner, res, parser, flags, outError);
                if (s == null) {
                    mParseError = 
                    PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }
                owner.services.add(s);
            } else if (tagName.equals("provider")) {
                Provider p = parseProvider(owner, res, parser, flags, outError);
                if (p == null) {
                    mParseError =
                    PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }
                owner.providers.add(p);
             ...
            } 
        }
     ...
}

parseBaseApplication 方法有近500行代码,这里只截取了解析四大组件相关的代码。

注释1处如果标签名为 activity,就调用注释2处的 parseActivity 方法解析 activity 标签并得到一个 Activity 对象(PackageParser 的静态内部类),这个方法有300多行代码,解析一个 activity 标签就如此繁琐,activity 标签只是 Application 中众多标签的一个,而 Application 只是 AndroidManifest 众多标签的一个,这让我们更加理解了为什么此前解析 APK 时要使用轻量级解析了。

注释3处将解析得到的 Activity 对象保存在 Package 的列表 activities 中。其他的四大组件也是类似的逻辑。

PackageParser 解析 APK 的代码逻辑非常庞大,基本了解本文所讲的就足够了,如果有兴趣可以自行看源码。

parseBaseApk 方法主要的解析结构可以理解为以下简图。
在这里插入图片描述

四 Package的数据结构

包被解析后,最终在内存是 Package,Package 是 PackageParser 的内部类,它的部分成员变量如下所示。

frameworks/base/core/java/android/content/pm/PackageParser.java

public final static class Package implements Parcelable {
    public String packageName;
    public String manifestPackageName;
    public String[] splitNames;
    public String volumeUuid;
    public String codePath;
    public String baseCodePath;
    ...
    public 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);//1
    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);
...
}

注释1处,activities 列表中存储了类型为 Activity 的对象,需要注意的是这个 Acticity 并不是我们常用的那个 Activity,而是 PackageParser 的静态内部类,Package 中的其他列表也都是如此。Package 的数据结构简图如下所示。
在这里插入图片描述
从这个简图中可以发现 Package 的数据结构是如何设计的:

  • Package 中存有许多组件,比如 Acticity、Provider、Permission 等等,它们都继承基类 Component
  • 每个组件都包含一个 info 数据,比如 Activity 类中包含了成员变量 ActivityInfo,这个 ActivityInfo 才是真正的 Activity 数据
  • 四大组件的标签内可能包含 <intent-filter> 来过滤 Intent 信息,因此需要 IntentInfo 来保存组件的 intent 信息,组件基类 Component 依赖于 IntentInfo,IntentInfo 有三个子类 ActivityIntentInfo、ServiceIntentInfo 和 ProviderIntentInfo,不同组件依赖的 IntentInfo 会有所不同,比如 Activity 继承自 Component<ActivityIntentInfo> ,Permission 继承自 Component<IntentInfo>

最终的解析的数据会封装到 Package 中,除此之外在解析过程中还有两个轻量级数据结构 ApkLite 和 PackageLite,因为这两个数据和 Package 没有太大的关联就没有在上图中表示。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值