--- title: 'PackageManagerService - APK 解析' disqus: kyleAlien --- PackageManagerService - APK 解析 === :::success PackageManagerService 主流程請看 [**PackageManagerService 篇**](https://hackmd.io/zlyaXIOJR-mUdvdk3u84Gg?view) ::: ## OverView of Content [TOC] ## APK 解析 在 PKMS 的建構函數中 (第二、三階段) 有使用到 **scanDirTracedLI** 方法來掃描某個目錄的 APK 文件 以下是 Android 10.0 的 **系統 APP** | APP 類別 | 目錄 | other | | -------- | -------- | -------- | | 系統 APP | /vendor/overlay | | | 系統 APP | /product/overlay | | | 系統 APP | /product_services/overlay | | | 系統 APP | /odm/overlay | | | 系統 APP | /oem/overlay | | | 系統 APP | /system/framework | ![](https://i.imgur.com/bjTkxNp.png) | | 系統 APP | /system/priv-app | ![](https://i.imgur.com/0uSUx8c.png) | | 系統 APP | /system/app | ![](https://i.imgur.com/Vz0tzLv.png) | | 系統 APP | /vendor/priv-app | | | 系統 APP | /vendor/app | | | 系統 APP | /odm/priv-app | | | 系統 APP | /odm/app | | | 系統 APP | /oem/app | | | 系統 APP | /oem/priv-app | | | 系統 APP | /product/priv-app | | | 系統 APP | /product/app | | | 系統 APP | /product_services/priv-app | | | 系統 APP | /product_services/app | | ### scanDirTracedLI 掃描資料夾 * PKMS#**scanDirTracedLI** 方法取名:是指在 `Lock mInstallLock` 的狀態下掃描 目錄 & APK 文件 :::warning * PKMS#方法取名規則:xxx(方法名) + LI、LIF、LPw、LPr ? [**參考**](https://david1840.github.io/2021/05/13/Android%E7%B3%BB%E7%BB%9F-PMS-scanPackageDirtyLI%E7%9A%84LI%E6%98%AF%E4%BB%80%E4%B9%88%E6%84%8F%E6%80%9D/) | 關鍵字 | 說明 | | -------- | -------- | | L | Lock | | I | mInstallLock | | P | mPackages | | w | writing | | r | reading | | F | Freeze | scanDirTracedLI 就是在呼叫該方法前需要保證鎖住 mInstallLock 對象 ::: * PKMS#scanDirTracedLI 方法會根據傳入的第一個參數決定要掃描哪一個目錄 ```java= // PackageManagerService.java private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime, PackageParser2 packageParser, ExecutorService executorService) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]"); try { // @ 分析 scanDirLI scanDirLI(scanDir, parseFlags, scanFlags, currentTime, packageParser, // Parser APK 類 executorService); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } ``` ### scanDirLI - [ParallelPackageParser](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/ParallelPackageParser.java) 解析隊列 * PKMS#scanDirLI 會使用 ParallelPackageParser (對列類) 來收集目標資料夾內的 APK 文件並解析(解析部份後面分析) ```java= // PackageManagerService.java private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime, PackageParser2 packageParser, ExecutorService executorService) { // 指定目錄下的所有檔案 final File[] files = scanDir.listFiles(); ... 省略判空 ParallelPackageParser parallelPackageParser = new ParallelPackageParser(packageParser, executorService); // Submit files for parsing in parallel int fileCount = 0; for (File file : files) { // 是 APK or 目錄 final boolean isPackage = (isApkFile(file) || file.isDirectory()) && !PackageInstallerService.isStageName(file.getName()); if (!isPackage) { // 過濾非 APK 文件 continue; } // 把 APK 文件存入 ParallelPackageParser // @ 分析 ++submit 函數++ parallelPackageParser.submit(file, parseFlags); fileCount++; } // 一個個處理 submit 掃描的結果 for (; fileCount > 0; fileCount--) { // 取出掃描結過 ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take(); Throwable throwable = parseResult.throwable; int errorCode = PackageManager.INSTALL_SUCCEEDED; String errorMsg = null; if (throwable == null) { // 使用靜態 library if (parseResult.parsedPackage.isStaticSharedLibrary()) { renameStaticSharedLibraryPackage(parseResult.parsedPackage); } try { // 在手機平台初始化時添加新的 package 到 內部 data 中 addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags, currentTime, null); } /* 省略 catch */ } /* 省略其他類型的錯誤 */ ... 省略部分 // 如果非系統 APK && 解析失敗 if ((scanFlags & SCAN_AS_SYSTEM) == 0 && errorCode != PackageManager.INSTALL_SUCCEEDED) { logCriticalInfo(Log.WARN, "Deleting invalid package at " + parseResult.scanFile); // 刪除 APP 文件 removeCodePathLI(parseResult.scanFile); } } } ``` * [**ParallelPackageParser**](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/ParallelPackageParser.java)#submit:把路徑中的 APK 內容異步線程池中,並在解析完後把 ParseResult 結果放入 BlockingQueue 隊列 ```java= // ParallelPackageParser.java // QUEUE_CAPACITY = 30 private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY); private final PackageParser2 mPackageParser; private final ExecutorService mExecutorService; ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService) { mPackageParser = packageParser; mExecutorService = executorService; } public void submit(File scanFile, int parseFlags) { // 使用線程池 mExecutorService.submit(() -> { ParseResult pr = new ParseResult(); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]"); try { pr.scanFile = scanFile; // @ 注意 ++parsePackage++ 函數 pr.parsedPackage = parsePackage(scanFile, parseFlags); } catch (Throwable e) { pr.throwable = e; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } try { // 將處理好的任務放入對列 mQueue.put(pr); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // Propagate result to callers of take(). // This is helpful to prevent main thread from getting stuck waiting on // ParallelPackageParser to finish in case of interruption mInterruptedInThread = Thread.currentThread().getName(); } }); } @VisibleForTesting protected ParsedPackage parsePackage(File scanFile, int parseFlags) throws PackageParser.PackageParserException { // @ 調用 PackageParser2#mPackageParser return mPackageParser.parsePackage(scanFile, parseFlags, true); } ``` > ![](https://i.imgur.com/k1RWUvH.png) ### PackageParser2 - 解析 APK 文件 * ParallelPackageParser 類的 submit 方法最終會調用 [**PackageParser2**](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/parsing/PackageParser2.java)#parsePackage: 對 APK 進行解析 (調用 ParsingPackageUtils 解析) ```java= // PackageParser2.java private ParsingPackageUtils parsingUtils; private ThreadLocal<ParseTypeImpl> mSharedResult; public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches) throws PackageParserException { ... 省略部分 ParseInput input = mSharedResult.get().reset(); // 透過 ParsingPackageUtils 解析 APK ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags); if (result.isError()) { throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(), result.getException()); } ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed(); ... 省略部分 return parsed; } ``` * [**ParsingPackageUtils**](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/content/pm/parsing/ParsingPackageUtils.java)#parsePackage: ^1^ 如果傳入是目錄調用 `parseClusterPackage`,^2^ 否則調用 `parseMonolithicPackage` 解析 APK :::info * **Split APK 機制** 在 Android 5.0 後引入了 Split APK 機制,其目的是為了解決 DVM 對方法的 65535 上限限制,透過 Split 機制可以將大型 APK 切成多個獨立 APK 1. Single APK:一個完整個 APK 文件,由單個 baseAPK 完成,**又稱為 Monlithic** 2. Mutiple APK:在一個文件目錄中安裝應用,它是由一個 `baseAPK` + 多個 `splitApk` 組成,**又稱為 Cluster** > ![](https://i.imgur.com/hRUfuxX.png) ::: 以下分析較難的 parseClusterPackage 方法 ```java= // ParsingPackageUtils.java public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) throws PackageParserException { if (packageFile.isDirectory()) { // 多個文件 // @ 分析 parseClusterPackage return parseClusterPackage(input, packageFile, flags); } else { // 解析 單個 APK 文件 return parseMonolithicPackage(input, packageFile, flags); } } ``` > ![](https://i.imgur.com/koCSZFi.png) * parseClusterPackage 分析 1. 輕量解析目錄文件 2. 判斷是否是核心應用,如果是該 APP 不是核心應用,但目前是 CoreApps 模式(詳細需要往上查找 PKMS 創建),則會拋出異常 3. 解析 baseAPK 文件,下面繼續分析 parseBaseApk 方法 4. 解析 splitAPK 文件(1 ~ 多個) ```java= // ParsingPackageUtils.java private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir, int flags) { // 1. 輕量解析目錄文件 final ParseResult<PackageLite> liteResult = ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0); if (liteResult.isError()) { return input.error(liteResult); } final PackageLite lite = liteResult.getResult(); // 2. 是否只解析核心應用 // 所謂的核心是為了創建極簡的啟動環境 // // mOnlyCoreApps:只要設備加密就會是 true (從外部傳入) // lite.isCoreApp():當前包是否含有核心應用,對應 AndroidManifest 中的 coreApp 值 if (mOnlyCoreApps && !lite.isCoreApp()) { return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED, "Not a coreApp: " + packageDir); } ... 省略部份 try { // 3. 取得 baseAPK 檔案 final File baseApk = new File(lite.getBaseApkPath()); // 解析複製好的 baseAPK 檔案 // 分析 @ parseBaseApk final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk, lite.getPath(), assetLoader, flags); if (result.isError()) { return input.error(result); } ParsingPackage pkg = result.getResult(); // 判斷是否有 splitApk if (!ArrayUtils.isEmpty(lite.getSplitNames())) { pkg.asSplit( lite.getSplitNames(), lite.getSplitApkPaths(), lite.getSplitRevisionCodes(), splitDependencies ); // 取得 splitApk 的數量 final int num = lite.getSplitNames().length; for (int i = 0; i < num; i++) { final AssetManager splitAssets = assetLoader.getSplitAssetManager(i); // 解析每個 splitApk 檔案 parseSplitApk(input, pkg, i, splitAssets, flags); } } pkg.setUse32BitAbi(lite.isUse32bitAbi()); return input.success(pkg); } catch (PackageParserException e) { return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed to load assets: " + lite.getBaseApkPath(), e); } finally { IoUtils.closeQuietly(assetLoader); } } ``` > ![](https://i.imgur.com/f6VcXOJ.png) ### [ParsingPackageUtils](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/content/pm/parsing/ParsingPackageUtils.java) - parseBaseApk 分析 AndroidManifest.xml * 當找到要分析的目標 APK 後,就會調用 ParsingPackageUtils#parseBaseApk 方法 1. 如果 baseApk 絕對路徑是以 `/mnt/expand/` 開頭,就取 `/mnt/expand/` 後的設定為 volumeUuid 2. 透過 AssetManager 解析 baseApk 中的 `AndroidManifest.xml` 文件 3. 呼叫重載方法 parseBaseApk * 讀取 [**attrs_manifest.xml**](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/attrs_manifest.xml) 內的屬性集 * 透過 parseBaseApkTags 方法分析 AndroidManifest 基本屬性 ```java= // ParsingPackageUtils.java public static final String MNT_EXPAND = "/mnt/expand/"; public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile, String codePath, SplitAssetLoader assetLoader, int flags) throws PackageParserException { // 取得 baseApk 絕對位置 final String apkPath = apkFile.getAbsolutePath(); // 1. 取得 volumeUuid String volumeUuid = null; if (apkPath.startsWith(MNT_EXPAND)) { final int end = apkPath.indexOf('/', MNT_EXPAND.length()); volumeUuid = apkPath.substring(MNT_EXPAND.length(), end); } ... log 訊息 // 取得 AssetManager final AssetManager assets = assetLoader.getBaseAssetManager(); final int cookie = assets.findCookieForPath(apkPath); if (cookie == 0) { return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Failed adding asset path: " + apkPath); } // 2. 指定分析 AndroidManifest.xml 文件 try (XmlResourceParser parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME)) { final Resources res = new Resources(assets, mDisplayMetrics, null); // 3. @ 分析 parseBaseApk ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res, parser, flags); ... 省略部分 // 用於以後標示這個解析後的 Package pkg.setVolumeUuid(volumeUuid); ... return input.success(pkg); } /* 省略 catch */ } // ParseResult 方法重載 private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath, String codePath, Resources res, XmlResourceParser parser, int flags) throws XmlPullParserException, IOException { ... 省略部分 // 屬性集在 attrs_manifest.xml final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest); try { final boolean isCoreApp = parser.getAttributeBooleanValue(null, "coreApp", false); final ParsingPackage pkg = mCallback.startParsingPackage( pkgName, apkPath, codePath, manifestArray, isCoreApp); // @ 分析 parseBaseApkTags final ParseResult<ParsingPackage> result = parseBaseApkTags(input, pkg, manifestArray, res, parser, flags); if (result.isError()) { return result; } return input.success(pkg); } finally { // TypedArray 必須回收 manifestArray.recycle(); } } ``` > ![](https://i.imgur.com/qRhZvIS.png) * ParsingPackageUtils#`parseBaseApkTags` 分析 Apk 中的 AndroidManifest 的 Application、Activity、Service、Broadcast、ContentProvider 等等訊息 ```java= // ParsingPackageUtils.java private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg, TypedArray sa, Resources res, XmlResourceParser parser, int flags) throws XmlPullParserException, IOException { ... 省略部分 final int depth = parser.getDepth(); int type; // 在這裡會 Parser 完 AndroidManifest 全部的內容 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > depth)) { if (type != XmlPullParser.START_TAG) { continue; } String tagName = parser.getName(); final ParseResult result; if (TAG_APPLICATION.equals(tagName)) { if (foundApp) { // 一個 APP 只能有一個 Application 標籤 if (RIGID_PARSER) { result = input.error("<manifest> has more than one <application>"); } else { Slog.w(TAG, "<manifest> has more than one <application>"); result = input.success(null); } } else { foundApp = true; // Parser 四大組件 // Activity、Service、Broadcast、ContentProvider result = parseBaseApplication(input, pkg, res, parser, flags); } } else { // Parser 基本的元素,像權限組之類的 // @ parseBaseApkTag result = parseBaseApkTag(tagName, input, pkg, res, parser, flags); } ... 省略部分 } ... 省略部分 return input.success(pkg); } private ParseResult parseBaseApkTag(String tag, ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags) throws IOException, XmlPullParserException { switch (tag) case TAG_PERMISSION: return parsePermission(input, pkg, res, parser); ... 省略其他 case } } ``` > ![](https://i.imgur.com/Xb9a7MU.png) ### [ParsingPackageImpl](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/content/pm/parsing/ParsingPackageImpl.java) - 儲存 Package 訊息 * Android 組件解析關係圖,解析的實作交給 Parsed 實作類 (`ParsedActivity`、`ParsedService`... 等等) > ![](https://i.imgur.com/GCTr3lq.png) * ParsingPackageImpl 類負責儲存 APK 最終分析的結果,其中就包含了 `4 大組件`、 `packageName`、`versionCode`... 等等訊息 1. 每個組件中都含有 xxxInfo 數據、該 Info 才是該組件的數據 > * Activity 內會有 [**ActivityInfo**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/content/pm/ActivityInfo.java) > > * Service 內會有 [**ServiceInfo**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/content/pm/ServiceInfo.java) 2. 四大組件的標籤內有包含 `<intent-filter\>`,用來過濾 Intent 訊息,其 **Package 結果也會保存在 ParsingPackageImpl 中** (透過 ParsedIntentInfoUtils 類解析) :::info 其實就是保存了 **AndroidManifest.xml** 的訊息 ::: ```java= // ParsingPackageImpl.java public class ParsingPackageImpl implements ParsingPackage, Parcelable { private static final String TAG = "PackageImpl"; ... // Package 基本資料 protected int versionCode; protected int versionCodeMajor; private int baseRevisionCode; @Nullable @DataClass.ParcelWith(ForInternedString.class) private String versionName; private int compileSdkVersion; @Nullable @DataClass.ParcelWith(ForInternedString.class) private String compileSdkVersionCodeName; @NonNull @DataClass.ParcelWith(ForInternedString.class) protected String packageName; @Nullable @DataClass.ParcelWith(ForInternedString.class) private String realPackage; @NonNull protected String mBaseApkPath; // 四大組件 + 權限 @NonNull protected List<ParsedActivity> activities = emptyList(); @NonNull protected List<ParsedActivity> receivers = emptyList(); @NonNull protected List<ParsedService> services = emptyList(); @NonNull protected List<ParsedProvider> providers = emptyList(); @NonNull private List<ParsedAttribution> attributions = emptyList(); @NonNull protected List<ParsedPermission> permissions = emptyList(); @NonNull protected List<ParsedPermissionGroup> permissionGroups = emptyList(); @NonNull protected List<ParsedInstrumentation> instrumentations = emptyList(); // Intent info 訊息 @NonNull @DataClass.ParcelWith(ParsedIntentInfo.ListParceler.class) private List<Pair<String, ParsedIntentInfo>> preferredActivityFilters = emptyList(); @NonNull private Map<String, ParsedProcess> processes = emptyMap(); ... 省略部份 } ``` ### Package 解析結果 - 存到 PKMS Map 中 先回顧 scanDirLI 方法掃描完 APK 後,緊接著就是執行 `addForInitLI` 方法,將掃描結果除存到 PKMS 中,方便之後取用 ```java= // PackageManagerService.java private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime, PackageParser2 packageParser, ExecutorService executorService) { // 指定目錄下的所有檔案 final File[] files = scanDir.listFiles(); ... 省略判空 ParallelPackageParser parallelPackageParser = new ParallelPackageParser(packageParser, executorService); int fileCount = 0; for (File file : files) { ... 掃描 apk 文件 } // 一個個處理 submit 掃描的結果 for (; fileCount > 0; fileCount--) { // 取出掃描結過 ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take(); Throwable throwable = parseResult.throwable; int errorCode = PackageManager.INSTALL_SUCCEEDED; String errorMsg = null; if (throwable == null) { ... try { // @ 追蹤 addForInitLI 方法 addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags, currentTime, null); } /* 省略 catch */ } /* 省略其他類型的錯誤 */ ... 省略部分 } } ``` * PKMS#addForInitLI 方法:會在 PKMS 中儲存一個 Package 結構包,方便系統經後查詢(對於會對於 `/system`、`/vendor` 中的 APK 進行版本跟簽名檢查) :::danger 如果不能通過簽名檢查,則 APP 資料會從 `/data` 中被移除 ::: ```java= // PackageManagerService.java private AndroidPackage addForInitLI(ParsedPackage parsedPackage, @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime, @Nullable UserHandle user) throws PackageManagerException { ... 省略檢查 final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user, null); if (scanResult.success) { synchronized (mLock) { boolean appIdCreated = false; try { final String pkgName = scanResult.pkgSetting.name; final Map<String, ReconciledPackage> reconcileResult = reconcilePackagesLocked( new ReconcileRequest( Collections.singletonMap(pkgName, scanResult), mSharedLibraries, mPackages, Collections.singletonMap( pkgName, getSettingsVersionForPackage(parsedPackage)), Collections.singletonMap(pkgName, getSharedLibLatestVersionSetting(scanResult))), mSettings.getKeySetManagerService(), mInjector); appIdCreated = optimisticallyRegisterAppId(scanResult); // @ 追蹤 commitReconciledScanResultLocked 方法 commitReconciledScanResultLocked( reconcileResult.get(pkgName), mUserManager.getUserIds()); } /* 省略 catch */ } } ... 省略部份 return scanResult.pkgSetting.pkg; } ``` * PKMS#commitReconciledScanResultLocked 方法:提交掃描好的 Package 資訊,並修改系統狀態 ```java= // PackageManagerService.java private AndroidPackage commitReconciledScanResultLocked( @NonNull ReconciledPackage reconciledPkg, int[] allUsers) { final ScanResult result = reconciledPkg.scanResult; final ScanRequest request = result.request; ... 省略部份 final int userId = user == null ? 0 : user.getIdentifier(); // 修正 package 狀態 // @ 追蹤 commitPackageSettings 方法 commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags, (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg); if (pkgSetting.getInstantApp(userId)) { mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId); } pkgSetting.setStatesOnCommit(); return pkg; } ``` * PKMS 會將掃描好的 apk 檔案儲存到 `WatchedArrayMap` 中,將來要透過 PKMS 來找尋對應 APK 資料時就會到 `WatchedArrayMap` 中尋找 | Key | Value | | -------- | -------- | | Package name | AndroidPackage | ```java= // PackageManagerService.java final WatchedArrayMap<String, AndroidPackage> mPackages = new WatchedArrayMap<>(); private void commitPackageSettings(@NonNull AndroidPackage pkg, /* 省略部份參數 */) { // 取得分析好的 Package name final String pkgName = pkg.getPackageName(); ... 省略部份 synchronized (mLock) { ... // 將 package 訊息添加到 mPackages 表中 mPackages.put(pkg.getPackageName(), pkg); ... } } ``` ## Appendix & FAQ :::info ::: ###### tags: `Android Framework`