本文共 32851 字,大约阅读时间需要 109 分钟。
PackageManagerService 系列文章如下(基于 Android 9.0 源码)
?
? ? ? ? ? ?修改日志
1、重新梳理 Android 9.0 源码中 PMS 的启动流程;
2、重新梳理涉及 Settings 的代码逻辑; 3、重新梳理 XML 文件扫描的代码逻辑; 4、博文格式,文章排版等优化;如果你真正的深入分析过 PackageManagerService,你会发现 PackageManagerService 的源码真的很大,而且逻辑、结构都甚为复杂。对 PackageManagerService 系列的源码分析是基于 Android 8.1的,我们知道随着 Android 版本的迭代,代码逻辑面目全非,分析起来的难度很大。为什么这么说?因为笔者也是从头开始分析,也想找一些大神所写的博文作为参考!但是!!!搜遍了各大技术博客,未能找到一篇较新的关于 PackageManagerService 的分析文章。即便是基于老版本(如 Android 5.1、6.0)的 PackageManagerService 分析,也没有一篇内容详尽,由里到外的全面深入分析之作,整个篇幅都是粘贴大量源码。所以导致笔者基本上是快速滑动滚轮,然后尽快的点击小叉叉。当然不敢说这个系列的文章能够完全将 PackageManagerService 分析到位。但痛恨所有文章千篇一律,苦于学习源码的痛苦,更怕半途而废,事倍功半,所以决定要分析就干到底!秉承这样的装逼精神,我们开始 PMS 的分析之旅吧!
关键类 | 路径 /frameworks/base/ |
---|---|
Process.java | core/java/android/os/Process.java |
Settings.java | services/core/java/com/android/server/pm/Settings.java |
SettingBase.java | services/core/java/com/android/server/pm/SettingBase.java |
SystemConfig.java | core/java/com/android/server/SystemConfig.java |
SystemServer.java | services/java/com/android/server/SystemServer.java |
SharedUserSetting.java | services/core/java/com/android/server/pm/SharedUserSetting.java |
PackageManagerService.java | services/core/java/com/android/server/pm/PackageManagerService.java |
PackageManagerService(PMS)是 SystemServer 启动后的第一个核心服务,也是 Android 系统中最常用的服务之一。它负责系统中 Package 的管理,应用程序的安装、卸载、信息查询等。如果你是面向 Android 系统开发的工程师,基础概念我也不需要再多赘述,我们的重点是阅读分析源码,钻研原理的奥秘。
首先,我们看一下 PackageManagerService 及客户端的家族谱,如下图所示(这边暂且只需要有个印象,整个系列分析完再回来看这个家族谱,你会清晰很多!)
简单说明:
? IPackageManager 接口类中定义了服务端和客户端通信的业务函数,还定义了内部类 Stub,该类从 Binder 派生并实现了 IPackageManager 接口。
? PackageManagerService 继承自 IPackageManager.Stub类,由于 Stub 类从 Binder 派生,因此 PackageManagerService 将作为服务端参与 Binder 通信。
? Stub 类中定义了一个内部类 Proxy,该类有一个 IBinder类型(实际类型为 BinderProxy)的成员变量 mRemote,mRemote 用于和服务端 PackageManagerService通信。
? IPackageManager 接口类中定义了许多业务函数,但是处于安全等方面的考虑,Android 对外(即SDK)提供的只是一个子集,该子类被封装在抽象类 PackageManager中。客户端一般通过 Context 的 getPackageManager 函数返回一个类型为 PackageManager的对象,该对象的实际类型是 PackageManager 的子类 ApplicationPackageManager。这种基于接口编程的方式,虽然极大降低了模块之间的耦合性,却给代码分析带来了不小的麻烦。
? ApplicationPackageManager 类继承自 PackageManager类。它并没有直接参与 Binder 通信,而是通过 mPM 成员变量指向一个 IPackageManager.Stub.Proxy 类型的对象。
【提示】:源码中可能找不到 IPackageManager.java 文件。该文件是在编译过程中产生的,最终的文件位于 Android 源码 /out(out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/core/java/android/content/pm)目录下面。
看下 IPackageManager.java:
public interface IPackageManager extends android.os.IInterface { /** Local-side IPC implementation stub class. */ // 定义内部类 Stub,派生自 Binder,实现 IPackageManager 接口 public static abstract class Stub extends android.os.Binder implements android.content.pm.IPackageManager { private static final java.lang.String DESCRIPTOR = "android.content.pm.IPackageManager"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } ... ... // 定义 Stub 的内部类 Proxy,实现 IPackageManager 接口 private static class Proxy implements android.content.pm.IPackageManager { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } ... ... } ... ... } ... ...}
看源码,梳理流程,就像是走进迷宫,很容易迷失!所以在开始分析之前,我给读者一些建议:紧抓主干,熟悉流程,再去啃细枝末节!!!
先来说说 PackageManagerService 是怎么启动的:PackageManagerService
作为系统的核心服务,由 SystemServer
创建,SystemServer 调用了 PackageManagerService 的 main()
创建 PackageManagerService 实例(关于 SystemServer 的分析可以阅读)。
// 源码路径:frameworks/base/services/java/com/android/server/SystemServer.java/** * The main entry point from zygote. */public static void main(String[] args) { new SystemServer().run();}private void run() { // Start services. try { startBootstrapServices(); startOtherServices(); ... ...}
// 源码路径:frameworks/base/services/java/com/android/server/SystemServer.javaprivate PackageManagerService mPackageManagerService;private Context mSystemContext;private boolean mOnlyCore;private void startBootstrapServices() { ... ... // 启动 installer 服务 Installer installer = mSystemServiceManager.startService(Installer.class); // 处于加密状态则仅仅解析核心应用 String cryptState = SystemProperties.get("vold.decrypt"); if (ENCRYPTING_STATE.equals(cryptState)) { Slog.w(TAG, "Detected encryption in progress - only parsing core apps"); mOnlyCore = true; } else if (ENCRYPTED_STATE.equals(cryptState)) { Slog.w(TAG, "Device encrypted - only parsing core apps"); mOnlyCore = true; } // 调用 PMS 的 main 函数,主要是创建 PMS 服务,并注册到 ServiceManager(服务管家) mPackageManagerService = PackageManagerService.main(mSystemContext, installer, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); // 判断本次是否为初次启动,当 Zygote 或 SystemServer 退出时,init 会再次启动它们 // 所以这里的 FirstBoot 是指开机后的第一次启动 mFirstBoot = mPackageManagerService.isFirstBoot(); // 获取 PackageManager mPackageManager = mSystemContext.getPackageManager(); ... ...}
// 源码路径:frameworks/base/services/java/com/android/server/SystemServer.javaprivate void startOtherServices() { ... ... traceBeginAndSlog("MakePackageManagerServiceReady"); mPackageManagerService.systemReady(); traceEnd(); ... ...}
通过源码,我们知道了 SystemServer 调用 PackageManagerService 的 main()
创建了 PackageManagerService 实例。那么接下来的重点就是关注 PackageManagerService 的 main()
!
// 源码路径:frameworks/base/services/core/java/com/android/server/pm/ PackageManagerService.javapublic static PackageManagerService main(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { // Self-check for initial settings: 此处主要检查系统属性 PackageManagerServiceCompilerMapping.checkProperties(); // 此处创建构造函数,其中,factoryTest:决定是否测试版本,onlyCore:决定是否只解析系统目录 PackageManagerService m = new PackageManagerService(context, installer, factoryTest, onlyCore); m.enableSystemUserPackages(); // 利用 Binder 通信,将自己注册到 ServiceManager 进程中(这是 Binder 服务的常规注册流程) ServiceManager.addService("package", m); final PackageManagerNative pmn = m.new PackageManagerNative(); ServiceManager.addService("package_native", pmn); return m;}
该方法主要创建 PMS 对象
,并将其注册到 ServiceManager
中,内部是一个 HashMap
的集合,存储了很多相关的 Binder 服务,缓存起来,我们在使用的时候,会通过 getService(key)
的方式去 Map
中获取。
main 函数看似几行代码很简单,但执行时间却很长。主要原因是 PMS 在其“构造函数”中做了很多“重体力活”,这也是 Android 启动速度慢的主要原因之一。
具体分析前,我们先简单了解一下 PMS 构造函数的主要功能:
扫描 Android 系统中几个目标文件夹中的 APK,从而建立合适的数据结构来管理各种信息,如:Package 信息、四大组件信息、权限信息等。
抽象地来看,PMS 像一个加工厂,它解析实际的物理文件(APK文件)以生成符合自己要求的产品。(例如:PMS 将解析 APK 包中的 AndroidManifest.xml
,并根据其中声明的 Activity 标签
来创建与此对应的对象并加以保管。)
从源码角度来看,PMS 的工作流程相对简单。但深入研究后,发现其很复杂!
复杂的是其中用于保存各种信息的数据结构
和它们之间的关系
,以及影响最终结果的策略控制
。
如果你自行研究过 PMS,你会发现代码中存在大量不同的数据结构以及它们之间的关系会让人大为头疼。所以,在这篇文章中我们除了分析 PMS 的工作流程以外,会重点关注重要的数据结构以及它们的作用。
接下来开始重点分析 PMS 的构造函数,如果放在一篇文章中去分析是完全不可能梳理清楚的!
我们分两部分研究,如下:
? 构造函数(1) - 前期准备工作 <font color=#FF0000>(本篇文章要讨论的内容)
? 构造函数(2) - 扫描 Package 和 扫尾工作正式开始分析 PMS 的构造函数:
// 此处创建构造函数,其中,factoryTest:决定是否测试版本,onlyCore:决定是否只解析系统目录PackageManagerService m = new PackageManagerService(context, installer, factoryTest, onlyCore);
// public static final int SDK_INT = SystemProperties.getInt("ro.build.version.sdk", 0);final int mSdkVersion = Build.VERSION.SDK_INT;public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { /* * mSdkVersion是 PMS 的成员变量,定义的时候进行赋值,其值取自系统属性 ro.build.version.sdk * 如果没有定义,则 APK 就无法知道自己运行在 Android 哪个版本上 */ if (mSdkVersion <= 0) { Slog.w(TAG, "**** ro.build.version.sdk not set!"); } mContext = context; mFactoryTest = factoryTest; // 运行在非工厂模式下 mOnlyCore = onlyCore; // 标记是否只加载核心服务 mMetrics = new DisplayMetrics(); // 存储与显示屏相关的一些属性,例如屏幕的宽/高尺寸,分辨率等信息 mInstaller = installer; // 创建 Installer 对象,该对象和 Native 进程 installd 交互 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); mPermissionManager = PermissionManagerService.create(context, new DefaultPermissionGrantedCallback() { @Override public void onDefaultRuntimePermissionsGranted(int userId) { synchronized(mPackages) { mSettings.onDefaultRuntimePermissionsGrantedLPr(userId); } } }, mPackages /*externalLock*/); mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy(); // Settings 是一个非常重要的类,该类用于存储系统运行过程中的一些设置,我们后面会重点分析这个类! mSettings = new Settings(mPermissionManager.getPermissionSettings(), mPackages); } } // 添加system、phone、log、nfc、bluetooth、shell这六种 shareUserId 到 mSettings // addSharedUserLPw 函数做了什么?这是我们接下来要分析的重点! 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); ... ...}
刚进入构造函数,我们就遇到了第一个较为复杂的数据结构 Settings
,以及它的 addSharedUserLPw
函数。
3.2.1 构造函数
public final class Settings { ... ... Settings(PermissionSettings permissions, Object lock) { this(Environment.getDataDirectory(), permissions, lock); } Settings(File dataDir, PermissionSettings permission, Object lock) { mLock = lock; mPermissions = permission; mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock); // 创建指向 /data/system/ 目录的 File 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); // 用于描述系统所安装的 Package 信息 mSettingsFilename = new File(mSystemDir, "packages.xml"); // packages.xml的备份信息 mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml"); // 保存系统中存在的所有非系统自带的 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; // 记录系统中被强制停止运行的 App 信息,如有 App 被强制停止运行,会将一些信息记录到该文件中 mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml"); // packages-stopped.xml 的备份信息 mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); } ... ...}
Settings 的构造函数的主要工作:建立与某些系统配置文件、目录之间的关联。首先,它会创建指向 /data/system/
目录的 File
实例,这个目录下会保存很多系统文件。其次,就是创建 /data/system/
目录下的某些 .xml 文件
或其他文件的 File
实例。
上面源码中涉及到 5 个文件:
✨ packages.xml: PMS 扫描完目标文件夹后,会创建packages.xml。当系统进行程序安装、卸载和更新等操作时,均会更新该文件;
✨ packages-backup.xml:packages.xml 文件的备份;✨ packages.list:用于描述系统中存在的所有非系统自带的 APK 信息。当这些 APK 有变化时,PKMS就会更新该文件;
✨ packages-stopped.xml:记录被用户强行停止的应用的 Package 信息(例如,从设置进入某个应用,然后点击强行停止,那么应用的Package信息就会被记录);
✨ packages-stopped-back.xml:packages-stopped.xml 文件的备份。
我们注意到,上面的介绍中涉及到了两个back-up
文件,它们是做什么的呢?其实 Android 系统在修改packages.xml
、packages-stopped.xml
之前,会先对它们进行备份
。当对它们的修改操作正常完成,则会删掉备份的文件。如果在修改过程中系统出现问题重启了,会再次去读取这两个文件;如果此时发现它们的备份文件还存在,则说明上一次对两份文件的修改操作发生了异常,这两份文件的内容可能已经不准确了,这时系统会去使用之前备份的文件的内容。
创建完相关系统文件 File 实例后,Settings 的构造工作也就结束了。
之前我们提出了一个问题:addSharedUserLPw 函数做了什么?从上面截取一段代码回顾一下:
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
addSharedUserLPw 传递了 4 个参数:
✨ android.uid.system:字符串,name 和 uid 一一对应
public static final int SYSTEM_UID = 1000;
public static final int FLAG_SYSTEM = 1<<0;
public static final int PRIVATE_FLAG_PRIVILEGED = 1<<3;
对 addSharedUserLPw 函数分析之前,我们有必要了解 SYSTEM_UID
的相关知识。
UID 为 用户 ID
的缩写,GID 为 用户组 ID
的缩写。一般来说,每一个进程都会有一个对应的 UID(即标示该进程属于哪个用户,不同用户拥有不同权限)。一个进程也可分属不用的用户组(每个用户都有对应的权限)。UID/GID 和进程的权限有关。
在 Android 平台中,系统定义的 UID/GID 在 Process.java 文件中,如下所示(列举部分):
// 源码路径:frameworks/base/core/java/android/os/Process.javapublic static final int SYSTEM_UID = 1000; // 系统进程的 UID/GIDpublic static final int PHONE_UID = 1001; // Phone 进程的 UID/GIDpublic static final int SHELL_UID = 2000; // shell 进程的 UID/GIDpublic static final int LOG_UID = 1007; // LOG 进程的 UID/GIDpublic static final int WIFI_UID = 1010; // WIFI 进程的 UID/GIDpublic static final int MEDIA_UID = 1013; // mediaserver 进程的 UID/GIDpublic static final int NFC_UID = 1027; // NFC 进程的 UID/GIDpublic static final int FIRST_APPLICATION_UID = 10000;// 第一个应用 Package 的起始 UIDpublic static final int LAST_APPLICATION_UID = 19999; // 系统所支持的最大的应用 Package 的 UID
现在我们开始分析 addSharedUserLPw 函数:
// 源码路径:frameworks/base/services/core/java/com/android/server/pm/Settings.java SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) { // 根据 key 从 map 中获取值 SharedUserSetting s = mSharedUsers.get(name); // 如果值不为 null 并且保存的 uid 和传递过来的一致,就直接返回结果 // uid 不一致则返回 null if (s != null) { if (s.userId == uid) { return s; } PackageManagerService.reportSettingsProblem(Log.ERROR, "Adding duplicate shared user, keeping first: " + name); return null; } // 若 s 为 null,则根据传递过来的参数新创建对象 s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags); s.userId = uid; // 在系统中保存值为 uid 的 用户 id,成功返回 true if (addUserIdLPw(uid, s, name)) { mSharedUsers.put(name, s); // 将 name 与 s 键值对添加到 mSharedUsers 中保存 return s; } return null;}
从源码中我们发现,Settings 中有一个 mSharedUsers
成员,该成员存储的是 【“字符串” 与 “SharedUserSetting” 键值对】
,也就是说可以通过 字符串
为 key
得到对应的 SharedUserSetting
对象。
那么 SharedUserSetting 是什么?创建它的目的是什么?接下来我们继续分析!
3.2.3.1 SharedUserSetting
为了解释 SharedUserSetting,我们拿 SystemUI 作为例子来讨论这个问题。
我们看下 SystemUI 的 AndroidManifest.xml
(这个文件你肯定不陌生):
在 AndroidManifest.xml
中,声明了一个名为 android:sharedUserId
的属性:android.uid.systemui
。
有必要聊聊这个 "sharedUserId" 的作用!
1、两个或多个声明了同一种 sharedUserId
的 APK
可共享彼此的数据,并且可运行在同一进程中。
2、通过声明特定的 sharedUserId
,该 APK
所在 进程
将被赋予指定的 UID
(比如本例中的 SystemUI 声明了 system 的 uid,运行 SystemUI 的进程就可享受 system 用户所对应的权限)。
除了在AndroidManifest.xml
中声明sharedUserId
外,APK 在编译时还必须使用对应的证书进行签名。例如本例的SystemUI
,在其Android.mk
中需要额外申明LOCAL_CERTIFICATE := platform
,如此才可以获得指定的UID
(当然这个不是我们分析的重点,在项目开发的过程中,我们会了解到这一点)。
通过以上分析,我们知道了如何组织一种数据结构来包括上面的内容。
有 3 个关键点需要注意:
? XML 中 sharedUserId 属性指定了一个字符串,它是 UID 的字符串描述,故对应数据结构中也应该有这样一个字符串,这样就把代码和 XML 中的属性联系起来了。 ? 在 LINUX 系统中,真正的 uid 是一个整数,所以该数据结构中必然有一个整型变量。 ? 多个 Package 可声明同一个 sharedUserId,因此该数据结构必然会保存那些声明了相同 sharedUserId 的 Package 的某些信息。
对 SharedUserSetting 我们做个总结:
1、Settings
类定义了一个 mSharedUsers
成员,它是一个 ArrayMap
,以 字符串
(如:android.uid.system)为 key
,对应的 Value
是一个 SharedUserSetting
对象。
final ArrayMapmSharedUsers = new ArrayMap ();
2、SharedUserSetting
定义了一个成员变量 packages
,类型为 ArraySet
,用于保存声明了相同 sharedUserId
的 Package
的权限设置信息(这一点我们之前提到过)。
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 ArraySetpackages = new ArraySet (); ... ...}
3、每个 Package
有自己的权限设置。权限的概念由 PackageSeting
类表达。该类继承自 PackageSettingBase
类,PackageSettingBase
又继承自 SettingBase
。
public final class PackageSetting extends PackageSettingBase {}public abstract class PackageSettingBase extends SettingBase {}
SettingBase
对象持有 PermissionsState
对象,用于表示可用的权限。
abstract class SettingBase { protected final PermissionsState mPermissionsState;}
4、Settings
中还有两个成员,一个是 mUserIds
,另一个是 mOtherUserIds
,这两位成员的类型分别是 ArrayList
和 SparseArray
。其目的是以 UID
为索引,得到对应的 SharedUserSeting
对象。在一般情况下,以 索引
获取数组元素
的速度,比以 Key
获取 ArrayMap
中 元素
的速度要快很多。
private final ArrayList
3.2.3.2 addUserIdLPw
我们回忆一下 addSharedUserLPw 方法:
// 源码路径:frameworks/base/services/core/java/com/android/server/pm/Settings.java SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) { SharedUserSetting s = mSharedUsers.get(name); ... ... s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags); s.userId = uid; if (addUserIdLPw(uid, s, name)) { mSharedUsers.put(name, s); return s; } return null;}
源码中还有一个 addUserIdLPw
方法,它的功能就是将 SharedUserSettings
对象保存到对应的数组中,代码如下:
// 源码路径:frameworks/base/services/core/java/com/android/server/pm/Settings.javaprivate boolean addUserIdLPw(int uid, Object obj, Object name) { // 系统所支持的最大的应用 Package 的 UID,不能超出限制 19999 if (uid > Process.LAST_APPLICATION_UID) { return false; } // 第一个应用 Package(非系统安装应用)的起始 UID if (uid >= Process.FIRST_APPLICATION_UID) { // 获取数组的长度 int N = mUserIds.size(); // 计算索引,其值是 uid 和 FIRST_APPLICATION_UID 的差 final int index = uid - Process.FIRST_APPLICATION_UID; while (index >= N) { mUserIds.add(null); N++; } // 如果数组的目标索引值位置有不为 null 的值,说明已经添加过 if (mUserIds.get(index) != null) { PackageManagerService.reportSettingsProblem(Log.ERROR, "Adding duplicate user id: " + uid + " name=" + name); return false; } // 应用 Package 的 uid 由 mUserIds 保存 mUserIds.set(index, obj); } else { if (mOtherUserIds.get(uid) != null) { PackageManagerService.reportSettingsProblem(Log.ERROR, "Adding duplicate shared id: " + uid + " name=" + name); return false; } // 系统 Package 的 uid 由 mOtherUserIds 保存 mOtherUserIds.put(uid, obj); } return true;}
至此对 Settings 的分析我们暂时告一段落。
我们来看一个 SharedUserSettings 的类图:(类名改变,后期重新补图)
如上图所示,Settings
对象中持有多个 SharedUserSetting
对象,每个 SharedUserSetting
对象又会持有多个 PackageSetting
对象。
从继承关系来看,SharedUserSetting
和 PackageSetting
对象,最终都将继承 SettingBase
对象。
从图上可以看出,SettingBase
对象持有 PermissionsState
对象,用于表示可用的权限。
因此,SharedUserSetting
对象和 PackageSetting
对象中都将包含有 PermissionsState
。
从而我们可以据此推测出,SharedUserSetting
中持有的是一组 Package
共有的权限;PackageSetting
中持有的是单个 Package
独有的权限。
分析完 PMS 构造函数前期工作的第一阶段后,接下来就要继续回到构造函数中分析剩下的代码:
// 源码:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.javapublic PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { ... ... // 第一阶段 // 该值和调试有关,一般不设置该属性 String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { if ("*".equals(separateProcesses)) { mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES; mSeparateProcesses = null; Slog.w(TAG, "Running with debug.separate_processes: * (ALL)"); } else { mDefParseFlags = 0; mSeparateProcesses = separateProcesses.split(","); Slog.w(TAG, "Running with debug.separate_processes: " + separateProcesses); } } else { mDefParseFlags = 0; mSeparateProcesses = null; } // 对应用进行 dexopt 优化的辅助类 mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context, "*dexopt*"); DexManager.Listener dexManagerListener = DexLogger.getListener(this, installer, mInstallLock); mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock); mArtManagerService = new ArtManagerService(mContext, this, installer, mInstallLock); mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); mOnPermissionChangeListeners = new OnPermissionChangeListeners(FgThread.get().getLooper()); // 获取当前设备的显示屏信息 getDefaultDisplayMetrics(context, mMetrics); // 通过 SystemConfig 读取系统的 feature、permession 等配置, // 并初始化 mGlobalGids/mAvailableFeatures 成员 SystemConfig systemConfig = SystemConfig.getInstance(); // 重点讨论 mAvailableFeatures = systemConfig.getAvailableFeatures(); mProtectedPackages = new ProtectedPackages(mContext); ... ... }
以上代码除了创建了几个对象以外,还有一个重要的需要关注的类:SystemConfig,这就是我们接下来分析的重点!!!
我们先来分析 SystemConfig systemConfig = SystemConfig.getInstance() 函数!
// 源码路径:frameworks/base/core/java/com/android/server/SystemConfig.java/** * Loads global system configuration info. */public class SystemConfig { static SystemConfig sInstance; ... ... public static SystemConfig getInstance() { // 单例模式 synchronized (SystemConfig.class) { if (sInstance == null) { sInstance = new SystemConfig(); } return sInstance; } } ... ...}
查看它的构造函数:
// 源码路径:frameworks/base/core/java/com/android/server/SystemConfig.java/** * 通过 readPermissions() 读取并解析 /system/etc/ 等目录下的 sysconfig.xml、permission.xml 文件 */SystemConfig() { // Read configuration from 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); // Vendors are only allowed to customze libs, features and privapp permissions int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS; 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 features and OEM permissions int oemPermissionFlag = ALLOW_FEATURES | ALLOW_OEM_PERMISSIONS; readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "sysconfig"), oemPermissionFlag); readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "permissions"), oemPermissionFlag); // Allow Product to customize system configs around libs, features, permissions and apps int productPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS; readPermissions(Environment.buildPath( Environment.getProductDirectory(), "etc", "sysconfig"), productPermissionFlag); readPermissions(Environment.buildPath( Environment.getProductDirectory(), "etc", "permissions"), productPermissionFlag);}
我们发现 SystemConfig
的构造函数所做的工作就是:readPermissions()
,即从文件中读取权限
!
接下来我们看看 readPermissions 的源码:
// 源码路径:frameworks/base/core/java/com/android/server/SystemConfig.java 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 // 处理该目录下的非 platform.xml 文件 if (f.getPath().endsWith("etc/permissions/platform.xml")) { platformFile = f; continue; } ... ... // 调用 readPermissionsFromXml 解析此 XML 文件 readPermissionsFromXml(f, permissionFlag); } // Read platform permissions last so it will take precedence if (platformFile != null) { // 不知道你有没有发现,platform.xml文件的解析优先级最高! readPermissionsFromXml(platformFile, permissionFlag); }}
从源码中,我们发现 readPermissions
函数不就是调用 readPermissionFromXml
函数解析 "/xxx/etc/permissions/"
目录下的文件吗?
这些文件似乎都是 XML 文件。你也许有个疑问?该目录下都有哪些 XML 文件?这些 XML 文件中有些什么内容呢?以我手中的 pixel 为例:
sailfish:/system/etc/permissions $ ls -alls -altotal 168drwxr-xr-x 2 root root 4096 2009-01-01 16:00 .drwxr-xr-x 14 root root 4096 2009-01-01 16:00 ..-rw-r--r-- 1 root root 1050 2009-01-01 16:00 android.software.live_wallpaper.xml-rw-r--r-- 1 root root 748 2009-01-01 16:00 android.software.webview.xml-rw-r--r-- 1 root root 1778 2009-01-01 16:00 com.android.ims.rcsmanager.xml-rw-r--r-- 1 root root 828 2009-01-01 16:00 com.android.location.provider.xml-rw-r--r-- 1 root root 828 2009-01-01 16:00 com.android.media.remotedisplay.xml-rw-r--r-- 1 root root 820 2009-01-01 16:00 com.android.mediadrm.signer.xml-rw-r--r-- 1 root root 158 2009-01-01 16:00 com.android.omadm.service.xml-rw-r--r-- 1 root root 435 2009-01-01 16:00 com.android.sdm.plugins.connmo.xml-rw-r--r-- 1 root root 701 2009-01-01 16:00 com.android.sdm.plugins.sprintdm.xml-rw-r--r-- 1 root root 234 2009-01-01 16:00 com.android.vzwomatrigger.xml-rw-r--r-- 1 root root 1079 2009-01-01 16:00 com.customermobile.preload.vzw.xml-rw-r--r-- 1 root root 850 2009-01-01 16:00 com.google.android.camera.experimental2016.xml-rw-r--r-- 1 root root 563 2009-01-01 16:00 com.google.android.dialer.support.xml-rw-r--r-- 1 root root 816 2009-01-01 16:00 com.google.android.maps.xml-rw-r--r-- 1 root root 835 2009-01-01 16:00 com.google.android.media.effects.xml-rw-r--r-- 1 root root 811 2009-01-01 16:00 com.google.vr.platform.xml-rw-r--r-- 1 root root 160 2009-01-01 16:00 com.verizon.apn.xml-rw-r--r-- 1 root root 158 2009-01-01 16:00 com.verizon.embms.xml-rw-r--r-- 1 root root 288 2009-01-01 16:00 com.verizon.llkagent.xml-rw-r--r-- 1 root root 174 2009-01-01 16:00 com.verizon.provider.xml-rw-r--r-- 1 root root 220 2009-01-01 16:00 com.verizon.services.xml-rw-r--r-- 1 root root 239 2009-01-01 16:00 features-verizon.xml-rw-r--r-- 1 root root 811 2009-01-01 16:00 obdm_permissions.xml-rw-r--r-- 1 root root 8916 2009-01-01 16:00 platform.xml-rw-r--r-- 1 root root 23092 2009-01-01 16:00 privapp-permissions-google.xml-rw-r--r-- 1 root root 1346 2009-01-01 16:00 privapp-permissions-marlin.xml-rw-r--r-- 1 root root 20848 2009-01-01 16:00 privapp-permissions-platform.xml-rw-r--r-- 1 root root 1587 2009-01-01 16:00 vzw_mvs_permissions.xmlsailfish:/system/etc/permissions $
既然我们上面一直在说 platform.xml 这个文件,那就看下 platform.xml 有什么:
... ... ... ... ... ... ... ...
platform.xml 文件中主要使用了如下 4 个标签:
✨ permission 和 group 用于建立 Linux 层 gid 和 Andrid 层 permission 之间的映射关系。
✨ assign-permission 用于向指定的 uid 赋予相应的权限。这个权限由 Android 定义,用于字符串表示。
✨ library 用于指定系统库。当应用程序运行时,系统会自动为这些进程加载这些库。
不知道你是否已经产生了疑问?设备上的 /system/etc/permission 目录中的文件是从哪里来的?我直接告诉你答案:在编译阶段由不用硬件平台根据自己的配置信息复制相关文件到目标目录中的来的。(这个具体我们不讨论,有兴趣的读者可以自行查阅)
前面我们说过:readPermissions
函数其实就是调用 readPermissionFromXml
函数解析 "/xxx/etc/permissions/"
目录下的文件!
readPermissionFromXml 又有什么作用?其实它的作用就是将 XML 文件中的标签以及它们之间的关系转换成代码中的相应数据结构,直接看源码:
// 源码路径:frameworks/base/core/java/com/android/server/SystemConfig.java private void readPermissionsFromXml(File permFile, int permissionFlag) { FileReader permReader = null; ... ... final boolean lowRam = ActivityManager.isLowRamDeviceStatic(); try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(permReader); ... ... while (true) { ... ... String name = parser.getName(); // 解析 group 标签 if ("group".equals(name) && allowAll) { String gidStr = parser.getAttributeValue(null, "gid"); if (gidStr != null) { int gid = android.os.Process.getGidForName(gidStr); // 转换 XML 中的 gid 字符串为整型,并保存到 mGlobalGids中 mGlobalGids = appendInt(mGlobalGids, gid); } else { Slog.w(TAG, "without gid in " + permFile + " at " + parser.getPositionDescription()); } XmlUtils.skipCurrentTag(parser); continue; // 解析 permission标签 } else if ("permission".equals(name) && allowPermissions) { String perm = parser.getAttributeValue(null, "name"); if (perm == null) { ... ... XmlUtils.skipCurrentTag(parser); continue; } perm = perm.intern(); // 调用 readPermission 处理 readPermission(parser, perm); // 解析 assign-permission 标签 } else if ("assign-permission".equals(name) && allowPermissions) { String perm = parser.getAttributeValue(null, "name"); ... ... String uidStr = parser.getAttributeValue(null, "uid"); ... ... // 如果是 assign-permission,则取出 uid 字符串,然后获得 Linux 平台上的整型 uid 值 int uid = Process.getUidForName(uidStr); ... ... perm = perm.intern(); // 和 assign 相关的信息保存在 mSystemPermissions 中 ArraySet perms = mSystemPermissions.get(uid); if (perms == null) { perms = new ArraySet (); mSystemPermissions.put(uid, perms); } perms.add(perm); XmlUtils.skipCurrentTag(parser); // 解析 library 标签 } 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 { ... ... // 将 XML 中的 name 和 library 属性值存储到 mSharedLibraries 中 mSharedLibraries.put(lname, lfile); } XmlUtils.skipCurrentTag(parser); continue; // 解析 feature标签 } else if ("feature".equals(name) && allowFeatures) { String fname = parser.getAttributeValue(null, "name"); int fversion = XmlUtils.readIntAttribute(parser, "version", 0); boolean allowed; ... ... } else if ("unavailable-feature".equals(name) && allowFeatures) { ... ... // 解析其它标签 } ... ...}
readPermission 函数果然是将 XML 中的标签转换成对应的数据结构!
01.
02.转载地址:http://qzpox.baihongyu.com/