Android7.0 PackageManagerService (2) PKMS构造函数的主要工作

时间:2021-09-22 00:01:40

从本篇博客开始,我们开始分析PKMS的构造函数,看看PKMS到底是如何解析和管理手机中APK的信息的。
由于PKMS的构造函数较长,我们会分段进行研究。

public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
........
mContext = context;
mFactoryTest = factoryTest; //假定为false,运行在非工厂模式下
mOnlyCore = onlyCore; //假定为false,即扫描所有的APK
mMetrics = new DisplayMetrics(); //分辨率相关

mSettings = new Settings(mPackages);
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
...................
}

一、PKMS中的Settings
刚进入到PKMS的构造函数,我们就遇到了Settings对象,及一大堆的addSharedUserLPw调用。
我们看看Settings的构造函数:

Settings(Object lock) {
this(Environment.getDataDirectory(), lock);
}

Settings(File dataDir, Object lock) {
mLock = lock;

mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

//目录指向"data/system"
mSystemDir = new File(dataDir, "system");
//创建目录
mSystemDir.mkdirs();
FileUtils.setPermissions(mSystemDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
-1, -1);
//packages.xml和packages-backup.xml为一组,用于描述系统所安装的Package信息,其中packages-backup.xml是packages.xml的备份
//PKMS写把数据写到backup文件中,信息全部写成功后在改名为非backup文件,以防止在写文件的过程中出错,导致信息丢失
mSettingsFilename = new File(mSystemDir, "packages.xml");
mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");

//packages.list保存系统中存在的所有非系统自带的APK信息,即UID大于10000的apk
mPackageListFilename = new File(mSystemDir, "packages.list");
FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);

//感觉是sdcardfs相关的文件
final File kernelDir = new File("/config/sdcardfs");
mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;

// Deprecated: Needed for migration
//packages-stopped.xml用于描述系统中强行停止运行的package信息,backup也是备份文件
mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
}

从代码可以看出,Settings的构造函数主要用于创建一些目录和文件,并配置相应的权限。其中:
* PKMS扫描完目标文件夹后,会创建packages.xml。当系统进行程序安装、卸载和更新等操作时,均会更新该文件;
* packages-list用于描述系统中存在的所有非系统自带的APK信息。当这些APK有变化时,PKMS就会更新该文件;
* packages-stopped.xml记录被用户强行停止的应用的Package信息(例如,从设置进入某个应用,然后点击强行停止,那么应用的Package信息就会被记录)。

因此,我们可以推测出Settings主要用于保存一些信息,实际上它确实是用于管理Android系统运行过程中的一些设置信息。

我们继续跟进Settings的addSharedUserLPw函数:

//name和uid一一对应,例如:"android.uid.system":Process.SYSTEM_UID(1000)
// "android.uid.phone" :RADIO_UID(Process.PHONE_UID, 1001)
//pkgFlags均为:ApplicationInfo.FLAG_SYSTEM
//pkgPrivateFlags均为:ApplicationInfo.PRIVATE_FLAG_PRIVILEGED
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
SharedUserSetting s = mSharedUsers.get(name);
if (s != null) {
if (s.userId == uid) {
return s;
}
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate shared user, keeping first: " + name);
return null;
}
//目的就是利用参数构造出SharedUserSetting
s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
s.userId = uid;

if (addUserIdLPw(uid, s, name)) {
//按 <名称---SharedUserSettings> 存入map中
mSharedUsers.put(name, s);
return s;
}
return null;
}

private boolean addUserIdLPw(int uid, Object obj, Object name) {
//LAST_APPLICATION_UID = 19999
if (uid > Process.LAST_APPLICATION_UID) {
return false;
}

//普通APK的uid
if (uid >= Process.FIRST_APPLICATION_UID) {
int N = mUserIds.size();
final int index = uid - Process.FIRST_APPLICATION_UID;

while (index >= N) {
mUserIds.add(null);
N++;
}
if (mUserIds.get(index) != null) {
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate user id: " + uid
+ " name=" + name);
return false;
}
mUserIds.set(index, obj);
} else {
if (mOtherUserIds.get(uid) != null) {
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate shared id: " + uid
+ " name=" + name);
return false;
}
mOtherUserIds.put(uid, obj);
}
return true;
}

Android7.0 PackageManagerService (2) PKMS构造函数的主要工作

PKMS创建Settings后,调用一系列的addSharedUserLPw函数,将形成如上图所示的数据结构。

如图所示,PKMS将根据参数构建出SharedUserSettings对象,可以通过两个维度来引用创建出的对象,即名称和uid。
在Settings中mSharedUsers是一个map对象,利用名称作为索引管理SharedUserSettings对象。
Settings中的mOtherUserIds和mUserIds,均是利用userId作为索引管理SharedUserSettings对象。不同的是mOtherUserIds是SparseArray,以系统uid作为键值;mUserIds是ArrayList,普通APK的uid为ArrayList的下标。

说了这么多,SharedUserSettings到底是什么?PKMS为什么要花这么大的力气,创建和管理SharedUserSettings?接下来,我们就来逐步揭晓答案。

1.1 SharedUserSettings
我们看看SharedUserSettings类:

final class SharedUserSetting extends SettingBase {
final String name;

int userId;

// flags that are associated with this uid, regardless of any package flags
int uidFlags;
int uidPrivateFlags;

//关键点
final ArraySet<PackageSetting> packages = new ArraySet<PackageSetting>();

final PackageSignatures signatures = new PackageSignatures();

SharedUserSetting(String _name, int _pkgFlags, int _pkgPrivateFlags) {
super(_pkgFlags, _pkgPrivateFlags);
uidFlags = _pkgFlags;
uidPrivateFlags = _pkgPrivateFlags;
name = _name;
}
............
void removePackage(PackageSetting packageSetting) {
if (packages.remove(packageSetting)) {
.......
}
}

void addPackage(PackageSetting packageSetting) {
if (packages.add(packageSetting)) {
........
}
}
}

从上面的代码来看,SharedUserSettings将持有一组PackageSetting。
从SharedUserSettings的命名来看,这一组PackageSetting应该有相似的共性。

为了进一步分析,我们举个例子来看看。
在packages/apps/Settings的AndroidManifest.xml中,有以下内容:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
package="com.android.settings"
coreApp="true"
//注意此处,在前文的PKMS为其创建了SharedUserSettings
android:sharedUserId="android.uid.system">
...............

如上所示,在xml文件中,声明了一个名为android:sharedUserId的属性,其值为”android.uid.system”。
实际上多个声明了同一种sharedUserId的APK可共享彼此的数据,并且可运行在同一进程中。更重要的是,通过声明特点的sharedUserId,该APK所在的进程将被赋予指定UID对应的权限。

我们知道Android系统中的UID表示用户ID,GID表示用户组ID,均与Linux系统中进程的权限管理有关。一般来说,每一个进程都会有一个对应的UID,针对不同的UID可以有不同的权限;同时,每个进程也可以分属于不同的用户组,即有对应的GID,针对不同的GID也可以有不同的权限。

通过上面这个例子及UID/GID的用途,SharedUserSettings的作用就可以体现出来了:
SharedUserSettings将“android:sharedUserId”属性的名称和对应的uid关联起来,同时持有所有声明相同sharedUserId的APK的PackageSettings,因此PKMS可以为同一类APK设置相同的权限。

除了在AndroidManifest.xml中声明sharedUserId外,APK在编译时还必须使用对应的证书签名。例如Settings.apk,对应的Android.mk中就声明了LOCAL_CERTIFICATE := platform。这样Settings.apk就具有系统权限了。

1.2 SharedUserSettings相关的类图
Android7.0 PackageManagerService (2) PKMS构造函数的主要工作

在这一部分的最后,我们来简单回顾一下SharedUserSettings相关的类图。
如上图所示,Settings对象中持有多个SharedUserSettings对象,每个SharedUserSettings对象由会持有多个PackageSettings对象。
从继承关系来看,SharedUserSettings和PackageSettings对象,最终都将继承SettingsBase对象。

从图上可以看出,SettingsBase对象持有PermissionsState对象,用于表示可用的权限。
因此,SharedUserSettings对象和PackageSettings对象中都将包含有PermissionsState。
可以据此推测出,SharedUserSettings中持有的是一组Package共有的权限;PackageSettings中持有的是单个Package独有的权限。

PKMS中Settings除去SharedUserSettings之外,还管理了其它重要的数据结构,我们暂时略过,等流程涉及到时,再作分析。

二、读取XML文件中系统配置信息
我们回到PKMS的构造函数,看下一段代码:

//debug相关
.......
//构造函数传入的InstallerService,与底层Installd通信
mInstaller = installer;
mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
"*dexopt*");

//定义一些回调函数
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());

mOnPermissionChangeListeners = new OnPermissionChangeListeners(
FgThread.get().getLooper());

//存储显示信息
getDefaultDisplayMetrics(context, mMetrics);

//获取系统配置信息
SystemConfig systemConfig = SystemConfig.getInstance();
//将系统配置信息,存储到PKMS中
mGlobalGids = systemConfig.getGlobalGids();
mSystemPermissions = systemConfig.getSystemPermissions();
mAvailableFeatures = systemConfig.getAvailableFeatures();
..........

在这一段代码中,PKMS创建了许多对象,暂时可以先不管它们,重点看看SystemConfig相关的函数。

//单例模式
public static SystemConfig getInstance() {
synchronized (SystemConfig.class) {
if (sInstance == null) {
sInstance = new SystemConfig();
}
return sInstance;
}
}

SystemConfig() {
// Read configuration from system
//从“system”目录下读取
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);

// Read configuration from the old permissions dir
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);

// Allow ODM to customize system configs around libs, features and apps
//从"/odm"目录下读取
int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS;
readPermissions(Environment.buildPath(
Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
readPermissions(Environment.buildPath(
Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);

// Only allow OEM to customize features
//从“oem”目录下读取
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES);
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES);
}

从上面的代码可以看出,创建SystemConfig时,将从不同的“etc”目录下读取权限信息,包括root目录、odm和oem目录,不同目录对应的可读取权限的范围不同。

我们看看readPermissions函数:

void readPermissions(File libraryDir, int permissionFlag) {
//检测目录是否存在,是否可读
..........
// Iterate over the files in the directory and scan .xml files
File platformFile = null;
for (File f : libraryDir.listFiles()) {
// We'll read platform.xml last
if (f.getPath().endsWith("etc/permissions/platform.xml")) {
platformFile = f;
continue;
}

//仅读取可读的xml文件
..........
readPermissionsFromXml(f, permissionFlag);
}

// Read platform permissions last so it will take precedence
if (platformFile != null) {
readPermissionsFromXml(platformFile, permissionFlag);
}
}

现在我们知道了,readPermissions就是从指定目录下,读取xml中的配置的权限信息。实际的手机上,可能没有代码中指定的所有目录,例如没有“odm”等,但system/etc/permissions目录一般都是有的。

1、xml文件内容举例
我手边有一台root过的android 6.0的手机,以system目录为例,看看其system/etc/permissions下的xml文件:

android.hardware.bluetooth.xml
android.hardware.camera.xml
//中间略去了一些。在中间甚至有厂商自己添加的xml
//与Android的设计初衷不符,可能是考虑到PKMS限制了"oem"目录下可以定义的权限种类,才添加到这个位置的
.......
platform.xml

1.1 platform.xml
platform.xml优先级最高,我们先看看platform.xml中的内容:

<?xml version="1.0" encoding="utf-8"?>
<!-- This file is used to define the mappings between lower-level system
user and group IDs and the higher-level permission names managed
by the platform.

Be VERY careful when editing this file! Mistakes made here can open
big security holes.
-->


<permissions>
<!-- The following tags are associating low-level group IDs with
permission names. By specifying such a mapping, you are saying
that any application process granted the given permission will
also be running with the given group ID attached to its process,
so it can perform any filesystem (read, write, execute) operations
allowed for that group. -->

<!--建立权限名与gid的映射关系,一般与需要和读写底层设备的进程会声明这些权限
这些权限涉及和Linux内核交互,所以需要在底层权限(由不同的用户的用户组界定)和Android层权限(由不同的字符串界定)之间建立映射关系 -->

<permission name="android.permission.BLUETOOTH_ADMIN" >
<group gid="net_bt_admin" />
</permission>

<permission name="android.permission.BLUETOOTH" >
<group gid="net_bt" />
</permission>
........

<!-- The following tags are assigning high-level permissions to specific
user IDs. These are used to allow specific core system users to
perform the given operations with the higher-level framework. For
example, we give a wide variety of permissions to the shell user
since that is the user the adb shell runs under and developers and
others should have a fairly open environment in which to
interact with the system. -->

<!--赋予对应uid相应的权限。例如下面第一行,uid为media时,那么就赋予其修改Audio设置的权限
其实就是将uid加入到对应的用户组中-->

<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
............

<!-- This is a list of all the libraries available for application
code to link against. -->

<!--系统提供的java库,APK运行时可以链接这些库-->
<library name="android.test.runner"
file="/system/framework/android.test.runner.jar" />

..........

<!-- These are the standard packages that are white-listed to always have internet
access while in power save mode, even if they aren't in the foreground. -->

<!--指定进程在省电模式下(非Idle态)仍可访问网络)-->
<allow-in-power-save-except-idle package="com.android.providers.downloads" />

<!-- Whitelist of what components are permitted as backup data transports. The
'service' attribute here is a flattened ComponentName string. -->

<!--指定服务可以传输备份数据-->
<backup-transport-whitelisted-service
service="android/com.android.internal.backup.LocalTransportService" />

<backup-transport-whitelisted-service
service="com.google.android.gms/.backup.BackupTransportService" />

<backup-transport-whitelisted-service
service="com.google.android.gms/.backup.component.D2dTransportService" />

</permissions>

从上面的xml文件可以看出,platform.xml主要作用是:
* permission和group字段用于建立Linux层GID和Android层permission字段之间的映射关系;
* assign-permission用于向指定的uid赋予相应的权限;
* library字段用于可链接的指定系统库
* allow-in-power-save-except-idle用于指定进程在省电模式下(非Idle)仍可上网
* backup-transport-whitelisted-service用于指定服务具有传输备份数据的权利

1.2 一般的xml
了解了platform.xml后,再看看其它的xml文件,这里以android.hardware.bluetooth.xml为例:

<?xml version="1.0" encoding="utf-8"?>
<!-- Adds the feature indicating support for the Bluetooth API -->
<permissions>
<feature name="android.hardware.bluetooth" />
</permissions>

这种类型的xml文件包含了一些feature标签,用于描述一个手持终端应该支持的硬件特性,例如上面的feature表示一个终端应该支持蓝牙功能。

最后需要说明的是,不同设备支持的硬件特性不一样。
同一套代码可能需要适配不同的设备,此时通过定义mk文件,可以在编译阶段根据当前硬件平台的配置信息,复制相关的xml文件到system/etc/permission目录下。

2、 readPermissionsFromXml
了解了xml文件的定义后,我们来看看readPermissionsFromXml函数:

private void readPermissionsFromXml(File permFile, int permissionFlag) {
FileReader permReader = null;
try {
//利用file构造fileReader
permReader = new FileReader(permFile);
} catch (FileNotFoundException e) {
.......
}

//读取系统属性"ro.config.low_ram",如果该属性为true,不会加载指定notLowRam的feature属性
//自己曾经试过,将大量的文件利用adb push导入到/data目录下,直到手机内存仅剩10几M,不能再导入任何文件
//此时,手机提示内存耗尽,部分系统功能可能无法正常使用
//个人感觉和这里的属性比较类似,一旦手机low_ram,此时终端重启后,将不再支持一些必须工作在内存足够条件下的特性
//不知道这个理解是否正确??
final boolean lowRam = ActivityManager.isLowRamDeviceStatic();

try {
XmlPullParser parser = Xml.newPullParser();
//Xml解析器的输入为fileReader读取的内容
parser.setInput(permReader);

//找到解析的起点
.........

//根据传入的flag,决定当前目录下,从xml文件中解析内容的范围
//对于system目录,allowAll
boolean allowAll = permissionFlag == ALLOW_ALL;
boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0;
boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0;
boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0;
boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
while (true) {
XmlUtils.nextElement(parser);
if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
break;
}

String name = parser.getName();
//解析group标签,前面介绍的xml文件中没有单独使用该标签的地方
if ("group".equals(name) && allowAll) {
String gidStr = parser.getAttributeValue(null, "gid");
if (gidStr != null) {
//将Gid字符串转化成整形,保存到mGlobalGids中
int gid = android.os.Process.getGidForName(gidStr);
mGlobalGids = appendInt(mGlobalGids, gid);
} else {
.........
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("permission".equals(name) && allowPermissions) {
String perm = parser.getAttributeValue(null, "name");
.......
perm = perm.intern();
//调用readPermission解析permission标签
readPermission(parser, perm);
} else if ("assign-permission".equals(name) && allowPermissions) {
//得到权限名
String perm = parser.getAttributeValue(null, "name");
........
//得到uid字符串
String uidStr = parser.getAttributeValue(null, "uid");
......
//将uid字符串转变为整形
int uid = Process.getUidForName(uidStr);
.......
perm = perm.intern();
//得到保存uid当前已有的所有权限的ArraySet
ArraySet<String> perms = mSystemPermissions.get(uid);
if (perms == null) {
perms = new ArraySet<String>();
mSystemPermissions.put(uid, perms);
}
//将uid新增的权限,加入到它的ArraySet
perms.add(perm);
XmlUtils.skipCurrentTag(parser);
} else if ("library".equals(name) && allowLibs) {
String lname = parser.getAttributeValue(null, "name");
String lfile = parser.getAttributeValue(null, "file");

if (lname == null) {
......
} else if (lfile == null) {
.....
} else {
//保存library标签对应的内容
mSharedLibraries.put(lname, lfile);
}
} else if ("feature".equals(name) && allowFeatures) {
String fname = parser.getAttributeValue(null, "name");
int fversion = XmlUtils.readIntAttribute(parser, "version", 0);

if (!lowRam) {
allowed = true;
} else {
//内存不足时,指定notLowRam的feature不再加载
String notLowRam = parser.getAttributeValue(null, "notLowRam");
allowed = !"true".equals(notLowRam);
}

if (fname == null) {
.....
} else if (allowed) {
//将feature构造成featureInfo,加入到mAvailableFeatures对象中
addFeature(fname, fversion);
}
.......
} else if ("unavailable-feature".equals(name) && allowFeatures) {
//mUnavailableFeatures保存不支持的feature
.........
} else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {
// These are the packages that are white-listed to be able to run in the
// background while in power save mode (but not whitelisted from device idle modes),
// as read from the configuration files.
//mAllowInPowerSaveExceptIdle中保存省电模式下(非Idle),可上网的应用
.........
} else if ("allow-in-power-save".equals(name) && allowAll) {
// These are the packages that are white-listed to be able to run in the
// background while in power save mode, as read from the configuration files.
//mAllowInPowerSave与mAllowInPowerSaveExceptIdle类似,权限更高
//这与Android M新特性Doze and App Standby模式有关
//DeviceIdleController用于判断设备是否进入Idle状态,进入Idle状态时,mAllowInPowerSaveExceptIdle中的应用要被禁掉
//但mAllowInPowerSave中的应用仍可运行
............
} else if ("allow-in-data-usage-save".equals(name) && allowAll) {
// These are the packages that are white-listed to be able to run in the
// background while in data-usage save mode, as read from the configuration files.
//mAllowInDataUsageSave保存此标签对应的packageName
//貌似android 7新增了一个节省数据流量的能力,有此标签的应用在节省数据流量时,仍可访问网络
............
} else if ("app-link".equals(name) && allowAppConfigs) {
// These are the package names of apps which should be in the 'always'
// URL-handling state upon factory reset.
//mLinkedApps保存此标签对应的packageName
//这个不太明白,好像是指定可以一直处于URL-handling state的app
.......
} else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) {
// These are the packages that are whitelisted to be able to run as system user
//mSystemUserWhitelistedApps保存此标签对应的packageName
//指定以system user权限运行的app
.......
} else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) {
// These are the packages that should not run under system user
//mSystemUserBlacklistedApp保存此标签对应的packageName
//指定在system user权限下,不应该运行的app
.........
}else if ("default-enabled-vr-app".equals(name) && allowAppConfigs) {
// These are the components that are enabled by default as VR mode listener services.
//mDefaultVrComponents保存此标签对应的packageName
//指定默认运行在VR模式下的components
.......
} else if ("backup-transport-whitelisted-service".equals(name) && allowFeatures) {
// These are the permitted backup transport service components
//mBackupTransportWhitelist保存此标签对应的packageName
//保存能够传输备份数据的服务
........
} else {
.......
}
}
} catch (XmlPullParserException e) {PullParserException e) {
.......
} catch (IOException e) {
.......
} finally {
IoUtils.closeQuietly(permReader);
}

// Some devices can be field-converted to FBE, so offer to splice in
// those features if not already defined by the static config
//加密相关的feature
if (StorageManager.isFileEncryptedNativeOnly()) {
addFeature(PackageManager.FEATURE_FILE_BASED_ENCRYPTION, 0);
addFeature(PackageManager.FEATURE_SECURELY_REMOVES_USERS, 0);
}

for (String featureName : mUnavailableFeatures) {
//从mAvailableFeatures移除不支持的feature
removeFeature(featureName);
}
}

从上面的代码可以看出readPermissions函数就是将xml文件中的标签转换成对应的数据结构,此处重要的是理解各种标签的作用。
对于”permission”标签,还调用了readPermission函数:

void readPermission(XmlPullParser parser, String name)
throws IOException, XmlPullParserException {
if (mPermissions.containsKey(name)) {
throw new IllegalStateException("Duplicate permission definition for " + name);
}

final boolean perUser = XmlUtils.readBooleanAttribute(parser, "perUser", false);
final PermissionEntry perm = new PermissionEntry(name, perUser);
//将permission name和permissionEntry结合起来
mPermissions.put(name, perm);
........
while(.....) {
.......
String tagName = parser.getName();
if ("group".equals(tagName)) {
String gidStr = parser.getAttributeValue(null, "gid");
if (gidStr != null) {
int gid = Process.getGidForName(gidStr);
//对应gid存入permissionEntry结构体中,于是permission name与gid对应起来
perm.gids = appendInt(perm.gids, gid);
} else {
......
}
}
.......
}
}

Android7.0 PackageManagerService (2) PKMS构造函数的主要工作

PKMS创建的SystemConfig负责解析系统的xml配置文件,最终将形成上图所示的数据结构(列举了主要数据)。
在此之后,PKMS取出并保存了SystemConfig中的权限和feature等信息。

三、加载签名策略
我们回到PKMS的构造函数,看下一段代码:

..............
synchronized (mInstallLock) {
synchronized (mPackages) {
//mHandlerThread将负责Apk的安装和卸载
mHandlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
mHandlerThread.start();

//PackageHandler、ProcessLoggingHandler共用ServiceThread
mHandler = new PackageHandler(mHandlerThread.getLooper());
mProcessLoggingHandler = new ProcessLoggingHandler();

//Watchdog监控ServiceThread是否长时间阻塞
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);

//创建/data下下一系列的目录
File dataDir = Environment.getDataDirectory();
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
mEphemeralInstallDir = new File(dataDir, "app-ephemeral");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");

//针对Android系统中多用户场景
sUserManager = new UserManagerService(context, this, mPackages);

//Propagate permission configuration in to package manager.
//取出SystemConfig中的mPermissions
ArrayMap<String, SystemConfig.PermissionEntry> permConfig
= systemConfig.getPermissions();
for (int i=0; i<permConfig.size(); i++) {
SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
BasePermission bp = mSettings.mPermissions.get(perm.name);
if (bp == null) {
bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
mSettings.mPermissions.put(perm.name, bp);
}
if (perm.gids != null) {
//将android权限和gid关联的信息,存入到mSettings对象的mPermissions中
//boolean型的perUser,也是从xml中解析出来的
bp.setGids(perm.gids, perm.perUser);
}
}

//取出systemConfig对象中的链接库信息,保存到PKMS中
ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
for (int i=0; i<libConfig.size(); i++) {
mSharedLibraries.put(libConfig.keyAt(i), new SharedLibraryEntry(libConfig.valueAt(i), null));
}

//此处加载签名策略
mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();
...........

我们看看代码:

/**
* Load the mac_permissions.xml file containing all seinfo assignments used to
* label apps. The loaded mac_permissions.xml file is determined by the
* MAC_PERMISSIONS class variable which is set at class load time which itself
* is based on the USE_OVERRIDE_POLICY class variable. For further guidance on
* the proper structure of a mac_permissions.xml file consult the source code
* located at system/sepolicy/mac_permissions.xml.
*/

public static boolean readInstallPolicy() {
// Temp structure to hold the rules while we parse the xml file
List<Policy> policies = new ArrayList<>();

FileReader policyFile = null;
XmlPullParser parser = Xml.newPullParser();
try {
//MAC_PERMISSIONS为SELinuxMMAC中的静态变量,保存"system/etc/security/mac_permissions.xml"对应的file
//源码7.0中路径为"system/sepolicy/mac_permissions.xml",应该是编译后拷入到etc目录的
policyFile = new FileReader(MAC_PERMISSIONS);
.............
while (parser.next() != XmlPullParser.END_TAG) {
.........
switch (parser.getName()) {
case "signer":
//加载签名策略
//readSignerOrThrow负责解析xml,构造出policy
policies.add(readSignerOrThrow(parser));
break;
..........
}
}
} ......

// Now sort the policy stanzas
PolicyComparator policySort = new PolicyComparator();
Collections.sort(policies, policySort);
..........

synchronized (sPolicies) {
//加载完签名策略后存入静态变量
sPolicies = policies;
.....
}

return true;
}

从上面的代码可以看出,readInstallPolicy其实也是解析xml文件,以读出相应的签名策略。
我们看看”system/sepolicy/mac_permissions.xml”:

<?xml version="1.0" encoding="utf-8"?>
<policy>
<!--
..........
* valid stanzas can take one of the following forms:

// single cert protecting seinfo
<signer signature="@PLATFORM" >
<seinfo value="platform" />
</signer>

// multiple certs protecting seinfo (all contained certs must match)
<signer>
<cert signature="@PLATFORM1"/>
<cert signature="@PLATFORM2"/>
<seinfo value="platform" />
</signer>

// single cert protecting explicitly named app
<signer signature="@PLATFORM" >
<package name="com.android.foo">
<seinfo value="bar" />
</package>
</signer>

// multiple certs protecting explicitly named app (all certs must match)
<signer>
<cert signature="@PLATFORM1"/>
<cert signature="@PLATFORM2"/>
<package name="com.android.foo">
<seinfo value="bar" />
</package>
</signer>
-->


<!-- Platform dev key in AOSP -->
<signer signature="@PLATFORM" >
<seinfo value="platform" />
</signer>

</policy>

seinfo决定了Android中进程所在的domain,以及其数据文件在安全上下文中的Type,linux将根据此制定访问策略。这些内容涉及到SEAndroid安全机制,自己其实也是一知半解,有机会再做分析。

根据mac_permissions.xml的定义,如果App是在Android源码编译环境下,其Android.mk中指定了LOCAL_CERTIFICATE : = platform的话,它的 seinfo就是platform。如果Android.mk中不进行对应的设置,setinfo为默认值default。对于第三方APK,其seinfo值通常为default。

mac_permissions.xml编译进system/etc目录时,@PLATFORM将被实际的签名信息替换,以下是我从android6.0机器中导出的文件内容:

<?xml version="1.0" encoding="iso-8859-1"?><!-- AUTOGENERATED FILE DO NOT MODIFY --><policy><signer signature="308204a830820390a003020102020900b3998086d056cffa300d06092a864886f70d0101040500308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d301e170d3038303431353232343035305a170d3335303930313232343035305a308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d30820120300d06092a864886f70d01010105000382010d003082010802820101009c780592ac0d5d381cdeaa65ecc8a6006e36480c6d7207b12011be50863aabe2b55d009adf7146d6f2202280c7cd4d7bdb26243b8a806c26b34b137523a49268224904dc01493e7c0acf1a05c874f69b037b60309d9074d24280e16bad2a8734361951eaf72a482d09b204b1875e12ac98c1aa773d6800b9eafde56d58bed8e8da16f9a360099c37a834a6dfedb7b6b44a049e07a269fccf2c5496f2cf36d64df90a3b8d8f34a3baab4cf53371ab27719b3ba58754ad0c53fc14e1db45d51e234fbbe93c9ba4edf9ce54261350ec535607bf69a2ff4aa07db5f7ea200d09a6c1b49e21402f89ed1190893aab5a9180f152e82f85a45753cf5fc19071c5eec827020103a381fc3081f9301d0603551d0e041604144fe4a0b3dd9cba29f71d7287c4e7c38f2086c2993081c90603551d230481c13081be80144fe4a0b3dd9cba29f71d7287c4e7c38f2086c299a1819aa48197308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d820900b3998086d056cffa300c0603551d13040530030101ff300d06092a864886f70d01010405000382010100572551b8d93a1f73de0f6d469f86dad6701400293c88a0cd7cd778b73dafcc197fab76e6212e56c1c761cfc42fd733de52c50ae08814cefc0a3b5a1a4346054d829f1d82b42b2048bf88b5d14929ef85f60edd12d72d55657e22e3e85d04c831d613d19938bb8982247fa321256ba12d1d6a8f92ea1db1c373317ba0c037f0d1aff645aef224979fba6e7a14bc025c71b98138cef3ddfc059617cf24845cf7b40d6382f7275ed738495ab6e5931b9421765c491b72fb68e080dbdb58c2029d347c8b328ce43ef6a8b15533edfbe989bd6a48dd4b202eda94c6ab8dd5b8399203daae2ed446232e4fe9bd961394c6300e5138e3cfd285e6e4e483538cb8b1b357"><seinfo value="platform"/></signer><default><seinfo value="default"/></default></policy>

了解mac_permissions.xml的内容后,最后再看看解析xml使用的函数:

private static Policy readSignerOrThrow(XmlPullParser parser) throws IOException,
XmlPullParserException {
parser.require(XmlPullParser.START_TAG, null, "signer");
//策略构造器
Policy.PolicyBuilder pb = new Policy.PolicyBuilder();

// Check for a cert attached to the signer tag. We allow a signature
// to appear as an attribute as well as those attached to cert tags.
String cert = parser.getAttributeValue(null, "signature");
if (cert != null) {
pb.addSignature(cert);
}

while (parser.next() != XmlPullParser.END_TAG) {
.............
String tagName = parser.getName();
if ("seinfo".equals(tagName)) {
String seinfo = parser.getAttributeValue(null, "value");
pb.setGlobalSeinfoOrThrow(seinfo);
readSeinfo(parser);
} else if ("package".equals(tagName)) {
readPackageOrThrow(parser, pb);
} else if ("cert".equals(tagName)) {
String sig = parser.getAttributeValue(null, "signature");
pb.addSignature(sig);
readCert(parser);
} else {
skip(parser);
}
}

//构造出实际的policy
return pb.build();
}

容易看出,上面的函数就是根据标签信息,构造出对应的Selinux Policy。

四、扫描Package
我们回到PKMS的构造函数,看下一段代码:

........
//解析Settings构造函数中提及的文件:"packages.xml"、"packages-stopped.xml"等
//此处将通过解析XML文件,得到之前系统保存的Package相关的信息,暂时不深入分析函数
mRestoredSettings = mSettings.readLPw(sUserManager.getUsers(false));
..............
long startTime = SystemClock.uptimeMillis();
..............

// Set flag to monitor and not change apk file paths when
// scanning install directories.
//定义扫描参数
final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;
.........

/**
* Ensure all external libraries have had dexopt run on them.
*/

//这一部分代码,应该是利用installd对所有platform.xml定义的链接库文件进行dex优化
if (mSharedLibraries.size() > 0) {
// NOTE: For now, we're compiling these system "shared libraries"
// (and framework jars) into all available architectures. It's possible
// to compile them only when we come across an app that uses them (there's
// already logic for that in scanPackageLI) but that adds some complexity.
......................
}

//指向system/framework目录
File frameworkDir = new File(Environment.getRootDirectory(), "framework");

//处理系统升级相关的问题
..............

// Collect vendor overlay packages.
// (Do this before scanning any apps.)
// For security and version matching reason, only consider
// overlay packages if they reside in VENDOR_OVERLAY_DIR.
File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);

//扫描目标目录下的Package
scanDirTracedLI(vendorOverlayDir, mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);

//利用scanDirTracedLI扫描system/framework、system/priv-app、system/app、vendor/app等目录,传入的parseFlag不一样
........

我们跟进一下scanDirTracedLI:

private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir");
try {
//此处进行实际的扫描工作
scanDirLI(dir, parseFlags, scanFlags, currentTime);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}

private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
final File[] files = dir.listFiles();
.......
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}

try {
//处理目录下每一个package文件
scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
} catch (PackageManagerException e) {
.........
}
}
}

private PackageParser.Package scanPackageTracedLI(File scanFile, final int parseFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
try {
return scanPackageLI(scanFile, parseFlags, scanFlags, currentTime, user);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}

我们看一下此时调用的scanPackageLI函数:

private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
long currentTime, UserHandle user) throws PackageManagerException {
//创建出PackageParser对象
PackageParser pp = new PackageParser();
...........
final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(scanFile, parseFlags);
} catch (PackageParserException e) {
..........
} finally {
..........
}

//调用另一个scanPackageLI
return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);
}

从上面的代码,可以看出scanPackageLI先调用PackageParser对APK文件进行解析,完成从物理文件从对应数据结构的转换。
我们先来看看对应的parsePackage函数。

1、 PackageParser的parsePackage函数

/**
* Parse the package at the given location. Automatically detects if the
* package is a monolithic style (single APK file) or cluster style
* (directory of APKs).
*/

public Package parsePackage(File packageFile, int flags) throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
} else {
return parseMonolithicPackage(packageFile, flags);
}
}

从上面的代码可以看出,对于单一APK文件和多APK文件的package,分别调用了不同的函数进行处理。实际上,两个函数中的关键部分是一致的,我们以第一个函数为例,继续分析:

/**
* Parse all APKs contained in the given directory, treating them as a
* single package. This also performs sanity checking, such as requiring
* identical package name and version codes, a single base APK, and unique
* split names.
* /
private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
//1、解析出简化信息,例如名称、路径之类的
final PackageLite lite = parseClusterPackageLite(packageDir, 0);
.........
final AssetManager assets = new AssetManager();
try {
// Load the base and all splits into the AssetManager
// so that resources can be overriden when parsing the manifests.
//2、将APK的一些信息放入资源管理器中
loadApkIntoAssetManager(assets, lite.baseCodePath, flags);

if (!ArrayUtils.isEmpty(lite.splitCodePaths)) {
for (String path : lite.splitCodePaths) {
loadApkIntoAssetManager(assets, path, flags);
}
}

final File baseApk = new File(lite.baseCodePath);
//3、解析主要APK信息
final Package pkg = parseBaseApk(baseApk, assets, flags);
.........
if (!ArrayUtils.isEmpty(lite.splitNames)) {
final int num = lite.splitNames.length;
pkg.splitNames = lite.splitNames;
pkg.splitCodePaths = lite.splitCodePaths;
pkg.splitRevisionCodes = lite.splitRevisionCodes;
pkg.splitFlags = new int[num];
pkg.splitPrivateFlags = new int[num];

for (int i = 0; i < num; i++) {
//4、解析其它分离的APK信息
parseSplitApk(pkg, i, assets, flags);
}
}

pkg.setCodePath(packageDir.getAbsolutePath());
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} finally {
IoUtils.closeQuietly(assets);
}
}

上面的代码可以分为4个主要的步骤,我们现在来一一分析:

1.1 parseClusterPackageLite

private static PackageLite parseClusterPackageLite(File packageDir, int flags)
throws PackageParserException {
final File[] files = packageDir.listFiles();
................
String packageName = null;
int versionCode = 0;

final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
for (File file : files) {
if (isApkFile(file)) {
//执行实际的parse工作
final ApkLite lite = parseApkLite(file, flags);

// Assert that all package names and version codes are
// consistent with the first one we encounter.
if (packageName == null) {
packageName = lite.packageName;
versionCode = lite.versionCode;
} else {
//检查名称一致性
if (!packageName.equals(lite.packageName)) {
//throw exception
..............
}
//检查版本号一致性
if (versionCode != lite.versionCode) {
//throw exception
..............
}
}

// Assert that each split is defined only once
if (apks.put(lite.splitName, lite) != null) {
//throw exception
..........
}
}
}

//baseApk的splitName为null,因此remove后被移出
final ApkLite baseApk = apks.remove(null);
..........
// Always apply deterministic ordering based on splitName
final int size = apks.size();

String[] splitNames = null;
String[] splitCodePaths = null;
int[] splitRevisionCodes = null;
//splitAPK信息排序后,存储
if (size > 0) {
splitNames = new String[size];
splitCodePaths = new String[size];
splitRevisionCodes = new int[size];

splitNames = apks.keySet().toArray(splitNames);
Arrays.sort(splitNames, sSplitNameComparator);

for (int i = 0; i < size; i++) {
splitCodePaths[i] = apks.get(splitNames[i]).codePath;
splitRevisionCodes[i] = apks.get(splitNames[i]).revisionCode;
}
}

final String codePath = packageDir.getAbsolutePath();
//构造出PackageLite并返回
return new PackageLite(codePath, baseApk, splitNames, splitCodePaths,
splitRevisionCodes);
}

容易看出,上述代码中进行实际解析操作的函数是parseApkLite:

public static ApkLite parseApkLite(File apkFile, int flags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();

AssetManager assets = null;
XmlResourceParser parser = null;
try {
assets = new AssetManager();
assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Build.VERSION.RESOURCES_SDK_INT);

//资源管理器中存储路径信息
int cookie = assets.addAssetPath(apkPath);
........
final DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();

final Resources res = new Resources(assets, metrics, null);
//获取一个XML资源解析文件,该对象解析的是APK中的AndroidManifest.xml文件
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);

//获取APK中的签名信息
..........

//XmlResourceParser继承自AttributeSet
final AttributeSet attrs = parser;

return parseApkLite(apkPath, res, parser, attrs, flags, signatures, certificates);
} catch (XmlPullParserException | IOException | RuntimeException e) {
.......
} finally {
IoUtils.closeQuietly(parser);
IoUtils.closeQuietly(assets);
}
}

private static ApkLite parseApkLite(String codePath, Resources res, XmlPullParser parser,
AttributeSet attrs, int flags, Signature[] signatures, Certificate[][] certificates)
throws IOException, XmlPullParserException, PackageParserException {
//得到packageName+splitName
final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);

//以下变量设为默认值
int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
int versionCode = 0;
int revisionCode = 0;
boolean coreApp = false;
boolean multiArch = false;
boolean use32bitAbi = false;
boolean extractNativeLibs = true;

//利用XML资源解析器,从xml中取出上述变量对应的值(未定义则用默认值)
................

//利用上述变量构成ApkLite返回
return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
revisionCode, installLocation, verifiers, signatures, certificates, coreApp,
multiArch, use32bitAbi, extractNativeLibs);
}

上面的代码做了多次封装,但本质是获取AndroidManifest.xml对应的XML资源解析器,解析出其中部分属性,然后形成ApkLite对象返回。

1.2 loadApkIntoAssetManager

private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
throws PackageParserException {
...........
// The AssetManager guarantees uniqueness for asset paths, so if this asset path
// already exists in the AssetManager, addAssetPath will only return the cookie
// assigned to it
//前一部分实际上已经调用过AssetManager添加apkPath
int cookie = assets.addAssetPath(apkPath);
...........
return cookie;
}

/**
* Add an additional set of assets to the asset manager. This can be
* either a directory or ZIP file. Not for use by applications. Returns
* the cookie of the added asset, or 0 on failure.
*/

public final int addAssetPath(String path) {
return addAssetPathInternal(path, false);
}

private final int addAssetPathInternal(String path, boolean appAsLib) {
synchronized (this) {
//依赖Native函数,完成实际的添加
int res = addAssetPathNative(path, appAsLib);
.........
return res;
}
}

上面的主要是APK对应的资源文件的路径,加入到资源管理器中。最终还是依赖于Native层的函数完成实际的工作,在此处先不做进一步分析。

1.3 parseBaseApk

private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {
//创建Package对象,填充部分构造Package对象需要的信息
.........
return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
}

/**
* This is the common parsing routing for handling parent and child
* packages in a base APK. The difference between parent and child
* parsing is that some tags are not supported by child packages as
* well as some manifest attributes are ignored. The implementation
* assumes the calling code has already handled the manifest tag if needed
* (this applies to the parent only).
* /
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)) {
...........
String tagName = parser.getName();
............
if (tagName.equals(TAG_APPLICATION)) {
........
//解析“application”标签
f (!parseBaseApplication(pkg, res, parser, flags, outError)) {
return null;
}
} else if (tagName.equals(TAG_OVERLAY)) {
//填充overlay资源对应信息
...........
}.........
//解析一系列AndroidManifest.xml中定义的标签
................
}
}

parseBaseApk主要就是构造Package对象,然后解析AndroidManifest.xml中的标签,形成对应的数据结构。
由于AndroidManifest.xml可使用的标签太多,不一一列举。

1.4 parseSplitApk
parseSplitApk的内容与parseBaseApk基本一致:

private void parseSplitApk(Package pkg, int splitIndex, AssetManager assets, int flags)
throws PackageParserException {
final String apkPath = pkg.splitCodePaths[splitIndex];
...........
//路径进入到资源管理器中
final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
..........
try {
res = new Resources(assets, mMetrics, null);
assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Build.VERSION.RESOURCES_SDK_INT);
//同样构造出AndroidManifest.xml对应的资源解析器
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
//同样是解析AndroidManifest.xml中的标签信息,只是解析的标签内容不同
pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError);
.........
} .......
.......
}

以上就是parseClusterPackage的主要内容,实际上就是解析出Package对应的数据结构。
代码看起来相当繁琐,但实际思想确实很简单的,无非就是解析AndroidManifest.xml对应的标签项,然后形成对应的数据结构插入到Package中。

前面提到过parseClusterPackage是用于解析存在多个APK文件的Package,parseMonolithicPackage用于解析单个APK文件的Package。
实际上parseMonolithicPackage就是靠parseBaseApk函数完成解析工作的,是parseClusterPackage函数对应工作的一个子集。

Android7.0 PackageManagerService (2) PKMS构造函数的主要工作
图片链接

扫描Package的第一部分工作,难度不大,但极其的繁琐,跟着流程走一边真是想死的心都有了。不过正如Torvalds大神所说的,”RTFSC, read the fucking source code”,耐着性子多看看,是提高的基础条件。

上图画出了PackageParser解析Apk文件,得到的主要的数据结构,实际的内容远多于这些,我们仅保留了四大组件和权限相关的内容。
上面这些类,全部是定义于PackageParser中的内部类,这些内部类主要的作用就是保存AndroidManifest.xml解析出的对应信息。
以PackageParser.Activity为例,注意到该类持有ActivityInfo类,继承自Component< ActivityIntentInfo>。其中,ActivityInfo用于保存Activity的信息;Component类是一个模板,对应元素类型是ActivityIntentInfo,顶层基类为IntentFilter。四大组件中的其它成员,也有类似的继承结构。
这种设计的原因是:Package除了保存信息外,还需要支持Intent匹配查询。例如,当收到某个Intent后,由于ActivityIntentInfo继承自IntentFilter,因此它能判断自己是否满足Intent的要求。如果满足,则返回对应的ActivityInfo。

最后,我们结合上图回忆一下整个扫描过程:
* PackageParser首先解析出了ApkLite,得到每个Apk文件的简化信息(对于具有多个Apk文件的Package来说,将得到多个ApkLite);
* 利用所有的ApkLite及XML中的其它信息,解析出PackageLite;
* 利用PackageLite中的信息及XML中的其它信息,解析出Package信息;Package中就基本上涵盖了AndroidManifest.xml中涉及的所有信息。
注意在上述的解析过程中,PackageParser利用AssetManager存储了Package中资源文件的地址。

2、另一个scanPackageLI函数
通过上述的扫描过程,我们得到了当前Apk文件对应的Package信息。但这部分信息是存储在PackageParser中的,必须将这部分信息上交到PKMS中。毕竟最终的目的是:让PKMS能得到所有目录下Package的信息。

private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,
final int policyFlags, int scanFlags, long currentTime, UserHandle user)
throws PackageManagerException {
// If the package has children and this is the first dive in the function
// we recursively scan the package with the SCAN_CHECK_ONLY flag set to see
// whether all packages (parent and children) would be successfully scanned
// before the actual scan since scanning mutates internal state and we want
// to atomically install the package and its children
//有childPackage时,第一次只执行检查的工作
if ((scanFlags & SCAN_CHECK_ONLY) == 0) {
//当解析一个Package的AndroidManifest.xml时,如果该XML文件中使用了"package"的tag
//那么该tag对应的package是当前XML文件对应package的childPackage
if (pkg.childPackages != null && pkg.childPackages.size() > 0) {
scanFlags |= SCAN_CHECK_ONLY;
}
} else {
//第二次进入,才开始实际的解析
scanFlags &= ~SCAN_CHECK_ONLY;
}

final PackageParser.Package scannedPkg;
try {
// Scan the parent
//scanFlags将决定这一次是否仅执行检查工作
scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags, currentTime, user);

final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPkg = pkg.childPackages.get(i);
scanPackageLI(childPkg, policyFlags,
scanFlags, currentTime, user);
}
} finally {
.........
}

if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
//第一次检查完毕后,再次调用函数
return scanPackageTracedLI(pkg, policyFlags, scanFlags, currentTime, user);
}

return scannedPkg;
}

private PackageParser.Package scanPackageLI(PackageParser.Package pkg, final int policyFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
boolean success = false;
try {
//实际的解析函数,长达1000行......我觉得要是我来写的话,应该无法通过代码审查
final PackageParser.Package res = scanPackageDirtyLI(pkg, policyFlags, scanFlags,
currentTime, user);
success = true;
return res;
} finally {
...........
}
}

我们跟进一下scanPackageDirtyLI函数:

2.1 特殊处理”Android” package

private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
final int policyFlags, final int scanFlags, long currentTime, UserHandle user)
throws PackageManagerException {
final File scanFile = new File(pkg.codePath);
..........
//根据policyFlags设置package及其中applicationInfo等成员的信息
..........
//mCustomResolverComponentName是从系统资源中读出的,可以配置
if (mCustomResolverComponentName != null &&
mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) {
//这里的用途和下面判断packageName是否为"android有关"
//Replacing default ResolverActivity
setUpCustomResolverActivity(pkg);
}

if (pkg.packageName.equals("android")) {
synchronized (mPackages) {
........
if ((scanFlags & SCAN_CHECK_ONLY) == 0) {
// Set up information for our fall-back user intent resolution activity.
mPlatformPackage = pkg;
pkg.mVersionCode = mSdkVersion;
mAndroidApplication = pkg.applicationInfo;

//上面的setUpCustomResolverActivity被调用时,mResolverReplaced就为true
if (!mResolverReplaced) {
mResolveActivity.applicationInfo = mAndroidApplication;
mResolveActivity.name = ResolverActivity.class.getName();
mResolveActivity.packageName = mAndroidApplication.packageName;
mResolveActivity.processName = "system:ui";
mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
mResolveActivity.exported = true;
mResolveActivity.enabled = true;
mResolveActivity.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
mResolveActivity.configChanges = ActivityInfo.CONFIG_SCREEN_SIZE
| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE
| ActivityInfo.CONFIG_SCREEN_LAYOUT
| ActivityInfo.CONFIG_ORIENTATION
| ActivityInfo.CONFIG_KEYBOARD
| ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
mResolveInfo.activityInfo = mResolveActivity;
mResolveInfo.priority = 0;
mResolveInfo.preferredOrder = 0;
mResolveInfo.match = 0;
mResolveComponentName = new ComponentName(
mAndroidApplication.packageName, mResolveActivity.name);
}
}
}
.............
}

在这一部分代码中,scanPackageDirtyLI函数单独处理了名为”android”的Package。
和该Pacakge对应的APK是framework-res.apk,定义于frameworks/base/core/res中,对应的AndroidManifest.xml为:

.........
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android" coreApp="true" android:sharedUserId="android.uid.system"
android:sharedUserLabel="@string/android_system_label">
.........

实际上,framework-res.apk还包含了以下常用的Activity:
* ChooserActivity:当多个Activity符合某个Intent的时候,系统会弹出此Activity,由用户选择合适的应用来处理。
*ShutdownActivity:关机前弹出的系统对话框。

现在很多做ROM的厂商,应该就会修改这些Activity,以满足自己的Feature。

该Package和系统息息相关,因此得到了PKMS的特变青睐,主要提现在以下几点:
* PKMS中的mPlatformPackage成员用于保存该Package信息。
* mAndroidApplication用于保存此Package中的ApplicationInfo。
* mResolveActivity指向用于表示ChooserActivity信息的ActivityInfo。
* mResolveInfo为ResolveInfo类型,它用于存储系统解析Intent(经IntentFilter过滤)后得到得到的结果信息,例如满足某个Intent的Activity的信息。

在从PKMS中查询满足某个Intent的Activity时,返回的就是ResolveInfo,再根据ResolveInfo的信息得到具体的Activity。
可能是因为ChooserActivity使用的地方较多,因此PKMS在此处保存这些信息,以提高运行过程中的效率。

在PKMS的构造函数中,有以下代码:

..............
String customResolverActivity = Resources.getSystem().getString(
R.string.config_customResolverActivity);
if (TextUtils.isEmpty(customResolverActivity)) {
ustomResolverActivity = null;
} else {
mCustomResolverComponentName = ComponentName.unflattenFromString(
customResolverActivity);
}
...........

因此可以通过改变配置信息,使得setUpCustomResolverActivity被调用,从而替换默认的ResolverActivity。

2.2 正常处理流程
我们回到scanPackageDirtyLI函数:

.........
synchronized (mPackages) {
//mPackages用于保存系统内所有Package,以pacakgeName为key
if (mPackages.containsKey(pkg.packageName)
|| mSharedLibraries.containsKey(pkg.packageName)) {
throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
"Application package " + pkg.packageName
+ " already installed. Skipping duplicate.");
}

// If we're only installing presumed-existing packages, require that the
// scanned APK is both already known and at the path previously established
// for it. Previously unknown packages we pick up normally, but if we have an
// a priori expectation about this package's install presence, enforce it.
// With a singular exception for new system packages. When an OTA contains
// a new system package, we allow the codepath to change from a system location
// to the user-installed location. If we don't allow this change, any newer,
// user-installed version of the application will be ignored.
//这一段注释和代码都不是很懂........
............
}

// Initialize package source and resource directories
File destCodeFile = new File(pkg.applicationInfo.getCodePath());
File destResourceFile = new File(pkg.applicationInfo.getResourcePath());

//代表该Package的SharedUserSettings对象
SharedUserSetting suid = null;
//代表该Pacakge的PacakgeSettings对象
PackageSetting pkgSetting = null;

..........
synchronized (mPackages) {
if (pkg.mSharedUserId != null) {
//创建Package对应的ShareduserSetting,然后加入到PKMS中Settings对象维护的数据结构中
suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, 0, true);
if (suid == null) {
//创建失败,抛出异常
.........
}
}

//创建出Package对应的PackageSettings,必要时还要处理Package新旧信息的转换
.............

if ((policyFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
// Check all shared libraries and map to their actual file path.
// We only do this here for apps not on a system dir, because those
// are the only ones that can fail an install due to this. We
// will take care of the system apps by updating all of their
// library paths after the scan is done.
//如果Package申明需要library或option-library, PKMS要确保这些library已经被加载到mSharedLibraries中
updateSharedLibrariesLPw(pkg, null);
}

//根据policy文件,找到Pacakge对应的seinfo,然后存入Pacakge的applicationInfo中
if (mFoundPolicyFile) {
SELinuxMMAC.assignSeinfoValue(pkg);
}

//处理Package的签名信息,还包括更新和验证
............

// Verify that this new package doesn't have any content providers
// that conflict with existing packages. Only do this if the
// package isn't already installed, since we don't want to break
// things that are installed.
if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
//如果是新安装的Pacakge,需要检查其中的Provider是否与之前安装的Package冲突
...........
}

//还是处理权限相关的,不太懂mAdoptPermissions
if ((scanFlags & SCAN_CHECK_ONLY) == 0 && pkg.mAdoptPermissions != null) {
// This package wants to adopt ownership of permissions from
// another package.
.................
}
}
.........
//设置运行该Pacakge的进程的进程名,一般为PackageName
pkg.applicationInfo.processName = fixProcessName(
pkg.applicationInfo.packageName,
pkg.applicationInfo.processName,
pkg.applicationInfo.uid);

if (pkg != mPlatformPackage) {
// Get all of our default paths setup
//看代码,此处只是为Pacakge赋予了安装路径
pkg.applicationInfo.initForUser(UserHandle.USER_SYSTEM);
}

//处理Native库和CPU ABI
................

//处理系统APK更新时,链接库的改变
synchronized (mPackages) {
..............
// New library entries can only be added through the
// system image. This is important to get rid of a lot
// of nasty edge cases: for example if we allowed a non-
// system update of the app to add a library, then uninstalling
// the update would make the library go away, and assumptions
// we made such as through app install filtering would now
// have allowed apps on the device which aren't compatible
// with it. Better to just have the restriction here, be
// conservative, and create many fewer cases that can negatively
// impact the user experience.
..................
}

...........

//将Package中的信息加入到PKMS的Settings对象中
//在此之前,四大组件的信息都是属于Package的私有财产,现在同一注册到PKMS中
//于是PKMS就可以对外提供统一的组件信息了
synchronized (mPackages) {
// Add the new setting to mSettings
mSettings.insertPackageSettingLPw(pkgSetting, pkg);
// Add the new setting to mPackages
mPackages.put(pkg.applicationInfo.packageName, pkg);
..............
// Add the package's KeySets to the global KeySetManagerService
ksms.addScannedPackageLPw(pkg);

//处理Provider信息
int N = pkg.providers.size();
.........
for (i=0; i<N; i++) {
PackageParser.Provider p = pkg.providers.get(i);
........
mProviders.addProvider(p);
........
}
..............
//处理service信息
N = pkg.services.size();
.........
for (i=0; i<N; i++) {
........
mServices.addService(s);
........
}
..............
//处理Receiver
N = pkg.receivers.size();
........
for (i=0; i<N; i++) {
.......
mReceivers.addActivity(a, "receiver");
.......
}
..............
//处理Activity
N = pkg.activities.size();
.........
for (i=0; i<N; i++) {
.......
mActivities.addActivity(a, "activity");
.......
}
........
//处理PermissionGroup
N = pkg.permissionGroups.size();
........
for (i=0; i<N; i++) {
.....
mPermissionGroups.put(pg.info.name, pg);
......
}
.........
//处理permission和instrumentation等
.........
return pkg;
}

Android7.0 PackageManagerService (2) PKMS构造函数的主要工作

PKMS扫描Pacakge的过程终于整理完毕,其实整个逻辑可以整理成上图。
我们从代码也可以看出,整个过程从大的逻辑上来看,其实并不复杂。但其中很多地方,例如每个标签的含义、对某些字段的处理细节,还是需要进一步分析才谈的上深入理解。此处,我们就像PKMS中提到的SCAN_CHECK_ONLY一样,先做一个大致的了解。需要实际问题时,再作详细分析。

五、最后的工作
我们再次回到PKMS的构造函数:

..............
// Prune any system packages that no longer exist.
//以下代码会清除一些Pacakge,例如不能使用的或不完整的,同时清除PKMS中保留的对应信息
...............

if (!mOnlyCore) {
..........
//扫描第三方APK的Pacakge
scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);

scanDirTracedLI(mDrmAppPrivateInstallDir, mDefParseFlags
| PackageParser.PARSE_FORWARD_LOCK,
scanFlags | SCAN_REQUIRE_KNOWN, 0);

scanDirLI(mEphemeralInstallDir, mDefParseFlags
| PackageParser.PARSE_IS_EPHEMERAL,
scanFlags | SCAN_REQUIRE_KNOWN, 0);
.............
}

// Resolve protected action filters. Only the setup wizard is allowed to
// have a high priority filter for these actions.
//为开机向导的action filter保留高优先级,不知原因
mSetupWizardPackage = getSetupWizardPackageName();
if (mProtectedFilters.size() > 0) {
...........
for (ActivityIntentInfo filter : mProtectedFilters) {
if (filter.activity.info.packageName.equals(mSetupWizardPackage)) {
.........
continue;
}
......
filter.setPriority(0);
}
}
mDeferProtectedFilters = false;
mProtectedFilters.clear();

// Now that we know all of the shared libraries, update all clients to have
// the correct library paths.
updateAllSharedLibrariesLPw();

//最后做一些其它的更新操作,例如Pacakge使用时间、权限
//做一些其它检查
...........

// can downgrade to reader
//将信息写到package.xml、package.lsit及pacakge-stopped.xml文件中
mSettings.writeLPr();

// Perform dexopt on all apps that mark themselves as coreApps. We do this pretty
// early on (before the package manager declares itself as early) because other
// components in the system server might ask for package contexts for these apps.
//
if ((isFirstBoot() || isUpgrade() || VMRuntime.didPruneDalvikCache()) && !onlyCore) {
..........
//对所有coreApp进行dexopt优化
int[] stats = performDexOpt(coreApps, false,
getCompilerFilterForReason(REASON_CORE_APP));
..........
}

//最后完成PKMS中一些变量的赋值、内存清理等工作
...............

最后一部分比较重要的其实还是解析非系统Apk的AndroidManifest.xml,形成对应的Package信息加入到PKMS中。
其它部分比较细节,此处不做详述。

六、总结
从逻辑的角度来看,PKMS构造函数主要功能比较清晰,但隐藏了许多细节。我们关注的是它大体的流程,及形成的数据结构。