Android系統服務PMS 包的解析過程(三)

語言: CN / TW / HK

一、背景

包的形式有各種各樣的包,如 xx.apk、xxx.jar、xx.so。PMS作為包的管理中心,管理Android中各種各樣的包。

而管理是需要在記憶體中才能進行的,因此,如何把靜態形式的資料轉變為記憶體中對應的資料結構,這就需要通過掃描來完成。

二、 包的掃描過程

我們關心apk的掃描過程。本質我們關心從靜態apk檔案 轉變成記憶體中的資料結構是什麼樣的!

不管是 system/framework分割槽 還是 data/分割槽。都會呼叫這個方法來進行掃描。

2.1 scanDirTracedLI()

xxxLI結尾的方法表明需要 mInstaller 鎖。 xxxLP結尾的方法表明需要 mPackages 鎖。

private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]"); try { scanDirLI(scanDir, parseFlags, scanFlags, currentTime); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } }

這個方法僅僅追加了trace日誌。

``` private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) { final File[] files = scanDir.listFiles(); ....... try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser( mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir, mParallelPackageParserCallback)) { // Submit files for parsing in parallel int fileCount = 0; // 遍歷檔案 for (File file : files) { final boolean isPackage = (isApkFile(file) || file.isDirectory()) && !PackageInstallerService.isStageName(file.getName()); if (!isPackage) { // Ignore entries which are not packages continue; } //1 解析 parallelPackageParser.submit(file, parseFlags); fileCount++; }

        // Process results one by one 2 解析完成後,處理結果
        for (; fileCount > 0; fileCount--) {
            ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
            Throwable throwable = parseResult.throwable;
            int errorCode = PackageManager.INSTALL_SUCCEEDED;

            if (throwable == null) {
                // TODO(toddke): move lower in the scan chain
                // Static shared libraries have synthetic package names
                if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
                    renameStaticSharedLibraryPackage(parseResult.pkg);
                }
                try {
                    scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
                            currentTime, null);
                } catch (PackageManagerException e) {
                    errorCode = e.error;
                    Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
                }
            } else if (throwable instanceof PackageParser.PackageParserException) {
                PackageParser.PackageParserException e = (PackageParser.PackageParserException)
                        throwable;
                errorCode = e.error;
                Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
            } else {
                throw new IllegalStateException("Unexpected exception occurred while parsing "
                        + parseResult.scanFile, throwable);
            }

            // Delete invalid userdata apps
            if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&
                    errorCode != PackageManager.INSTALL_SUCCEEDED) {
                logCriticalInfo(Log.WARN,
                        "Deleting invalid package at " + parseResult.scanFile);
                removeCodePathLI(parseResult.scanFile);
            }
        }
    }
}

`` 遍歷給到目錄下檔案,開始解析。我們看看ParallelPackageParse` 類的submit()方法。

2.2 ParallelPackageParse.submit()

public void submit(File scanFile, int parseFlags) { //mService是執行緒池 mService.submit(() -> { ParseResult pr = new ParseResult(); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]"); try { // 1 PackageParser pp = new PackageParser(); pp.setSeparateProcesses(mSeparateProcesses); pp.setOnlyCoreApps(mOnlyCore); pp.setDisplayMetrics(mMetrics); pp.setCacheDir(mCacheDir); pp.setCallback(mPackageParserCallback); pr.scanFile = scanFile; // 解析的檔案 // 2 pr.pkg = parsePackage(pp, scanFile, parseFlags); } catch (Throwable e) { pr.throwable = e; } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } try { mQueue.put(pr); // 3 } 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(); } }); } 1. 構建解析物件,設定解析引數 2. 解析得到 package 物件 ,賦值給result返回 3. 把解析結果加入佇列

具體看看第二步的解析過程,如何得到package物件的。

@VisibleForTesting protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile, int parseFlags) throws PackageParser.PackageParserException { return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */); } 最終呼叫了 packageParser類的 parsePackage()方法。

``` @UnsupportedAppUsage public Package parsePackage(File packageFile, int flags, boolean useCaches) throws PackageParserException { // 如果useCaches=true,優先獲取快取 Package parsed = useCaches ? getCachedResult(packageFile, flags) : null; if (parsed != null) { return parsed; }

    long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
    if (packageFile.isDirectory()) { 
       // cluster 多個splits apk 情況
        parsed = parseClusterPackage(packageFile, flags);
    } else {
       // monolithic 完整包。只要一個apk 
        parsed = parseMonolithicPackage(packageFile, flags);
    }

    long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
    cacheResult(packageFile, flags, parsed);
    if (LOG_PARSE_TIMINGS) {
        parseTime = cacheTime - parseTime;
        cacheTime = SystemClock.uptimeMillis() - cacheTime;
        if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) {
            Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime
                    + "ms, update_cache=" + cacheTime + " ms");
        }
    }
    return parsed;
}

``` 如果是資料夾,則對應的是splits包,否則是完整包。

splits包: 對應一個baseApk和一個或多個的aplits apk。

完整包,就是單個完整的apk檔案。

先看完整包的解析。

2.2 parseMonolithicPackage()

  • PackageLite:單個包輕量級的資訊 Lightweight parsed details about a single package.
  • ApkLite:單個apk檔案的一些簡單的資訊 Lightweight parsed details about a single APK file.

``` public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException { // 1 final PackageLite lite = parseMonolithicPackageLite(apkFile, flags); if (mOnlyCoreApps) { if (!lite.coreApp) { throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "Not a coreApp: " + apkFile); } }

    final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
    try {// 2
        final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
        pkg.setCodePath(apkFile.getCanonicalPath());
        pkg.setUse32bitAbi(lite.use32bitAbi);
        return pkg;
    } catch (IOException e) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to get path: " + apkFile, e);
    } finally {
        IoUtils.closeQuietly(assetLoader);
    }
}

`` 1. 解析清單檔案,得到 PackageLite 物件,它包含了一些包的輕量級的資訊。如 安裝位置、程式碼路徑、ApkLite的基本資訊等。 2. 最終呼叫 核心介面parseBaseApk(),得到Package` 物件。

2.2.1 parseBaseApk()

``` private Package parseBaseApk(File apkFile, AssetManager assets, int flags) throws PackageParserException { final String apkPath = apkFile.getAbsolutePath();

        ......
        // 1
        parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
        // 2 
        final Resources res = new Resources(assets, mMetrics, null);

        final String[] outError = new String[1];
        // 3
        final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
        // 4 
        pkg.setVolumeUuid(volumeUuid);
        pkg.setApplicationVolumeUuid(volumeUuid);
        pkg.setBaseCodePath(apkPath);
        pkg.setSigningDetails(SigningDetails.UNKNOWN);

        return pkg;

}

`` 1. 通過AssetManager得到清單檔案的解析器 parser 2. 新建一個 resources 資源物件 3. 呼叫過載方法同名方法parseBaseApk()` 4. 賦值pkg一些資訊

2.2.2 parseBaseApk() 重名方法

``` private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { final String splitName; final String pkgName;

    try {
        Pair<String, String> packageSplit = parsePackageSplitNames(parser, parser);
        pkgName = packageSplit.first;
        splitName = packageSplit.second;

        if (!TextUtils.isEmpty(splitName)) { //1 
            outError[0] = "Expected base APK, but found split " + splitName;
            mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
            return null;
        }
    } catch (PackageParserException e) {
        mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
        return null;
    }

    ......
    // 2 
    final Package pkg = new Package(pkgName);

    TypedArray sa = res.obtainAttributes(parser,
            com.android.internal.R.styleable.AndroidManifest);

    pkg.mVersionCode = sa.getInteger(
            com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
    pkg.mVersionCodeMajor = sa.getInteger(
            com.android.internal.R.styleable.AndroidManifest_versionCodeMajor, 0);
    pkg.applicationInfo.setVersionCode(pkg.getLongVersionCode());
    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.mCompileSdkVersion = sa.getInteger(
            com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0);
    pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion;
    pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString(
            com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0);
    if (pkg.mCompileSdkVersionCodename != null) {
        pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern();
    }
    pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename;

    sa.recycle();
    // 3 
    return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
}

`` 1. 這裡只解析 baseApk,如果是splitApk,則返回 2. 建立package 物件,並賦值基礎的資訊 3. 呼叫parseBaseApkCommon()`

2.2.3 parseBaseApkCommon()

``` private Package parseBaseApkCommon(Package pkg, Set acceptedTags, Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { ...... // 1 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; }

        String tagName = parser.getName();    
        ......   
        if (tagName.equals(TAG_APPLICATION)) {
           ......
            foundApp = true;
            // 2
            if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
                return null;
            }
        } else if (tagName.equals(TAG_KEY_SETS)) {
            if (!parseKeySets(pkg, res, parser, outError)) {
                return null;
            }
            // 3 
        } else if (tagName.equals(TAG_PERMISSION_GROUP)) { //許可權組
            if (!parsePermissionGroup(pkg, flags, res, parser, outError)) {
                return null;
            }
        } else if (tagName.equals(TAG_PERMISSION)) {  //許可權
            if (!parsePermission(pkg, res, parser, outError)) {
                return null;
            }
        } else if (tagName.equals(TAG_PERMISSION_TREE)) { //許可權相關
            if (!parsePermissionTree(pkg, res, parser, outError)) {
                return null;
            }
        } else if (tagName.equals(TAG_USES_PERMISSION)) {//使用者許可權
            if (!parseUsesPermission(pkg, res, parser)) {
                return null;
            }
        }  else if (tagName.equals(TAG_PROTECTED_BROADCAST)) {
        ......
 }
 return

}
`` 1.遍歷清單檔案的xml節點 2. 解析application` 標籤節點 3. 解析許可權相關的節點 4. 其他節點

方法很長,我們只關注重點部分: application標籤的解析。

2.2.4 parseBaseApplication()

``` private boolean parseBaseApplication(Package owner, Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { final ApplicationInfo ai = owner.applicationInfo; final String pkgName = owner.applicationInfo.packageName;

  TypedArray sa = res.obtainAttributes(parser,
          com.android.internal.R.styleable.AndroidManifestApplication);

  ......        
  int type;
    boolean hasActivityOrder = false;
    boolean hasReceiverOrder = false;
    boolean hasServiceOrder = false;
    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();
        // 1
        if (tagName.equals("activity")) {
            Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
                    owner.baseHardwareAccelerated);
            if (a == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            hasActivityOrder |= (a.order != 0);
            owner.activities.add(a);
        // 2
        } else if (tagName.equals("receiver")) {
            Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
                    true, false);
            if (a == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            hasReceiverOrder |= (a.order != 0);
            owner.receivers.add(a);
        // 3
        } else if (tagName.equals("service")) {
            Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
            if (s == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            hasServiceOrder |= (s.order != 0);
            owner.services.add(s);
         //  4
        } else if (tagName.equals("provider")) {
            Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
            if (p == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            owner.providers.add(p);

        } else if (tagName.equals("activity-alias")) {
            Activity a = parseActivityAlias(owner, res, parser, flags, outError, cachedArgs);
            if (a == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }

            hasActivityOrder |= (a.order != 0);
            owner.activities.add(a);

        } else if (parser.getName().equals("meta-data")) {
            // note: application meta-data is stored off to the side, so it can
            // remain null in the primary copy (we like to avoid extra copies because
            // it can be large)
            if ((owner.mAppMetaData = parseMetaData(res, parser, owner.mAppMetaData,
                    outError)) == null) {
                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                return false;
            }
        }else 
        ......

    }

  }
  retur true;

}
```

  1. 解析得到 Activity標籤對應的記憶體中Activity物件。 Activity a = new Activity(cachedArgs.mActivityArgs, new ActivityInfo());
  2. 解析 receiver標籤,
  3. 解析 service 標籤,得到 Service s = new Service(cachedArgs.mServiceArgs, new ServiceInfo());
  4. 解析 provider 標籤,得到 Provider p = new Provider(cachedArgs.mProviderArgs, new ProviderInfo());
  5. 解析 meta-data、activity-alias等標籤

至此,我們得到了 application和四大元件在記憶體中的資訊。 至於在記憶體中的資料結構是怎麼樣的,放到最後總結。

再看看splits包的解析。

2.3 parseClusterPackage()

``` private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {

    final PackageLite lite = parseClusterPackageLite(packageDir, 0); // 1
    if (mOnlyCoreApps && !lite.coreApp) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                "Not a coreApp: " + packageDir);
    }

    // Build the split dependency tree.
    SparseArray<int[]> splitDependencies = null;
    final SplitAssetLoader assetLoader;
    if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
        try {
            splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
            assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
        } catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
        }
    } else {
        assetLoader = new DefaultSplitAssetLoader(lite, flags);
    }

    try {
        final AssetManager assets = assetLoader.getBaseAssetManager();
        final File baseApk = new File(lite.baseCodePath);
        // 2 
        final Package pkg = parseBaseApk(baseApk, assets, flags);
        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;
            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;
            pkg.applicationInfo.splitClassLoaderNames = new String[num];

            for (int i = 0; i < num; i++) {
                final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
                // 3 
                parseSplitApk(pkg, i, splitAssets, flags);
            }
        }

        pkg.setCodePath(packageDir.getCanonicalPath());
        pkg.setUse32bitAbi(lite.use32bitAbi);
        return pkg;
    } catch (IOException e) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to get path: " + lite.baseCodePath, e);
    } finally {
        IoUtils.closeQuietly(assetLoader);
    }
}

`` 1. 也是先解析得到包的輕量級資訊 packageLite,裡面包含了一個 apkLite物件。 2. 解析baseApk,流程跟解析完整包一樣 3. 解析splits apk,內部也是通過解析清單檔案`來構建記憶體中對應的資料結構

綜上, 包掃描的最終目的就是得到記憶體中包對應的資料結構,那麼這些資料結構是什麼關係呢?

三、解析後的資料結構

image.png

3.1 Package

PackageParser 中的內部類。Package 物件在記憶體中表示一個完整apk檔案。 - 包含一個baseApk以及0個或者多個splits Apks。 - 包含了所有四大元件的集合。 - 其他基礎資訊 此外還有 PackageLiteApkLite 等值包含輕量級資訊(如 包名、版本號、程式碼路徑等)的物件。 ``` public final static class Package implements Parcelable {

    @UnsupportedAppUsage
    public String packageName;

    public String manifestPackageName;

    /** Names of any split APKs, ordered by parsed splitName */
    public String[] splitNames;

    // TODO: work towards making these paths invariant
    public String volumeUuid;

    /**
     * Path where this package was found on disk. For monolithic packages
     * this is path to single base APK file; for cluster packages this is
     * path to the cluster directory.
     */
    public String codePath;

    /** Path of base APK */
    public String baseCodePath;
    /** Paths of any split APKs, ordered by parsed splitName */
    public String[] splitCodePaths;

    /** Revision code of base APK */
    public int baseRevisionCode;
    /** Revision codes of any split APKs, ordered by parsed splitName */
    public int[] splitRevisionCodes;

    /** Flags of any split APKs; ordered by parsed splitName */
    public int[] splitFlags;

    /**
     * Private flags of any split APKs; ordered by parsed splitName.
     *
     * {@hide}
     */
    public int[] splitPrivateFlags;

    public boolean baseHardwareAccelerated;

    // For now we only support one application per package.
    @UnsupportedAppUsage
    public ApplicationInfo applicationInfo = new ApplicationInfo();

    @UnsupportedAppUsage
    public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
    @UnsupportedAppUsage
    public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
    @UnsupportedAppUsage
    public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
    @UnsupportedAppUsage
    public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
    @UnsupportedAppUsage
    public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
    @UnsupportedAppUsage
    public final ArrayList<Service> services = new ArrayList<Service>(0);
    @UnsupportedAppUsage
    public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);

    @UnsupportedAppUsage
    public final ArrayList<String> requestedPermissions = new ArrayList<String>();

    /** Permissions requested but not in the manifest. */
    public final ArrayList<String> implicitPermissions = new ArrayList<>();

    @UnsupportedAppUsage
    public ArrayList<String> protectedBroadcasts;
    ......

}

```

3.2 Component

PackageParser 中的內部類。 這是一個抽象類,具體的子類為四大元件以及許可權。如Activity、Service、Permission每個元件子類 都有一個info成員變數,對應一個ComponentInfo結構。 元件中真正的資料存放在info中。此外,每個元件可能還配置有一個或多個 IntentFilter 資訊,因此,內部有一個intents 集合,對應記憶體中 IntentInfo。

3.3 ComponentInfo

PackageParser中的內部類。 真正存放資料的地方。 ActivityInfo、ServiceInfo等。

3.4 IntentInfo

PackageParser中的內部類。Component中有個 intents 成員集合,用來存放intent相關的資訊。如:ActivityIntentInfo、ServiceIntentInfo 和ProviderIntentInfo。

四、總結

靜態包的檔案形式,通過 PackageParser 解析最終得以轉變為記憶體中的資料結構 Package,它包含了整個apk包的資訊,如 Application、Activity、Service等。至此,PMS就可以愉快的管理起來了!

五、感謝

http://liuwangshu.cn/framework/pms/5-packageparser.html

http://blog.csdn.net/yiranfeng/article/details/103961304

http://duanqz.github.io/2017-01-04-Package-Manage-Mechanism