在 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,然后把解析结果添加到一个类型为BlockingQueue的mQueue中。后面在处理解析结果的时候,那个解析结果就是从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

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

818

被折叠的 条评论
为什么被折叠?



