前面一篇文章对PKMS扫描前的流程做了一个分析,可谓小试牛刀。本文开始分析APK扫描工作,让我们翻起点浪花。
承接前方的PKMS构造函数代码如下
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// ...
// Create sub-components that provide services / data. Order here is important.
synchronized (mInstallLock) {
synchronized (mPackages) {
// ...
// 解析系统配置文件
mPermissionManager = PermissionManagerService.create(context,
mPackages /*externalLock*/);
mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy();
// Holds information about dynamic settings.
mSettings = new Settings(Environment.getDataDirectory(),
mPermissionManager.getPermissionSettings(), mPackages);
}
}
// 保存共享用户信息
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
// ...
// 这个属性默认为 false
String separateProcesses = SystemProperties.get("debug.separate_processes");
if (separateProcesses != null && separateProcesses.length() > 0) {
// ...
} else {
// 注意这两个变量,在解析APK时,有用到。
mDefParseFlags = 0;
mSeparateProcesses = null;
}
// ...
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
// ...
// ... 省略转移library信息的代码
// /system/framework
File frameworkDir = new File(Environment.getRootDirectory(), "framework");
// ... 省略关于系统升级的代码
// 创建APK解析的缓存目录
mCacheDir = preparePackageParserCache();
// Set flag to monitor and not change apk file paths when
// scanning install directories.
int scanFlags = SCAN_BOOTING | SCAN_INITIAL;
if (mIsUpgrade || mFirstBoot) {
// 我们分析的是首次开机的过程,因此这里会设置如下flag
scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
}
// 1. 首先扫描overlay的APK
scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_VENDOR,
0);
// 省略扫描其它分区的overlay apk代码
// 保存静态overlay apk的信息
mParallelPackageParserCallback.findStaticOverlayPackages();
// 2. 扫描/system/frameworks目录,这个目录下只有frameworks-res.apk
scanDirTracedLI(frameworkDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_NO_DEX
| SCAN_AS_SYSTEM
| SCAN_AS_PRIVILEGED,
0);
// 3. 省略扫描其它分区下的APK的代码
从整体看,它首先是扫描overlay apk,因为overlay apk会对某个目标apk进行资源覆盖,因此必须要先扫描。
然后再扫描 /system/framework 目录下的apk,而这个目录下只有一个 frameworks-res.apk。
最后扫描其它分区下的apk,例如 /system/priv-app、/system/app,/vendor/app 目录,等等。
对所有APK的扫描都是使用 scanDirTracedLI()
,而它会直接调用scanDirLI()
,代码如下
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = scanDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
return;
}
try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
mParallelPackageParserCallback)) {
// 1. 提交请求到线程池
int fileCount = 0;
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
continue;
}
parallelPackageParser.submit(file, parseFlags);
fileCount++;
}
// 2. 处理结果
for (; fileCount > 0; fileCount--) {
ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
Throwable throwable = parseResult.throwable;
int errorCode = PackageManager.INSTALL_SUCCEEDED;
if (throwable == null) {
if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
renameStaticSharedLibraryPackage(parseResult.pkg);
}
try {
scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
currentTime, null);
}
}
// ...
}
}
}
多么清晰的两步,首先向线程池提交解析APK的任务,然后在主线程中,阻塞式的获取结果并处理它。
这段代码演示了如何利用多线程处理任务,然后利用主线程获取结果。分析源码其实也是一个学习技术的手段!!!
看似小小的两步,其实代码量非常大,本文只分析第一步。而第二步,留到后面一篇文章再来分析。
源码分析不能一蹴而就,需要我们个个击破,然后再整合信息。
现在让我们来看看ParallelPackageParser.submit()
如何提交解析APK任务
public void submit(File scanFile, int parseFlags) {
mService.submit(() -> {
// 用于保存解析的结果
ParseResult pr = new ParseResult();
try {
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);
pp.setCacheDir(mCacheDir);
pp.setCallback(mPackageParserCallback);
pr.scanFile = scanFile;
// 用PackageParser解析APK
pr.pkg = parsePackage(pp, scanFile, parseFlags);
}
// ...
try {
// 解析的结果保存到BlockingQueue中
mQueue.put(pr);
} catch (InterruptedException e) {
// ...
}
});
}
提交到线程池中的任务,首先通过PackageParser解析APK,然后把结果保存到一个BlockingQueue中。
现在让我们开始激动人心的APK解析之旅,PackageParser.parsePackage()
代码如下
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
// 这这里可以看出,APK解析结果可以从缓存中获取
Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
if (parsed != null) {
return parsed;
}
if (packageFile.isDirectory()) {
parsed = parseClusterPackage(packageFile, flags);
} else {
// 解析APK
parsed = parseMonolithicPackage(packageFile, flags);
}
// 缓存APK解析结果
cacheResult(packageFile, flags, parsed);
return parsed;
}
这个方法主要是负责缓存功能,其它的解析工作交给了 parseMonolithicPackage() 函数,代码如下
@Deprecated
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
// 简单解析AndroidManifest.xml一些标签属性
final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (mOnlyCoreApps) {
// ...
}
final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
try {
// 注意第二个参数是AssetsManager,它表示APK的assets目录的资源管理器
final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
pkg.setCodePath(apkFile.getCanonicalPath());
// 可以
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} // ...
}
这个函数被标记为@Deprecated,可能是由于历史原因造成的。这个函数主要是为了加载本APK和Split APK的asset资源,但是呢parseMonolithicPackageLite()函数并没有解析Split apk的路径,因此使用 DefaultSplitAssetLoader.getBaseAssetManager() 时,并没有加载Split apk的asset资源,这可能就是这个函数标记为废弃的原因吧。
除了加载asset资源,APK的剩余解析工作交给了parseBaseApk()函数,代码如下
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
// 如果APK的路径是以/mnt/expand/开头,那么解析磁盘的uuid
String volumeUuid = null;
if (apkPath.startsWith(MNT_EXPAND)) {
final int end = apkPath.indexOf('/', MNT_EXPAND.length());
volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
}
mParseError = PackageManager.INSTALL_SUCCEEDED;
mArchiveSourcePath = apkFile.getAbsolutePath();
XmlResourceParser parser = null;
try {
final int cookie = assets.findCookieForPath(apkPath);
if (cookie == 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Failed adding asset path: " + apkPath);
}
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
// Resources代表APK的资源
final Resources res = new Resources(assets, mMetrics, null);
final String[] outError = new String[1];
// 继续解析APK
final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
if (pkg == null) {
throw new PackageParserException(mParseError,
apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
}
// 设置uuid
pkg.setVolumeUuid(volumeUuid);
pkg.setApplicationVolumeUuid(volumeUuid);
pkg.setBaseCodePath(apkPath);
pkg.setSigningDetails(SigningDetails.UNKNOWN);
return pkg;
}
// ...
}
从整体看,这段代码是处理APK在/mnt/expand/目录下的情况,然后剩余的工作交给了parseBaseApk(),代码如下
从解析APK的函数调用链可以观察到,每个函数似乎只做一件事。这样每个函数职责清晰,也可以避免某一个参数代码过长。
private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {
final String splitName;
final String pkgName;
try {
// 1.解析了<manifest>标签的split属性
Pair<String, String> packageSplit = parsePackageSplitNames(parser, parser);
pkgName = packageSplit.first;
splitName = packageSplit.second;
// 从这里的判断可知,split的属性必须为空,真是奇怪,难道是不支持?
if (!TextUtils.isEmpty(splitName)) {
outError[0] = "Expected base APK, but found split " + splitName;
mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
return null;
}
} // ...
// 2.加载overlay apk的aasets目录资源
if (mCallback != null) {
String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath);
if (overlayPaths != null && overlayPaths.length > 0) {
for (String overlayPath : overlayPaths) {
res.getAssets().addOverlayPath(overlayPath);
}
}
}
// PackageParser.Package是用于保存APK解析的结果
final Package pkg = new Package(pkgName);
// 3.解析<manifest>标签的属性
TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifest);
// ...
pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
// ...
sa.recycle();
// 4. 继续解析其它的内容
return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
}
从整体看,这一步主要是完成<manifest>一部分属性解析工作,当然还有加载overlay apk资源的工作,最后把剩余的解析工作交给了parseBaseApkCommon()函数,代码如下
private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
IOException {
// ...
// 1. 解析<manifest>的sharedUserId标签
String str = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifest_sharedUserId, 0);
if (str != null && str.length() > 0) {
String nameError = validateName(str, true, true);
if (nameError != null && !"android".equals(pkg.packageName)) {
outError[0] = "<manifest> specifies bad sharedUserId name \""
+ str + "\": " + nameError;
mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
return null;
}
pkg.mSharedUserId = str.intern();
pkg.mSharedUserLabel = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifest_sharedUserLabel, 0);
}
// ...
// 2. 循环解析<manifest>下的所有子标签
int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
// ...
if (tagName.equals(TAG_APPLICATION)) {
// 从这里可以看出,只能有一个<application>标签
if (foundApp) {
// ...
}
foundApp = true;
if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
return null;
}
} else if (tagName.equals(TAG_OVERLAY)) {
sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestResourceOverlay);
pkg.mOverlayTarget = sa.getString(
com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetPackage);
pkg.mOverlayTargetName = sa.getString(
com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetName);
pkg.mOverlayCategory = sa.getString(
com.android.internal.R.styleable.AndroidManifestResourceOverlay_category);
pkg.mOverlayPriority = sa.getInt(
com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority,
0);
pkg.mOverlayIsStatic = sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic,
false);
final String propName = sa.getString(
com.android.internal.R.styleable
.AndroidManifestResourceOverlay_requiredSystemPropertyName);
final String propValue = sa.getString(
com.android.internal.R.styleable
.AndroidManifestResourceOverlay_requiredSystemPropertyValue);
sa.recycle();
// 必须指定 targetPackage属性
if (pkg.mOverlayTarget == null) {
outError[0] = "<overlay> does not specify a target package";
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return null;
}
// priority属性值范围为[0, 9999]
if (pkg.mOverlayPriority < 0 || pkg.mOverlayPriority > 9999) {
outError[0] = "<overlay> priority must be between 0 and 9999";
mParseError =
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return null;
}
// 检测requiredSystemPropertyName指定的属性名对应的值与requiredSystemPropertyValue指定的值是否相等
if (!checkOverlayRequiredSystemProperty(propName, propValue)) {
Slog.i(TAG, "Skipping target and overlay pair " + pkg.mOverlayTarget + " and "
+ pkg.baseCodePath+ ": overlay ignored due to required system property: "
+ propName + " with value: " + propValue);
return null;
}
// PRIVATE_FLAG_IS_RESOURCE_OVERLAY 标签为overlay 资源
pkg.applicationInfo.privateFlags |=
ApplicationInfo.PRIVATE_FLAG_IS_RESOURCE_OVERLAY;
XmlUtils.skipCurrentTag(parser);
} else if (tagName.equals(TAG_KEY_SETS)) {
// ...
} else if (tagName.equals(TAG_PERMISSION_GROUP)) {
// ...
} else if (tagName.equals(TAG_PERMISSION)) {
// ...
} else if (tagName.equals(TAG_PERMISSION_TREE)) {
// ...
} else if (tagName.equals(TAG_USES_PERMISSION)) {
// ...
} else if (tagName.equals(TAG_USES_PERMISSION_SDK_M)
|| tagName.equals(TAG_USES_PERMISSION_SDK_23)) {
// ...
} else if (tagName.equals(TAG_USES_CONFIGURATION)) {
// ...
} else if (tagName.equals(TAG_USES_FEATURE)) {
// ...
} else if (tagName.equals(TAG_FEATURE_GROUP)) {
// ...
} else if (tagName.equals(TAG_USES_SDK)) {
// ...
} else if (tagName.equals(TAG_SUPPORT_SCREENS)) {
// ...
} else if (tagName.equals(TAG_PROTECTED_BROADCAST)) {
// 受保存广播只能由系统应用定义,并且由系统发送
} else if (tagName.equals(TAG_INSTRUMENTATION)) {
// ...
} else if (tagName.equals(TAG_ORIGINAL_PACKAGE)) {
// <original-package>是用于系统升级,替换某个系统app,并保留它的数据
sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestOriginalPackage);
String orig =sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestOriginalPackage_name, 0);
if (!pkg.packageName.equals(orig)) {
if (pkg.mOriginalPackages == null) {
// 这这里可知,可以有多个<original-package>标签,也就是说系统升级可以替换多个应用?
pkg.mOriginalPackages = new ArrayList<String>();
pkg.mRealPackage = pkg.packageName;
}
pkg.mOriginalPackages.add(orig);
}
sa.recycle();
XmlUtils.skipCurrentTag(parser);
} /// ...
} else if (tagName.equals(TAG_PACKAGE)) {
// ...
} else if (tagName.equals(TAG_RESTRICT_UPDATE)) {
//...
} //...
}
// ...
return pkg;
}
这里首先解析了<manifest>标签的sharedUserId属性,然后再循环解析了<manifest>下的所有子标签,例如<application>。
这里我们来谈下<overlay>标签,这个是关于overlay apk的,我们先看下系统中一个overlay apk的AndroidManifest.xml如休写的
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.overlay.target">
<overlay android:targetPackage="android" android:isStatic="true" android:priority="500"/>
</manifest>
<overlay>标签中,targetPackage表示要覆盖资源的目标app,这里的值是android,也就是说覆盖frameworks-res.apk的资源。isStatic表示是静态overlay app,这个是必不可少的。最后priority表示如果多个overlay app,取优先级最高的覆盖资源。
既然有静态overlay app,是否也有动态 overlay app?好像是有,但是我并没有深入了解这个东西。
可能我们最关心的就是如何解析<application>标签,因为这里面有我们学用的四大组件,这是由parseBaseApplication()函数解析的
private boolean parseBaseApplication(Package owner, Resources res,
XmlResourceParser parser, int flags, String[] outError)
throws XmlPullParserException, IOException {
// <application>标签的信息保存到ApplicationInfo中
final ApplicationInfo ai = owner.applicationInfo;
// ...
// ai.name 是由 <application>的name属性值,也就是自定义的Application类
if (ai.name != null) {
ai.className = ai.name;
}
// ...
// 关于备份功能
boolean allowBackup = sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_allowBackup, true);
if (allowBackup) {
// ...
}
// ...
// persistent属性
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_persistent,
false)) {
// Check if persistence is based on a feature being present
final String requiredFeature = sa.getNonResourceString(com.android.internal.R.styleable
.AndroidManifestApplication_persistentWhenFeatureAvailable);
if (requiredFeature == null || mCallback.hasFeature(requiredFeature)) {
// FLAG_PERSISTENT 表示app是persistent app
ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
}
}
// ...
// hardwareAccelerated属性
owner.baseHardwareAccelerated = sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated,
owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH);
if (owner.baseHardwareAccelerated) {
// FLAG_HARDWARE_ACCELERATED 表示开启硬件加速
ai.flags |= ApplicationInfo.FLAG_HARDWARE_ACCELERATED;
}
// ...
// directBootAware属性
if (sa.getBoolean(
R.styleable.AndroidManifestApplication_directBootAware,
false)) {
// PRIVATE_FLAG_DIRECT_BOOT_AWARE 表示这个app会在ams中启动
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
}
// ...
// 这个表示使用使用老式的外部存储
if (sa.getBoolean(
R.styleable.AndroidManifestApplication_requestLegacyExternalStorage,
owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE;
}
// ...
// app 分类,例如message, calendar分类
ai.category = sa.getInt(
com.android.internal.R.styleable.AndroidManifestApplication_appCategory,
ApplicationInfo.CATEGORY_UNDEFINED);
// ...
if (outError[0] == null) {
CharSequence pname;
// <manifest>的process属性
if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) {
pname = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestApplication_process,
Configuration.NATIVE_CONFIG_VERSION);
} else {
// Some older apps have been seen to use a resource reference
// here that on older builds was ignored (with a warning). We
// need to continue to do this for them so they don't break.
pname = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestApplication_process);
}
// 如果process属性的值以分号开头,那么最终的值为packageName+process
// 否则最终的值为 process属性的值
ai.processName = buildProcessName(ai.packageName, null, pname,
flags, mSeparateProcesses, outError);
// ...
}
// ...
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();
if (tagName.equals("activity")) {
Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
owner.baseHardwareAccelerated);
// ...
owner.activities.add(a);
} else if (tagName.equals("receiver")) {
Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
true, false);
// ...
owner.receivers.add(a);
} else if (tagName.equals("service")) {
Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
// ...
owner.services.add(s);
} else if (tagName.equals("provider")) {
Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
// ...
owner.providers.add(p);
} else if (tagName.equals("activity-alias")) {
// ...
} else if (parser.getName().equals("meta-data")) {
// ...
} else if (tagName.equals("static-library")) {
// 编译成静态库
} else if (tagName.equals("library")) {
// 编译成动态库
} else if (tagName.equals("uses-static-library")) {
// 使用静态库
} else if (tagName.equals("uses-library")) {
// 使用动态库
} else if (tagName.equals("uses-package")) {
XmlUtils.skipCurrentTag(parser);
} else if (tagName.equals("profileable")) {
// ...
} // ...
}
// ...
return true;
}
函数的最开始获取了PackageParser.Package.applicationInfo变量,它的类型是ApplicationInfo,用于保存Application标签的信息。
这段代码中保存了一些<application>标签属性解析的代码,是一些常用的。在这段代码末尾阶段,它解析了<application>标签下的子标签,例如四大组件的标签。四大组件分别使用PackageParser.Package.activities, PackageParser.Package.receivers , PackageParser.Package.services ,PackageParser.Package.providers 保存。
到此,APK的扫描工作已经完成,可谓是波澜不惊,现在我们要记住一点,APK扫描结果现在保存在PackageParser.Package中。然而这只是一个初步扫描过程,PKMS还会对结果进行处理,更重要的是把这个结果转移到PKMS的数据结构中,这就是下一篇的内容。