Android包管理機制(五)APK是如何被解析的
在本系列的前面文章中,我介紹了PackageInstaller的初始化和安裝APK過程、PMS處理APK的安裝和PMS的建立過程,這些文章中經常會涉及到一個類,那就是PackageParser,它用來在APK的安裝過程中解析APK,那麼APK是如何被解析的呢?這篇文章會給你答案。
1.引入PackageParser
Android世界中有很多包,比如應用程式的APK,Android執行環境的JAR包(比如framework.jar)和組成Android系統的各種動態庫so等等,由於包的種類和數量繁多,就需要進行包管理,但是包管理需要在記憶體中進行,而這些包都是以靜態檔案的形式存在的,就需要一個工具類將這些包轉換為記憶體中的資料結構,這個工具就是包解析器PackageParser。
在 ofollow,noindex">Android包管理機制(三)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。
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:安裝檔案在一個檔案目錄中,其內部有多個被拆分的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包管理機制(四)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) { 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處用於以後標識這個解析後的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處讀取APK的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方法主要的解析結構可以理解為以下簡圖。

3.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沒有太大的關聯就沒有在上圖中表示。
分享Android、Java和大前端相關技術
