Android包管理機制(五)APK是如何被解析的

關(guān)聯(lián)系列
包管理機制系列

前言

在本系列的前面文章中,我介紹了PackageInstaller的初始化和安裝APK過程、PMS處理APK的安裝和PMS的創(chuàng)建過程,這些文章中經(jīng)常會涉及到一個類,那就是PackageParser,它用來在APK的安裝過程中解析APK,那么APK是如何被解析的呢?這篇文章會給你答案。

1.引入PackageParser

Android世界中有很多包,比如應(yīng)用程序的APK,Android運行環(huán)境的JAR包(比如framework.jar)和組成Android系統(tǒng)的各種動態(tài)庫so等等,由于包的種類和數(shù)量繁多,就需要進(jìn)行包管理,但是包管理需要在內(nèi)存中進(jìn)行,而這些包都是以靜態(tài)文件的形式存在的,就需要一個工具類將這些包轉(zhuǎn)換為內(nèi)存中的數(shù)據(jù)結(jié)構(gòu),這個工具就是包解析器PackageParser。

Android包管理機制(三)PMS處理APK的安裝這篇文章中,我們知道安裝APK時需要調(diào)用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處創(chuàng)建PackageParser,然后在注釋2處調(diào)用PackageParser的parsePackage方法來解析APK。

2.PackageParser解析APK

Android5.0引入了Split APK機制,這是為了解決65536上限以及APK安裝包越來越大等問題。Split APK機制可以將一個APK,拆分成多個獨立APK。
在引入了Split APK機制后,APK有兩種分類:

  • Single APK:安裝文件為一個完整的APK,即base APK。Android稱其為Monolithic。
  • Mutiple APK:安裝文件在一個文件目錄中,其內(nèi)部有多個被拆分的APK,這些APK由一個 base APK和一個或多個split APK組成。Android稱其為Cluster。

了解了APK,我們接著學(xué)習(xí)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,就需要調(diào)用parseClusterPackage方法來解析,如果是Single APK則調(diào)用parseMonolithicPackage方法來解析。這里以復(fù)雜的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處調(diào)用parseClusterPackageLite方法用于輕量級解析目錄文件,之所以要輕量級解析是因為解析APK是一個復(fù)雜耗時的操作,這里的邏輯并不需要APK所有的信息。parseClusterPackageLite方法內(nèi)部會通過parseApkLite方法解析每個Mutiple APK,得到每個Mutiple APK對應(yīng)的ApkLite(輕量級APK信息),然后再將這些ApkLite封裝為一個PackageLite(輕量級包信息)并返回。
注釋2處,mOnlyCoreApps用來指示PackageParser是否只解析“核心”應(yīng)用,“核心”應(yīng)用指的是AndroidManifest中屬性coreApp值為true,只解析“核心”應(yīng)用是為了創(chuàng)建一個極簡的啟動環(huán)境。mOnlyCoreApps在創(chuàng)建PMS時就一路傳遞過來,如果我們加密了設(shè)備,mOnlyCoreApps值就為true,具體的見Android包管理機制(四)PMS的創(chuàng)建過程這篇文章的第1小節(jié)。另外可以通過PackageParser的setOnlyCoreApps方法來設(shè)置mOnlyCoreApps的值。
lite.coreApp表示當(dāng)前包是否包含“核心”應(yīng)用,如果不滿足注釋2的條件就會拋出異常。
注釋3處的parseBaseApk方法用于解析base APK,注釋4處獲取split APK的數(shù)量,根據(jù)這個數(shù)量在注釋5處遍歷調(diào)用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) {
                throw new PackageParserException(mParseError,
                        apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
            }
            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處用于以后標(biāo)識這個解析后的Package,注釋4處的用于標(biāo)識該App所在的存儲卷UUID。
注釋2處又調(diào)用了parseBaseApk的重載方法,可以看出當(dāng)前的parseBaseApk方法主要是為了獲取和設(shè)置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的對應(yīng)屬性        
        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處創(chuàng)建了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的對應(yīng)的屬性。
注釋3處讀取APK的AndroidManifest中的coreApp的值。
最后會調(diào)用parseBaseApkCommon方法,這個方法非常長,主要用來解析APK的AndroidManifest中的各個
標(biāo)簽,比如application、permission、uses-sdk、feature-group等等,其中四大組件的標(biāo)簽在application標(biāo)簽下,解析application標(biāo)簽的方法為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行代碼,這里只截取了解析四大組件相關(guān)的代碼。注釋1處如果標(biāo)簽名為activity,就調(diào)用注釋2處的parseActivity方法解析activity標(biāo)簽并得到一個Activity對象(PackageParser的靜態(tài)內(nèi)部類),這個方法有300多行代碼,解析一個activity標(biāo)簽就如此繁瑣,activity標(biāo)簽只是Application中眾多標(biāo)簽的一個,而Application只是AndroidManifest眾多標(biāo)簽的一個,這讓我們更加理解了為什么此前解析APK時要使用輕量級解析了。注釋3處將解析得到的Activity對象保存在Package的列表activities中。其他的四大組件也是類似的邏輯。
PackageParser解析APK的代碼邏輯非常龐大,基本了解本文所講的就足夠了,如果有興趣可以自行看源碼。
parseBaseApk方法主要的解析結(jié)構(gòu)可以理解為以下簡圖。


3.Package的數(shù)據(jù)結(jié)構(gòu)

包被解析后,最終在內(nèi)存是Package,Package是PackageParser的內(nèi)部類,它的部分成員變量如下所示。
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的靜態(tài)內(nèi)部類,Package中的其他列表也都是如此。Package的數(shù)據(jù)結(jié)構(gòu)簡圖如下所示。


從這個簡圖中可以發(fā)現(xiàn)Package的數(shù)據(jù)結(jié)構(gòu)是如何設(shè)計的:

  • Package中存有許多組件,比如Acticity、Provider、Permission等等,它們都繼承基類Component。
  • 每個組件都包含一個info數(shù)據(jù),比如Activity類中包含了成員變量ActivityInfo,這個ActivityInfo才是真正的Activity數(shù)據(jù)。
  • 四大組件的標(biāo)簽內(nèi)可能包含<intent-filter>來過濾Intent信息,因此需要IntentInfo來保存組件的intent信息,組件基類Component依賴于IntentInfo,IntentInfo有三個子類ActivityIntentInfo、ServiceIntentInfo和ProviderIntentInfo,不同組件依賴的IntentInfo會有所不同,比如Activity繼承自Component<ActivityIntentInfo> ,Permission繼承自Component<IntentInfo> 。

最終的解析的數(shù)據(jù)會封裝到Package中,除此之外在解析過程中還有兩個輕量級數(shù)據(jù)結(jié)構(gòu)ApkLite和PackageLite,因為這兩個數(shù)據(jù)和Package沒有太大的關(guān)聯(lián)就沒有在上圖中表示。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容