尊重原创,转载请注明出处!
创作不易,如有帮助请点赞支持~
引言
前段时间忙到飞起,最近总算闲下来了,可以花点时间研究自己看过很多次但又由于各种原因浅尝辄止的 PKMS 源码了。
在研究 PKMS 源码的时候,有一个概念让我很在意,就是 “childPackages”,在安装应用的流程中到处可见 childPackages 的身影,但是自己在开发过程中从来没有接触过它,网上资料也少得可怜,根本不知道是用来干嘛的。
作为 PackageManagerService 系列的第一篇,今天我们先来看一看 childPackages 到底是啥?
源码分析
Android 在扫描或安装应用时,都需要解析 apk 中的 AndroidManifest.xml 来获取应用的包名、权限等信息,以及 Activity、Service 等组件,这部分逻辑是在 PackageParser 中进行的。
/frameworks/base/core/java/android/content/pm/PackageParser.java
private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
IOException {
......
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
......
if (tagName.equals(TAG_APPLICATION)) {
......
}
......
} else if (tagName.equals(TAG_PACKAGE)) {
if (!MULTI_PACKAGE_APK_ENABLED) {
XmlUtils.skipCurrentTag(parser);
continue;
}
if (!parseBaseApkChild(pkg, res, parser, flags, outError)) {
// If parsing a child failed the error is already set
return null;
}
}
......
}
......
}
从上面的代码可以看到,当 AndroidManifest.xml 中 <application> 标签的同级下有 <package> 标签时,就会走到 parseBaseApkChild 去解析 childPackage 的相关信息。
这里有一个重要的地方,解析 childPackage 前判断了一个变量——MULTI_PACKAGE_APK_ENABLED,如果为 false,则跳过 <package> 标签,不解析 childPackage。
MULTI_PACKAGE_APK_ENABLED ,看命名像是一个 apk 多 package 使能的开关,我们来看一下它的定义:
private static final String PROPERTY_CHILD_PACKAGES_ENABLED =
"persist.sys.child_packages_enabled";
// Build.IS_DEBUGGABLE即为"ro.debuggable"的属性值
private static final boolean MULTI_PACKAGE_APK_ENABLED = Build.IS_DEBUGGABLE &&
SystemProperties.getBoolean(PROPERTY_CHILD_PACKAGES_ENABLED, false);
除了开发调试阶段,一般生产的机器,ro.debuggable 属性值都为 0。此外,persist.sys.child_packages_enabled 这个属性值并没有地方进行设置,默认情况下为空。
也就是说,MULTI_PACKAGE_APK_ENABLED 在默认情况下为 false,即不解析 childPackage!
那么,我们可以先做个大胆的假设,childPackage 这个概念并没有什么卵用,啥也不是~
回过头来,继续看 parseBaseApkChild 的实现:
private boolean parseBaseApkChild(Package parentPkg, Resources res, XmlResourceParser parser,
int flags, String[] outError) throws XmlPullParserException, IOException {
// Let ppl not abuse this mechanism by limiting the packages per APK
if (parentPkg.childPackages != null && parentPkg.childPackages.size() + 2
> MAX_PACKAGES_PER_APK) {
// MAX_PACKAGES_PER_APK = 5
outError[0] = "Maximum number of packages per APK is: " + MAX_PACKAGES_PER_APK;
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
// Make sure we have a valid child package name
String childPackageName = parser.getAttributeValue(null, "package");
// 解析childPackage的包名,子package包名不能重复,也不能和父package包名一样,不重要,暂时跳过这部分逻辑
......
// Go ahead and parse the child
Package childPkg = new Package(childPackageName);
// 子package部分信息直接从父package继承
childPkg.mVersionCode = parentPkg.mVersionCode;
childPkg.baseRevisionCode = parentPkg.baseRevisionCode;
childPkg.mVersionName = parentPkg.mVersionName;
childPkg.applicationInfo.targetSdkVersion = parentPkg.applicationInfo.targetSdkVersion;
childPkg.applicationInfo.minSdkVersion = parentPkg.applicationInfo.minSdkVersion;
// 这里需要注意,子package中的标签需要在CHILD_PACKAGE_TAGS中定义,否则会跳过无法解析
childPkg = parseBaseApkCommon(childPkg, CHILD_PACKAGE_TAGS, res, parser, flags, outError);
if (childPkg == null) {
// If we got null then error was set during child parsing
return false;
}
// Set the parent-child relation
if (parentPkg.childPackages == null) {
parentPkg.childPackages = new ArrayList<>();
}
parentPkg.childPackages.add(childPkg);
childPkg.parentPackage = parentPkg;
return true;
}
震惊!一个应用竟然可以有多达 5 个的 package??? 没搞懂这有啥用。。。
解析子 package 也是调用上面的 parseBaseApkCommon 方法,唯一需要注意的地方:子 package 支持的标签列表在 CHILD_PACKAGE_TAGS 中定义,如下:
// These are the tags supported by child packages
private static final Set<String> CHILD_PACKAGE_TAGS = new ArraySet<>();
static {
CHILD_PACKAGE_TAGS.add(TAG_APPLICATION);
CHILD_PACKAGE_TAGS.add(TAG_USES_PERMISSION);
CHILD_PACKAGE_TAGS.add(TAG_USES_PERMISSION_SDK_M);
CHILD_PACKAGE_TAGS.add(TAG_USES_PERMISSION_SDK_23);
CHILD_PACKAGE_TAGS.add(TAG_USES_CONFIGURATION);
CHILD_PACKAGE_TAGS.add(TAG_USES_FEATURE);
CHILD_PACKAGE_TAGS.add(TAG_FEATURE_GROUP);
CHILD_PACKAGE_TAGS.add(TAG_USES_SDK);
CHILD_PACKAGE_TAGS.add(TAG_SUPPORT_SCREENS);
CHILD_PACKAGE_TAGS.add(TAG_INSTRUMENTATION);
CHILD_PACKAGE_TAGS.add(TAG_USES_GL_TEXTURE);
CHILD_PACKAGE_TAGS.add(TAG_COMPATIBLE_SCREENS);
CHILD_PACKAGE_TAGS.add(TAG_SUPPORTS_INPUT);
CHILD_PACKAGE_TAGS.add(TAG_EAT_COMMENT);
}
从支持的标签列表看起来,子 package 的声明方式跟普通应用的 AndroidManifest.xml 写法差不多,就是少了一些标签的支持而已。
小结一下,childPackage 的使用方式就是在应用 AndroidManifest.xml 中声明 <package> 的标签,并声明它的包名、Activity 等信息。安装应用时,系统会解析这些信息,添加到 childPackages 变量中保存起来。
DEMO实现
写了一个最简单的应用,AndroidManifest.xml 如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.childpkgdemo"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="19" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.childpkgdemo.SimpleActivity"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<package package="com.example.childpkgdemo2">
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name="com.example.childpkgdemo2.SimpleActivity2"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</package>
</manifest>
轻轻松松搞定,然后安装,安装失败。。。
$ adb install -r ChildPkgDemo.apk
Performing Streamed Install
adb: failed to install ChildPkgDemo.apk: Failure [INSTALL_FAILED_INTERNAL_ERROR: Package couldn't be installed in /data/app/com.example.childpkgdemo-2HYQ61kb01vMVIfEGsZcNA==: Only privileged apps can add child packages. Ignoring package com.example.childpkgdemo]
纳尼??使用 childPackage,还需要是 privileged apps??!!
根据 log 提示,到 PackageManagerService.java 中找到报错的地方:
private void assertPackageIsValid(PackageParser.Package pkg, int policyFlags, int scanFlags)
throws PackageManagerException {
......
// Only privileged apps and updated privileged apps can add child packages.
if (pkg.childPackages != null && !pkg.childPackages.isEmpty()) {
if ((policyFlags & PARSE_IS_PRIVILEGED) == 0) {
throw new PackageManagerException("Only privileged apps can add child "
+ "packages. Ignoring package " + pkg.packageName);
}
......
}
......
}
privileged apps,即 /system/priv-app/ 下的应用。所以,只有 priv-app 才配拥有子 package。。。
好吧,那就随便找个 priv-app,把上面子 package 的内容拷贝过去,运行看看有什么效果。
拷贝,编译,将应用 push 到 /system/priv-app/ 下,重启机器,一气呵成。
进入到 /data/system/packages.xml,过滤一下包名,果然多了子 package 的信息:
<package name="com.xxx.xxx" codePath="/system/priv-app/XXX" nativeLibraryPath="/system/priv-app/XXX/lib" primaryCpuAbi="armeabi-v7a" publicFlags="944291405" privateFlags="8" ft="17dcc1cd770" it="11e8dc5d800" ut="17dcc1cd770" version="100" versionName="5.2.4" applicationName="XXX" sharedUserId="1000" isOrphaned="true">
<package name="com.example.childpkgdemo2" codePath="/system/priv-app/XXX" nativeLibraryPath="/system/priv-app/XXX/lib" publicFlags="940097093" privateFlags="8" ft="17dcc1cd770" it="17dcc1cd770" ut="17dcc1cd770" version="100" versionName="5.2.4" applicationName="XXX" userId="10010" isOrphaned="true" parentPackageName="com.xxx.xxx">
可以发现子 package 虽然是和父 package 在同一个应用中编译出来的,但是子 package 和父 package的包名是彼此独立的,而且子 package 并没有和父 package 共享 userId。
通过 ps 指令查看进程,发现子 package 独享新的进程号,是创建了新的进程运行的。
system 1175 449 1131692 69232 SyS_epoll_wait b53af5a8 S com.xxx.xxx
u0_a10 1756 449 1146992 92940 SyS_epoll_wait b53af5a8 S com.example.childpkgdemo2
小结一下,子 package 和父 package 虽然是在同一个应用中声明的,但是他们有各自的包名、pid 和 uid,并且能声明各自的 activity、permission 等。
总结
到了最后,还是没想明白这个东西有什么用?
如果是为了创建新的进程,可以直接在组件中声明 android:process。
如果是为了不同的 uid,不同的权限等,可以直接在一个新的应用中实现啊。
更别提还需要系统是 eng 或 userdebug 版本(ro.debuggable = 1),以及需要设置属性才能将 childPackage 的功能打开了。
声明子 package 的应用还必须得是 priv-app,否则不让安装!
所以,总结起来就是,childPackage 在开发或实际使用过程中几乎不会用到,分析 PKMS 的过程中可以忽略这部分逻辑。
PS:这是我自己的理解,如果说得不对请大家指正,我会及时修改文章,免得误导别人,谢谢~