PKMS( PackageManagerService简称 )是一个系统服务,它的主要功能是解析APK并保存数据。由于在工作遇到过一些麻烦,最终的原因是由于对PKMS理解不够导致的,因此我决心把PKMS的源码重新并整理一次,以加深理解。
由于PKMS的代码量非常大,为了缓存阅读文章的疲劳,我将分三步讲解,如下
- 扫描APK前的准备工作。
- 扫描目录并解析APK
- 保存解析数据到文件。
扫描前准备工作
初始化
本文只分析第一阶段的工作,扫描前的准备工作。由于代码量非常大,因此分段来分析
// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// ...
mContext = context;
// factory test mode下为true,正常模式启动下,为false
mFactoryTest = factoryTest;
// 设备正在加密或者已经加密状态下,这个值为true,正常模式启动下,值为false
mOnlyCore = onlyCore;
mMetrics = new DisplayMetrics();
mInstaller = installer;
// 1. 初始化
synchronized (mInstallLock) {
synchronized (mPackages) {
// Expose private service for system components to use.
// 从注释看,这里是暴露私有接口供系统组件使用
LocalServices.addService(
PackageManagerInternal.class, new PackageManagerInternalImpl());
sUserManager = new UserManagerService(context, this,
new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
// ComponentResolver 用于在匹配四大组件时,解析数据
mComponentResolver = new ComponentResolver(sUserManager,
LocalServices.getService(PackageManagerInternal.class),
mPackages);
// 用于管理权限服务端
mPermissionManager = PermissionManagerService.create(context,
mPackages /*externalLock*/);
// DefaultPermissionGrantPolicy类型
mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy();
mSettings = new Settings(Environment.getDataDirectory(),
mPermissionManager.getPermissionSettings(), mPackages);
}
}
// ...
}
这里初始化了很多变量,这些变量到底有什么用,一时间还真难总结出来。然而,随着我们分析的逐步深入,自然就会明白这些变量的作用了。
这里我们主要关心 mPermissionManager 变量的初始化,它调用的是PermissionManagerService#create() 方法
// frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
public static PermissionManagerServiceInternal create(Context context,
@NonNull Object externalLock) {
final PermissionManagerServiceInternal permMgrInt =
LocalServices.getService(PermissionManagerServiceInternal.class);
if (permMgrInt != null) {
return permMgrInt;
}
new PermissionManagerService(context, externalLock);
// 返回值是在PermissionManagerService构造函数中保存的
return LocalServices.getService(PermissionManagerServiceInternal.class);
}
首次调用这个方法,会先创建 PermissionManagerService 对象,然后返回的 PermissionManagerService 内部接口的一个对象。
创建 PermissionManagerService
那么来看下 PermissionManagerService 创建时候做了什么
PermissionManagerService(Context context,
@NonNull Object externalLock) {
// 1. 初始化变量
mContext = context;
mLock = externalLock;
mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
// PermissionSettings用于存储权限
mPermissionSettings = new PermissionSettings(mLock);
// 创建后台线程和Handler
mHandlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
Watchdog.getInstance().addThread(mHandler);
// 一个默认的授权策略类
mDefaultPermissionGrantPolicy = new DefaultPermissionGrantPolicy(
context, mHandlerThread.getLooper(), this);
// 2. 从SystemConfig中获取信息
// SystemConfig是一个单例,在创建的时候会解析系统的配置文件
SystemConfig systemConfig = SystemConfig.getInstance();
// 保存SystemConfig解析的<assign-permission>标签的内容
mSystemPermissions = systemConfig.getSystemPermissions();
// 保存SystemConfig解析的<group>标签的内容
mGlobalGids = systemConfig.getGlobalGids();
// 这里把SystemConfig解析的<permission>标签内容转移到了PermissionSettings中
final ArrayMap<String, SystemConfig.PermissionEntry> permConfig =
SystemConfig.getInstance().getPermissions();
synchronized (mLock) {
for (int i=0; i<permConfig.size(); i++) {
final SystemConfig.PermissionEntry permissionEntry = permConfig.valueAt(i);
BasePermission bp = mPermissionSettings.getPermissionLocked(permissionEntry.name);
if (bp == null) {
bp = new BasePermission(permissionEntry.name, "android", BasePermission.TYPE_BUILTIN);
mPermissionSettings.putPermissionLocked(permissionEntry.name, bp);
}
if (permissionEntry.gids != null) {
bp.setGids(permissionEntry.gids, permissionEntry.perUser);
}
}
}
// 3. 保存PermissionManagerService的内部接口
PermissionManagerServiceInternalImpl localService =
new PermissionManagerServiceInternalImpl();
// create()方法返回的值就是在这里设置的
LocalServices.addService(PermissionManagerServiceInternal.class, localService);
LocalServices.addService(PermissionManagerInternal.class, localService);
}
PermissionManagerService 的创建过程我分为了三步。
第三步保存了内部接口,就是刚才 create() 方法返回值。
第二步,是从 SystemConfig 中获取数据并保存,因此只要我们把 SystemConfig 分析完了,这里的代码就自然明了。
SystemConfig加载全局配置文件
SystemConfig 用来加载全局的配置文件信息,从构造函数中,就可以看到加载了哪些目录下的配置文件
SystemConfig() {
// Read configuration from system
// 读取/system/etc/sysconfig/目录
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);
// Vendors are only allowed to customize these
int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS
| ALLOW_ASSOCIATIONS;
if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.O_MR1) {
// For backward compatibility
vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS);
}
readPermissions(Environment.buildPath(
Environment.getVendorDirectory(), "etc", "sysconfig"), vendorPermissionFlag);
readPermissions(Environment.buildPath(
Environment.getVendorDirectory(), "etc", "permissions"), vendorPermissionFlag);
// Allow ODM to customize system configs as much as Vendor, because /odm is another
// vendor partition other than /vendor.
int odmPermissionFlag = vendorPermissionFlag;
readPermissions(Environment.buildPath(
Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
readPermissions(Environment.buildPath(
Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
// ...
// Allow OEM to customize these
int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS | ALLOW_ASSOCIATIONS;
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "sysconfig"), oemPermissionFlag);
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "permissions"), oemPermissionFlag);
// Allow Product to customize all system configs
readPermissions(Environment.buildPath(
Environment.getProductDirectory(), "etc", "sysconfig"), ALLOW_ALL);
readPermissions(Environment.buildPath(
Environment.getProductDirectory(), "etc", "permissions"), ALLOW_ALL);
// Allow /product_services to customize all system configs
readPermissions(Environment.buildPath(
Environment.getProductServicesDirectory(), "etc", "sysconfig"), ALLOW_ALL);
readPermissions(Environment.buildPath(
Environment.getProductServicesDirectory(), "etc", "permissions"), ALLOW_ALL);
}
SystemConfig 通过 readPermissions()
解析目录下的配置文件,而每个目录下有两个子目录用于保存配置文件,例如对于 /system
目录,有如下两个子目录可以保存配置文件,分别为 /system/etc/sysconfig
和 /system/etc/permissions
。这两个子目录有什么区别呢?其实并没有区别,这是历史原因造成的。最新的Android版本会把配置文件放到 sysconfig
目录下。
readPermissions()
方法的第一个参数表示配置文件所在的目录,第二个参数表示允许解析配置文件中的哪些配置。例如,对于 /system
分区下目录配置文件,是可以配置所有,而对于 /vendor
分区,有些是不能配置。那么到底哪些能配置,哪些不能配置,请自行分析代码。
现在来分析下 readPermission()
方法,看来如何解析配置文件
void readPermissions(File libraryDir, int permissionFlag) {
// ...
File platformFile = null;
for (File f : libraryDir.listFiles()) {
// ...
if (f.getPath().endsWith("etc/permissions/platform.xml")) {
platformFile = f;
continue;
}
// ...
readPermissionsFromXml(f, permissionFlag);
}
if (platformFile != null) {
readPermissionsFromXml(platformFile, permissionFlag);
}
}
从这里可以看出,readPermissionsFromXml()
用于读取配置文件。然而这里有一个非常有意思的事情,platform.xml
比较特殊,它是最后一个读取的。但是从代码的分析来看,读取顺序并没有什么关系,只是配置文件中的配置项不能重复而已。
readPermissionsFromXml()
其实就是解析 XML 的过程,这段代码我不分析,但是我会总结下,哪些数据结构保存了哪些标签,以及标签有什么用。
-
mGlobalGids
保存<group>
标签内容。<group>
标签,我查阅了所有配置文件,没有看到使用的地方。 -
mPermissions
保存<persmission>
标签内容。例如<permission name="android.permission.INTERNET" > <group gid="inet" /> </permission>
<permission>
标签定义了权限名到gid
的映射,这有什么用呢?如果一个应用想使用网络,只需要在AndroidManifest.xml中添加网络权限<uses-permission android:name="android.permission.INTERNET"/>
即可。 -
mSystemPermissions
保存<assign-permission>
标签内容。例如<assign-permission name="android.permission.INTERNET" uid="shell" />
赋予了shell进程访问网络的能力。
-
mSharedLibraries
保存<library>
标签内容。这是定义系统库的地方。 -
mAllowInPowerSave
保存<allow-in-power-save>
标签内容。例如<allow-in-power-save package="com.android.providers.downloads" />
它允许应用在省电模式下使用网络,甚至app的后台也可以使用网络。
-
mAllowInPowerSaveExceptIdle
保存allow-in-power-save-except-idle
标签的内容。例如<allow-in-power-save-except-idle package="com.android.providers.calendar" />
那些能在省电模式下使用网络的应用,在Doze模式下(Do Not Disturbe模式)下,就不能使用网络。
-
mAllowInDataUsageSave
保存allow-in-data-usage-save
标签内容。例如<allow-in-data-usage-save package="com.android.providers.downloads" />
允许应用在省流量开启的情况下,也能使用网络,甚至应用在后台也可以。
-
mAllowImplicitBroadcasts
保存allow-implicit-broadcast
标签内容。例如<allow-implicit-broadcast action="android.intent.action.SIM_STATE_CHANGED" />
Android O+之后 ,限制了在AndroidManifest.xml注册的广播接收器接收后台广播。通过这个标签可以突破限制。
-
mHiddenApiPackageWhitelist
保存了<hidden-api-whitelisted-app>
标签的内容。例如<hidden-api-whitelisted-app package="com.android.launcher3" />
它定义了哪些应用可以使用隐藏api。
OK,现在我们已经明白了 SystemConfig 保存了哪些数据,那么 PermissionManagerService 中从 SystemConfig 获取数据的操作是一目了然,这里就不再回去分析那段代码了。
全局配置文件来自哪里
通常这些配置文件来自于frameworks/base/data/etc/
目录,通过Android.bp
把配置文件编译到相应的位置。但是有些系统应用,它会在自己的Android.bp
或Android.mk
文件中,把自己的配置文件编译到相应的目录下。
保存共享进程的信息
现在来继续分析 PKMS 构造函数
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// 1. 初始化
// ...
// 2. 保存共享进程的信息
// 这是保存系统进程的信息
// 第一个参数表示进程的名字
// 第二个参数表示进程的uid值
// 第三个参数ApplicationInfo.FLAG_SYSTEM表示系统进程
// 第四个参数ApplicationInfo.PRIVATE_FLAG_PRIVILEGED表示进程是拥有特权
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);
mSettings.addSharedUserLPw("android.uid.se", SE_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.networkstack", NETWORKSTACK_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
}
这一步,是保存系统进程的信息,然而这些进程是可以被系统应用共享的,共享的方式是在设置sharedUserId
属性,例如 Settings APK 就设置了这个属性
<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"
android:sharedUserId="android.uid.system">
因此 Settings APK 是运行在名为 android.uid.system
进程中。
现在来看下 Settings#addSharedUserLPw()
是如何保存这些可以被共享的进程的信息的
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
SharedUserSetting s = mSharedUsers.get(name);
if (s != null) {
if (s.userId == uid) {
return s;
}
return null;
}
s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
s.userId = uid;
if (registerExistingAppIdLPw(uid, s, name)) {
// 以进程名为key保存共享进程的信息
mSharedUsers.put(name, s);
return s;
}
return null;
}
private boolean registerExistingAppIdLPw(int appId, SettingBase obj, Object name) {
if (appId > Process.LAST_APPLICATION_UID) {
return false;
}
if (appId >= Process.FIRST_APPLICATION_UID) {
int size = mAppIds.size();
final int index = appId - Process.FIRST_APPLICATION_UID;
while (index >= size) {
mAppIds.add(null);
size++;
}
if (mAppIds.get(index) != null) {
return false;
}
// mAppIds保存非系统进程的信息
mAppIds.set(index, obj);
} else {
if (mOtherAppIds.get(appId) != null) {
return false;
}
// mOtherAppIds保存系统进程的信息
mOtherAppIds.put(appId, obj);
}
return true;
}
这里我们可以看到用了两个数据结构保存了可共享的系统进程
ArrayMap<String, SharedUserSetting> mSharedUsers
用于保存可共享进程的信息,以进程名为key。SparseArray<SettingBase> mOtherAppIds
用于保存系统进程的信息,以进程的uid为key。
注意这两个数据结构保存的侧重点,mSharedUsers
针对系统进程,并且还要可共享 ,mOtherAppIds
只针对系统进程,并不要求可共享。
同时我们还可以注意到 ArrayList<SettingBase> mAppIds
用于保存非系统应用进程的信息,这个我们会在后面分析中用到。
保存共享库的信息
现在继续分析PKMS构造函数
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// 1. 初始化
// 2. 保存共享进程信息
// ...
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
// ...
// 3. 保存共享库信息到 mSharedLibraries 中
ArrayMap<String, SystemConfig.SharedLibraryEntry> libConfig
= systemConfig.getSharedLibraries();
final int builtInLibCount = libConfig.size();
for (int i = 0; i < builtInLibCount; i++) {
String name = libConfig.keyAt(i);
SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i);
// 系统配置文件中声明的<library>库,用mSharedLibraries保存
addBuiltInSharedLibraryLocked(entry.filename, name);
}
// 既然已经知道所有的库,现在就为库添加依赖库
long undefinedVersion = SharedLibraryInfo.VERSION_UNDEFINED;
for (int i = 0; i < builtInLibCount; i++) {
String name = libConfig.keyAt(i);
SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i);
final int dependencyCount = entry.dependencies.length;
for (int j = 0; j < dependencyCount; j++) {
final SharedLibraryInfo dependency =
getSharedLibraryInfoLPr(entry.dependencies[j], undefinedVersion);
if (dependency != null) {
getSharedLibraryInfoLPr(name, undefinedVersion).addDependency(dependency);
}
}
}
}
这一步是把全局配置文件中的 <library>
信息保存到 mSharedLibraries
中,你可以参照下面的配置自行分析上面的代码
<library name="android.test.mock"
file="/system/framework/android.test.mock.jar"
dependency="android.test.base" />
检测首先开机
让我们继续分析PackageManagerService
的构造函数。
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// 1. 初始化
// 2. 保存共享进程信息
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
// 3. 保存共享库的信息
// 4. 检测是否首次开机
mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));
}
}
从 mFirstBoot
这个名子可以推断出,这里是判断是否首次开机,但是并不完全是这样,看下 Settings#readLPw()
boolean readLPw(@NonNull List<UserInfo> users) {
// ....
try {
if (fileInputStream == null) { // 这个判断表示packages-backup.xml不存在
if (!mSettingsFilename.exists()) { // 这个判断表示packages.xml不存在
// 把存储的信息重置为默认值,这个后面会用到
findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent();
findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent();
// 从这里可以看出,如果packages-backup.xml和packages.xml都不存在,那么就代表是首次开机
return false;
}
// 走到这里表示不是首次开机,那么后面就是读取packages.xml数据
fileInputStream = new FileInputStream(mSettingsFilename);
}
// ...
int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
// 省略解析的代码...
}
str.close();
}
// ...
return true;
}
我们分析系统源码的时候,目的性一定要明确,这样可以帮我简化分析,避免走入死胡同。既然是通过返回值检测是否是首次开机,那么我们只关心那些return的语句。从这里就可以看出,如果/data/system/pacakges.xml
和/data/system/pacakges-backup.xml
都不存在,那么就是首次开机。
为何是这样呢?当首次开机完毕,系统会把解析APK的数据写入到packages.xml
中。如果再次重启手机,系统会先把packages.xml
重命名为packages-backup.xml
以留作备份,然后把把解析的数据写入到packages.xml
中,如果写入成功了,就把packages-backup.xml
删除。因此如果packages-backup.xml
存在,那么就表示上一次系统保存数据时出错了,那也就证明不是第一次开机。同理,如果packages.xml
存在,那么证明上一次系统也保存了一次数据 ,这也不是第一次开机。现在你应该明白为何用这两个文件是否存在来判断是否是首次开机了吗?
另外还有一点值得提一下,如果不是首次开机,那么会读取pacakges.xml
文件,并填充Settings
中的数据结构,这与系统升级过程紧密相连。然而本文只关心首次开机过程,至于升级过程,看完本篇文章,可自行分析, 因此后面我会省略升级过程的代码。
到此,PKMS的第一阶段,扫描前的准备工作已经分析完毕,我将在后面一篇文章中分析扫描目录并解析APK的过程。