PackageManagerService(2)扫描目录并解析APK

深入探讨系统首次开机时,PackageManagerService如何扫描并解析APK,包括创建缓存目录、解析AndroidManifest.xml、处理OverlayAPK及扫描结果的保存过程。

PackageManagerService(1)扫描前的准备 文章中,分析了 PKMS ( PackageManagerService ) 在扫描之前的准备工作,这篇文章来分析扫描目录并解析APK的过程。

扫描目录并解析APK这个过程,伴随着大量升级相关的代码,但是我要提醒一句,我们现在分析的目标是系统首次开机,并不会把系统升级的代码混淆在一起分析。

扫描目录,解析APK

    public PackageManagerService(Context context, Installer installer,
                                 boolean factoryTest, boolean onlyCore) {
   
   
        synchronized (mInstallLock) {
   
   
            // writer
            synchronized (mPackages) {
   
   
                // 判断是否首次开机
                mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));

                // 承接上文继续分析...

                // 创建缓存目录,用于缓存扫描结果
                mCacheDir = preparePackageParserCache(); 

                int scanFlags = SCAN_BOOTING | SCAN_INITIAL;

                if (mIsUpgrade || mFirstBoot) {
   
   
                    // 注意,首次开机还要加上这个扫描flag
                    scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
                }                

                // 扫描目录并解析APK
                //先扫描Overlay APK
                scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),
                        mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR,
                        scanFlags | SCAN_AS_SYSTEM | SCAN_AS_VENDOR,
                        0);

                // 还会扫描其它分区的overlay目录,例如/product/overlay, /product_services/overlay 等等。
                // ...

                // 保存静态overlay包的信息,这些信息会在后面解析APK时用到
                mParallelPackageParserCallback.findStaticOverlayPackages();

                // /system/framework目录,这个目录下只有一个apk,那就是framework-res.apk
                scanDirTracedLI(frameworkDir,
                        mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR,
                        scanFlags | SCAN_NO_DEX | SCAN_AS_SYSTEM | SCAN_AS_PRIVILEGED,
                        0);

                // 扫描其它分区的目录, 方法与上面的一致....

            }
        }
    }

在扫描之前,会通过preparePackageParserCache()创建一个缓存目录,用来缓存扫描APK结果。如此一来,下次开机,如果缓存有效,就可以直接从缓存中获取apk信息,而不用再扫描APK了,这样加快了不是首次开机的速度。

开始扫描APK时,首先扫描的是Overlay APK,目录有/vendor/overlay, /product/overlay, /product_services/overlay, /odm/overlay, /oem/overlay。因此如果你想生成Overlay APK,那么必须编译到这些目录下。

什么是Overlay APK?Overlay APK是用来覆盖目标APK的资源的,然而仅限于res/values目录下的资源,例如替换应用的名字,主题等等。因此也称为运行时资源覆盖(Runtime Resource Overlay)。

然而,为了保证RRO生效,需要在使用静态的RRO,例如

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.qualcomm.qti.optinoverlay">
    <overlay
        android:isStatic="true"
        android:priority="1"
        android:targetPackage="com.qualcomm.location.XT" />
</manifest>

其中isStatic属性为true表示这个RRO是静态的,它会覆盖目标应用(targetPackage属性指定的应用)的资源。

mParallelPackageParserCallback.findStaticOverlayPackages()方法保存的就是静态RRO。这个将在后面解析APK时用到。

本文的目标是PackageManagerService扫描并解析APK,至于Overlay APK的代码,还请大家自行分析。

那么现在我们来看看scanDirTracedLI()是如何扫描目录并解析APK的

    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;
        }
        /**
         * mCacheDir用于缓存apk信息
         * mParallelPackageParserCallback用于在解析时提供overlay资源
         */
        try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
                mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
                mParallelPackageParserCallback)) {
   
   
            int fileCount = 0;
            for (File file : files) {
   
   
                // 必须是.apk或者目录
                final boolean isPackage = (isApkFile(file) || file.isDirectory())
                        && !PackageInstallerService.isStageName(file.getName());
                if (!isPackage) {
   
   
                    continue;
                }
                // 1. 提交APK文件,并行解析
                parallelPackageParser.submit(file, parseFlags);
                fileCount++;
            }

            // 2. 处理扫描结果
            for (; fileCount > 0; fileCount--) {
   
   
                // APK数据保存在ParseResult.pkg中,类型为PackageParser.Package
                ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
                Throwable throwable = parseResult.throwable;
                int errorCode = PackageManager.INSTALL_SUCCEEDED;

                if (throwable == null) {
   
   
                    // 使用<static-library>生成的静态库,需要重新设置包名,形式为package_libVersion
                    if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
   
   
                        renameStaticSharedLibraryPackage(parseResult.pkg);
                    }
                    try {
   
   
                        // 扫描,并返回一个新的结果
                        // 注意最后一个参数user,值为null
                        scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
                                currentTime, null);
                    } 
                    // ...
                } 
				// ...
            }
        }
    }

ParallelPackageParser 是一个并行的APK解析器,通过ParallelPackageParser#submit() 方法,向线程池提交待解析的APK,然后通过ParallelPackageParser#take() 来不断地获取结果,用于扫描处理。

提交并解析APK

现在来看下 ParallelPackageParser#submit() 如何提交并解析APK

    public void submit(File scanFile, int parseFlags) {
   
   
        mService.submit(() -> {
   
   
            // ParseResult只有三个变量
            // pkg变量保存扫描的结果
            // scanFile保存扫描的文件
            // throwable保存扫描出现的异常
            ParseResult pr = new ParseResult();
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
            try {
   
   
                PackageParser packageParser = new PackageParser();
                packageParser.setSeparateProcesses(mSeparateProcesses);
                packageParser.setOnlyCoreApps(mOnlyCore);
                packageParser.setDisplayMetrics(mMetrics);
                packageParser.setCacheDir(mCacheDir);
                packageParser.setCallback(mPackageParserCallback);
                pr.scanFile = scanFile;
                // 通过PackageParser解析APK,并返回解析结果
                // 解析结果的类型为 PackageParser.Package
                pr.pkg = parsePackage(packageParser, 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();
                mInterruptedInThread = Thread.currentThread().getName();
            }
        });
    }

这里向线程池中提交了一个请求,在这个请求中,会通过parsePackage()解析APK,然后把解析结果添加到一个类型为BlockingQueuemQueue中。后面在处理解析结果的时候,那个解析结果就是从mQueue中获取的。

现在来看下 parsePackage() 是如何解析APK的

    protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
            int parseFlags) throws PackageParser.PackageParserException {
   
   
        // 第三个参数表示是否使用缓存,这样会提升开机效率
        return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
    }

原来就是通过PackageParser#parsePackage()来解析的,注意第三个参数的值为 true ,它代表对解析结果使用缓存。

	// frameworks/base/core/java/android/content/pm/PackageParser.java
	
    public Package parsePackage(File packageFile, int flags, boolean useCaches)
            throws PackageParserException {
   
   
        // 1. 获取有效的缓存结果
        Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
        if (parsed != null) {
   
   
            return parsed;
        }

        long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
        if (packageFile.isDirectory()) {
   
   
            parsed = parseClusterPackage(packageFile, flags);
        } else {
   
   
            // 2. 解析APK
            parsed = parseMonolithicPackage(packageFile, flags);
        }

        long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
        // 3. 把解析的结果进行缓存
        // 原理是把解析的数据分解为字节数组,然后写入到缓存文件中
        cacheResult(packageFile, flags, parsed);
        return parsed;
    }

从这里可以看出,整个解析过程使用了缓存,但是本文不打算分析这个缓存的机制,有兴趣的同学可以研究下,并不难。

而解析APK使用的是parseMonolithicPackage()

    public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
   
   
        // 这里只是简单解析了AndroidManifest.xml的某些属性,这里主要是为了Split APK功能,本文不讨论
        final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);

        // ...

        final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
        try {
   
   
            final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
            // PackageParser.Package.codePath存储了apk的全路径
            pkg.setCodePath(apkFile.getCanonicalPath());
            pkg.setUse32bitAbi(lite.use32bitAbi);
            return pkg;
        }

        // ...
    }

这一步解析加入了Split APK解析功能,如果大家想了解Split APK功能是如何实现的,看完本文可自行分析。

那么接下来是使用parseBaseApk()来继续解析APK

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

        // ...

        mParseError = PackageManager.INSTALL_SUCCEEDED;
        mArchiveSourcePath = apkFile.getAbsolutePath();

        XmlResourceParser par
<think>嗯,用户想找PackageManagerService扫描系统文件的函数名,Android源码里的。首先,我需要回忆一下PackageManagerService的结构。根据引用[2],PackageManagerService的构造函数做了很多任务,包括扫描各种软件包,比如供应商overlay、基础框架、系统应用等。可能扫描相关的函数就在构造函数中被调用。 另外,引用[4]提到PMS是从SystemServer的startBootstrapServices()启动的,所以可能扫描发生在初始化阶段。根据之前的知识,Android系统启动时,PMS会扫描系统目录如/system/app、/vendor/app等来安装系统应用。 通常,扫描系统应用的主要函数可能是scanDirTracedLI()或者scanDirLI(),这些函数负责遍历指定目录处理APK文件。比如,在扫描系统应用时,可能会调用scanDirTracedLI()传入目录路径如/system/app。此外,可能有多个扫描阶段,比如扫描privileged apps,普通系统应用等,这些可能在scanDirTracedLI的不同调用中处理。 再想,引用[2]中提到扫描各种软件包,包括特权系统软件包、普通系统软件包,这些可能对应不同的目录。因此,具体的函数调用可能在PMS的构造过程中,通过调用scanDirTracedLI()扫描各个系统目录。比如,scanDirTracedLI(new File(systemDir, "app"), ...)这样的代码,其中systemDir可能是/system分区下的路径。 所以,总结一下,扫描系统文件的主要函数应该是scanDirTracedLI(),而具体的调用可能分布在PMS的构造函数中,针对不同的系统目录进行多次调用。同时,可能需要查看源码中的具体实现,比如在scanDirLI()或scanDirTracedLI()里处理扫描逻辑,解析APK生成Package信息。</think>在 Android 源码中,PackageManagerService 扫描系统文件的核心函数是: **scanDirTracedLI()** 该函数负责递归扫描指定目录(如 `/system/app`、`/vendor/app` 等系统级路径)下的 APK 文件,调用底层解析函数。其调用路径如下: ```java // 示例代码片段(基于 Android 8.1) private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) { for (File file : dir.listFiles()) { if (file.isDirectory()) { scanDirLI(file, parseFlags, scanFlags, currentTime); // 递归扫描 } else if (file.isFile() && isPackageFile(file)) { scanPackageTracedLI(file, parseFlags, scanFlags, currentTime, null); // 解析单个 APK } } } ``` ### 关键函数说明 1. **scanDirTracedLI()** - 入口函数,用于追踪扫描过程 - 参数包含目标目录解析标志位等 - 调用路径:`PackageManagerService 构造函数 -> scanDirTracedLI() -> scanDirLI()` 2. **scanPackageTracedLI()** - 解析单个 APK 文件的核心函数 - 内部会调用 `PackageParser.parsePackage()` 解析 AndroidManifest.xml ### 系统路径扫描过程 在 PMS 初始化时(引用[2]描述),会扫描以下目录: ```java /system/framework // 基础资源包 /system/app // 核心系统应用 /vendor/app // 厂商定制应用 /product/app // 产品级应用(Android 10+) ``` 具体代码实现可参考 `PackageManagerService` 构造函数中对 `scanDirTracedLI()` 的多次调用[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值