基于Android P版本PKMS详解二

本文详细介绍了Android P版本中`PKMS`如何扫描系统目录如/system/framework、/system/app和/vendor/app,并通过`scanDirTracedLI`方法处理APK文件。`PKMS`使用`ParallelPackageParser`进行并行解析,处理队列中的APK文件,然后调用`PackageParser`解析。文章还提到了开机时间分析,展示了不同阶段的耗时,揭示了APP数量对开机时间的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由以上代码可知,`PKMS` 扫描了很多目录,下面列举几个重点说明:

 ✨ /system/framework :该目录中的文件都是系统库,例如:framework.jar、services.jar、framework-res.apk 等。不过 scanDirTracedLI 只扫描 APK 文件,所以 framework-res.apk 是该目录中唯一被扫描的文件。

 ✨ /system/app :该目录下全是默认的系统应用。例如:Browser.apk、SettingsProvider.apk 等。

 ✨ /vendor/app :该目录中的文件由厂商提供,即全是厂商特定的 APK 文件。

### 4.2.1 scanDirTracedLI

`PKMS` 扫描目录统一调用 `scanDirTracedLI` 方法:

```java {.line-numbers}
    public 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);
        }
    }
```

重点转移到 `scanDirLI` 方法:

```java {.line-numbers}
    private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
        final File[] files = scanDir.listFiles();
        if (ArrayUtils.isEmpty(files)) {
            Log.d(TAG, "No files in app dir " + scanDir);
            return;
        }
        ...
        // ParallelPackageParser 是 Android O 新增的一个类,可以理解它其实就是一个队列,
        // 收集系统 apk 文件,然后从这个队列里面一个个取出 apk ,调用 PackageParser 解析!
        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());
                // 过滤掉非 apk 文件,如果不是则跳过继续扫描
                if (!isPackage) {
                    // Ignore entries which are not packages
                    continue;
                }
                parallelPackageParser.submit(file, parseFlags);
                fileCount++;
            }

            // Process results one by one
            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 {
                        if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
                            /*
                             * Android P 调用 scanPackageChildLI,
                             * Android O 调用 scanPackageLI,
                             *
                             * 调用 scanPackageChildLI 方法扫描一个特定的 apk 文件,
                             * 返回值是 PackageParser 的内部类 Package,
                             * 该类的实例代表一个 APK 文件,所以它就是和 apk 文件对应的数据结构。
                             */
                            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
                // 如果是非系统 apk 并且解析失败
                if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&
                        errorCode != PackageManager.INSTALL_SUCCEEDED) {
                    logCriticalInfo(Log.WARN,
                            "Deleting invalid package at " + parseResult.scanFile);
                    // 非系统 Package 扫描失败,删除文件
                    removeCodePathLI(parseResult.scanFile);
                }
            }
        }
    }
```

看下 `ParallelPackageParser` 这个类:

> frameworks/base/services/core/java/com/android/server/pm/ParallelPackageParser.java

```java {.line-numbers}
/**
 * Helper class for parallel parsing of packages using {@link PackageParser}.
 * <p>Parsing requests are processed by a thread-pool of {@link #MAX_THREADS}.
 * At any time, at most {@link #QUEUE_CAPACITY} results are kept in RAM</p>
 */
class ParallelPackageParser implements AutoCloseable {

    private static final int QUEUE_CAPACITY = 10;
    private static final int MAX_THREADS = 4;

    private final String[] mSeparateProcesses;
    private final boolean mOnlyCore;
    private final DisplayMetrics mMetrics;
    private final File mCacheDir;
    private final PackageParser.Callback mPackageParserCallback;
    private volatile String mInterruptedInThread;

    // BlockingQueue 是一个 FIFO(先进先出)的 Queue(队列),是设计用来实现生产者-消费者队列的。
    // 它提供了可阻塞的插入和移除方法,当队列容器已满,生产者线程会被阻塞,直到队列未满;
    // 当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。
    // ArrayBlockingQueue 是由数组实现的有界阻塞队列,创建的时候需要指定 capacity(容量,可以存储的最大的元素个数,因为它不会自动扩容)以及是否为公平锁。
    private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);

    // 线程池
    private final ExecutorService mService = ConcurrentUtils.newFixedThreadPool(MAX_THREADS,
            "package-parsing-thread", Process.THREAD_PRIORITY_FOREGROUND);

    ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps,
            DisplayMetrics metrics, File cacheDir, PackageParser.Callback callback) {
        mSeparateProcesses = separateProcesses;
        mOnlyCore = onlyCoreApps;
        mMetrics = metrics;
        mCacheDir = cacheDir;
        mPackageParserCallback = callback;
    }

    static class ParseResult {

        PackageParser.Package pkg; // Parsed package
        File scanFile; // File that was parsed
        Throwable throwable; // Set if an error occurs during parsing

        @Override
        public String toString() {
            return "ParseResult{" +
                    "pkg=" + pkg +
                    ", scanFile=" + scanFile +
                    ", throwable=" + throwable +
                    '}';
        }
    }

    /**
     * Take the parsed package from the parsing queue, waiting if necessary until the element
     * appears in the queue.
     * @return parsed package
     */
    public ParseResult take() {
        try {
            if (mInterruptedInThread != null) {
                throw new InterruptedException("Interrupted in " + mInterruptedInThread);
            }
            return mQueue.take();
        } catch (InterruptedException e) {
            // We cannot recover from interrupt here
            Thread.currentThread().interrupt();
            throw new IllegalStateException(e);
        }
    }

    /**
     * Submits the file for parsing
     * @param scanFile file to scan
     * @param parseFlags parse falgs
     */
    public void submit(File scanFile, int parseFlags) {
        mService.submit(() -> {
            ParseResult pr = new ParseResult();
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
            try {
                PackageParser pp = new PackageParser();
                pp.setSeparateProcesses(mSeparateProcesses);
                pp.setOnlyCoreApps(mOnlyCore);
                pp.setDisplayMetrics(mMetrics);
                pp.setCacheDir(mCacheDir);
                pp.setCallback(mPackageParserCallback);
                pr.scanFile = scanFile;
                pr.pkg = parsePackage(pp, 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 PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
            int parseFlags) throws PackageParser.PackageParserException {
        return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
    }

    ...

}
```

`ParallelPackageParser` 是一个并行的 `PackageParser.Package` 解析器,内部使用线程池来完成并行的工作。具体解析操作后面再另行分析,这里先按照解析成功继续分析:

```java {.line-numbers}
    /**
     *  Scans a package and returns the newly parsed package.
     *  @throws PackageManagerException on a parse error.
     */
    private PackageParser.Package scanPackageChildLI(PackageParser.Package pkg,
            final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
            @Nullable UserHandle user)
                    throws PackageManagerException {
        // If the package has children and this is the first dive in the function
        // we scan the package with the SCAN_CHECK_ONLY flag set to see whether all
        // packages (parent and children) would be successfully scanned before the
        // actual scan since scanning mutates internal state and we want to atomically
        // install the package and its children.
        if ((scanFlags & SCAN_CHECK_ONLY) == 0) {
            if (pkg.childPackages != null && pkg.childPackages.size() > 0) {
                scanFlags |= SCAN_CHECK_ONLY;
            }
        } else {
            scanFlags &= ~SCAN_CHECK_ONLY;
        }

        // Scan the parent
        PackageParser.Package scannedPkg = addForInitLI(pkg, parseFlags,
                scanFlags, currentTime, user);

        // Scan the children
        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
        for (int i = 0; i < childCount; i++) {
            PackageParser.Package childPackage = pkg.childPackages.get(i);
            addForInitLI(childPackage, parseFlags, scanFlags,
                    currentTime, user);
        }


        if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
            return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user);
        }

        return scannedPkg;
    }
```

### 4.2.2 addForInitLI

```java {.line-numbers}
    /**
     * Adds a new package to the internal data structures during platform initialization.
     * <p>After adding, the package is known to the system and available for querying.
     * <p>For packages located on the device ROM [eg. packages located in /system, /vendor,
     * etc...], additional checks are performed. Basic verification [such as ensuring
     * matching signatures, checking version codes, etc...] occurs if the package is
     * identical to a previously known package. If the package fails a signature check,
     * the version installed on /data will be removed. If the version of the new package
     * is less than or equal than the version on /data, it will be ignored.
     * <p>Regardless of the package location, the results are applied to the internal
     * structures and the package is made available to the rest of the system.
     * <p>NOTE: The return value should be removed. It's the passed in package object.
     */
    private PackageParser.Package addForInitLI(PackageParser.Package pkg,
            @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
            @Nullable UserHandle user)
                    throws PackageManagerException {
        final boolean scanSystemPartition = ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0)
                // M:operator app also is removable and not system flag
                || sPmsExt.isRemovableSysApp(pkg.packageName);
        final String renamedPkgName;
        final PackageSetting disabledPkgSetting;
        final boolean isSystemPkgUpdated;
        final boolean pkgAlreadyExists;
        PackageSetting pkgSetting;

        ...

        // 判断系统应用是否需要更新
        synchronized (mPackages) {
            renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage);
            final String realPkgName = getRealPackageName(pkg, renamedPkgName);
            if (realPkgName != null) {
                ensurePackageRenamed(pkg, renamedPkgName);
            }
            final PackageSetting originalPkgSetting = getOriginalPackageLocked(pkg, renamedPkgName);
            final PackageSetting installedPkgSetting = mSettings.getPackageLPr(pkg.packageName);
            pkgSetting = originalPkgSetting == null ? installedPkgSetting : originalPkgSetting;
            pkgAlreadyExists = pkgSetting != null;
            final String disabledPkgName = pkgAlreadyExists ? pkgSetting.name : pkg.packageName;
            disabledPkgSetting = mSettings.getDisabledSystemPkgLPr(disabledPkgName);
            isSystemPkgUpdated = disabledPkgSetting != null;

            ...

            final SharedUserSetting sharedUserSetting = (pkg.mSharedUserId != null)
                    ? mSettings.getSharedUserLPw(pkg.mSharedUserId,
                            0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true)
                    : null;
            ...

            if (scanSystemPartition) {
                // Potentially prune child packages. If the application on the /system
                // partition has been updated via OTA, but, is still disabled by a
                // version on /data, cycle through all of its children packages and
                // remove children that are no longer defined.
                // 更新子应用
                if (isSystemPkgUpdated) {
                    ...
                }
                ...
            }
        }

        final boolean newPkgChangedPaths =
                pkgAlreadyExists && !pkgSetting.codePathString.equals(pkg.codePath);
        final boolean newPkgVersionGreater =
                //M: change ">" to ">=" can restore the same version apk, test step:
                //1) xun

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值