[ Android源码分析 ] PackageManagerService 系列—— childPackages 到底是啥

[ Android源码分析 ] PackageManagerService 系列—— childPackages 到底是啥

尊重原创,转载请注明出处!
创作不易,如有帮助请点赞支持~

引言

前段时间忙到飞起,最近总算闲下来了,可以花点时间研究自己看过很多次但又由于各种原因浅尝辄止的 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:这是我自己的理解,如果说得不对请大家指正,我会及时修改文章,免得误导别人,谢谢~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值