本文首發於微信公衆號「劉望舒」
原文連接:APK是如何被解析的?html
相關文章 包管理機制系列前端
在本系列的前面文章中,我介紹了PackageInstaller的初始化和安裝APK過程、PMS處理APK的安裝和PMS的建立過程,這些文章中常常會涉及到一個類,那就是PackageParser,它用來在APK的安裝過程當中解析APK,那麼APK是如何被解析的呢?這篇文章會給你答案。java
Android世界中有不少包,好比應用程序的APK,Android運行環境的JAR包(好比framework.jar)和組成Android系統的各類動態庫so等等,因爲包的種類和數量繁多,就須要進行包管理,可是包管理須要在內存中進行,而這些包都是以靜態文件的形式存在的,就須要一個工具類將這些包轉換爲內存中的數據結構,這個工具就是包解析器PackageParser。android
在Android包管理機制(三)PMS處理APK的安裝這篇文章中,咱們知道安裝APK時須要調用PMS的installPackageLI方法: frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.javagit
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。github
Android5.0引入了Split APK機制,這是爲了解決65536上限以及APK安裝包愈來愈大等問題。Split APK機制能夠將一個APK,拆分紅多個獨立APK。 在引入了Split APK機制後,APK有兩種分類:微信
瞭解了APK,咱們接着學習PackageParser解析APK,查看PackageParser的parsePackage方法: frameworks/base/core/java/android/content/pm/PackageParser.javacookie
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.javaapp
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方法主要的解析結構能夠理解爲如下簡圖。
包被解析後,最終在內存是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的數據結構是如何設計的:
<intent-filter>
來過濾Intent信息,所以須要IntentInfo來保存組件的intent信息,組件基類Component依賴於IntentInfo,IntentInfo有三個子類ActivityIntentInfo、ServiceIntentInfo和ProviderIntentInfo,不一樣組件依賴的IntentInfo會有所不一樣,好比Activity繼承自Component<ActivityIntentInfo>
,Permission繼承自Component<IntentInfo>
。最終的解析的數據會封裝到Package中,除此以外在解析過程當中還有兩個輕量級數據結構ApkLite和PackageLite,由於這兩個數據和Package沒有太大的關聯就沒有在上圖中表示。
感謝
duanqz.github.io/2017-01-04-…
www.jianshu.com/p/69fb6f9a6…
分享Android、Java和大前端相關技術