内容简介:如果你真正的深入分析过 PackageManagerService,你会发现 PackageManagerService 的源码真的很大,而且逻辑、结构都甚为复杂。对 PackageManagerService 系列的源码分析是基于 Android 8.1的,我们知道随着 Android 版本的迭代,代码逻辑面目全非,分析起来的难度很大。为什么这么说?因为笔者也是从头开始分析,也想找一些大神所写的博文作为参考!但是!!!搜遍了各大技术博客,未能找到一篇较新的关于 PackageManagerService
开篇
前言
如果你真正的深入分析过 PackageManagerService,你会发现 PackageManagerService 的源码真的很大,而且逻辑、结构都甚为复杂。对 PackageManagerService 系列的源码分析是基于 Android 8.1的,我们知道随着 Android 版本的迭代,代码逻辑面目全非,分析起来的难度很大。为什么这么说?因为笔者也是从头开始分析,也想找一些大神所写的博文作为参考!但是!!!搜遍了各大技术博客,未能找到一篇较新的关于 PackageManagerService 的分析文章。即便是基于老版本(如 Android 5.1、6.0)的 PackageManagerService 分析,也没有一篇内容详尽,由里到外的全面深入分析之作,整个篇幅都是粘贴大量源码。所以导致笔者基本上是快速滑动滚轮,然后尽快的点击小叉叉。当然不敢说这个系列的文章能够完全将 PackageManagerService 分析到位。但痛恨所有文章千篇一律,苦于学习源码的痛苦,更怕半途而废,事倍功半,所以决定要分析就干到底!秉承这样的装逼精神,我们开始 PMS 的分析之旅吧!
核心源码
关键类 | 路径 |
---|---|
SystemServer.java | frameworks/base/services/java/com/android/server/SystemServer.java |
PackageManagerService.java | frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java |
Process.java | frameworks/base/core/java/android/os/Process.java |
SystemConfig.java | frameworks/base/core/java/com/android/server/SystemConfig.java |
Settings.java | frameworks/base/services/core/java/com/android/server/pm/Settings.java |
SharedUserSetting.java | frameworks/base/services/core/java/com/android/server/pm/SharedUserSetting.java |
简介
PackageManagerService(PMS)是 SystemServer 启动后的第一个核心服务,也是 Android 系统中最常用的服务之一。它负责系统中 Package 的管理,应用程序的安装、卸载、信息查询等。如果你是面向 Android 系统开发的工程师,基础概念我也不需要再多赘述,我们的重点是分析源码。
家族谱
首先,我们看一下 PackageManagerService 及客户端的家族谱,如下图所示(这边只需要有个印象,等代码分析完再回来看这个家族谱你会清晰很多!!!):
简单分析一下:
:beginner: IPackageManager 接口类中定义了服务端和客户端通信的业务函数,还定义了内部类 Stub,该类从 Binder 派生并实现了 IPackageManager 接口。
:beginner: PackageManagerService 继承自 IPackageManager.Stub类,由于 Stub 类从 Binder 派生,因此 PackageManagerService 将作为服务端参与 Binder 通信。
:beginner: Stub 类中定义了一个内部类 Proxy,该类有一个 IBinder类型(实际类型为 BinderProxy)的成员变量 mRemote,mRemote 用于和服务端 PackageManagerService通信。
:beginner: IPackageManager 接口类中定义了许多业务函数,但是处于安全等方面的考虑,Android 对外(即SDK)提供的只是一个子集,该子类被封装在抽象类 PackageManager中。客户端一般通过 Context 的 getPackageManager 函数返回一个类型为 PackageManager的对象,该对象的实际类型是 PackageManager 的子类 ApplicationPackageManager。这种基于接口编程的方式,虽然极大降低了模块之间的耦合性,却给代码分析带来了不小的麻烦。
:beginner: 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; } ... ... } ... ... } ... ... }
当你已经迷惑了,觉得看不下去了,请继续坚持!!!看源码,梳理流程,就像是走进迷宫,很容易迷失!所以在开始分析之前,我给读者一些建议:紧抓主干,熟悉流程,再去啃细枝末节!!!
Read The Fucking Code
重头戏永远都是 Read Source Code !!!所以,接下来我们就开始分析 PackageManagerService,后面简称为 PMS!
PackageManagerService 启动源
先来说说 PackageManagerService 是怎么启动的: PackageManagerService 作为系统的核心服务,由 SystemServer 创建,SystemServer 调用了 PackageManagerService 的 main 函数 创建 PackageManagerService 实例。
源码如下:
// 源码路径:frameworks/base/services/java/com/android/server/SystemServer.java private void run() { // Start services. try { startBootstrapServices(); ... ... } private PackageManagerService mPackageManagerService; private Context mSystemContext; private boolean mOnlyCore; // 调用 startBootstrapServices private void startBootstrapServices() { ... ... Installer installer = mSystemServiceManager.startService(Installer.class); // 调用 PMS 的 main 函数 mPackageManagerService = PackageManagerService.main(mSystemContext, installer, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); // 判断本次是否为初次启动,当 Zygote 或 SystemServer 退出时,init 会再次启动它们,所以这里的 FirstBoot 是指开机后的第一次启动 mFirstBoot = mPackageManagerService.isFirstBoot(); mPackageManager = mSystemContext.getPackageManager(); ... ... }
上面这段代码很好理解,即调用 PackageManagerService 的 main 函数创建 PackageManagerService 实例,那么重点工作就要 继续跟踪这个 main 函数 。
核心main函数
// 源码路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java public static PackageManagerService main(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { // 此处主要检查系统属性 PackageManagerServiceCompilerMapping.checkProperties(); /* * 此处调用构造函数,其中factoryTest决定是否是测试版本,onlyCore决定是否只解析系统目录 * :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: * 这边的构造函数将是我们 PackageManagerService 分析的重点!!! * :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: */ PackageManagerService m = new PackageManagerService(context, installer, factoryTest, onlyCore); m.enableSystemUserPackages(); ServiceManager.addService("package", m); final PackageManagerNative pmn = m.new PackageManagerNative(); // 利用 Binder 通信,将自己注册到 ServiceManager 进程中 ServiceManager.addService("package_native", pmn); return m; }
main 函数看似几行代码很简单,但执行时间却很长。主要原因是 PMS 在其“构造函数”中做了很多“重体力活”,这也是 Android 启动速度慢的主要原因之一。
这边,我们先简单了解一下 PMS 构造函数的主要功能:
扫描 Android 系统中几个目标文件夹中的 APK,从而建立合适的数据结构以管理如 Package 信息、四大组件信息、权限信息等各种信息。抽象地看,PMS 像一个加工厂,它解析实际的物理文件(APK文件)以生成符合自己要求的产品。(例如,PMS 将解析 APK 包中的 AndroidManifest.xml,并根据其中声明的 Activity 标签来创建与此对应的对象并加以保管。)
从代码上看,PMS 的工作流程还是相对简单的。但是深入研究下去,你会发现很复杂! 复杂的是其中用于保存各种信息的数据结构和它们之间的关系,以及影响最终结果的策略控制 。如果你自行研究过 PMS,你会发现代码中存在大量不同的数据结构以及它们之间的关系会让人大为头疼。
所以,在这篇文章中我们除了分析 PMS 的工作流程以外,会重点关注重要的数据结构以及它们的作用。
接下来就要集中重点分析 PMS 的构造函数,如果放在一篇文章中去分析完 PMS 逻辑架构是完全不可能讲解清楚的!!!所以才有了系列一说,我们分段讨论,规划如下:
:maple_leaf: 深入研究 PackageManagerService 构造函数(1) - 前期准备工作 (本篇文章要讨论的内容)
:maple_leaf: 深入研究 PackageManagerService 构造函数(2) - 扫描Package
:maple_leaf: 深入研究 PackageManagerService 构造函数(3) - 扫尾工作
构造函数分析 - 前期准备工作
现在开始,我们就从源码角度深入剖析 PMS !直接上源码:
// 源码路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java public static PackageManagerService main(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { ... ... // 此处调用构造函数,其中factoryTest决定是否是测试版本,onlyCore决定是否只解析系统目录 PackageManagerService m = new PackageManagerService(context, installer, factoryTest, onlyCore); ... ... }
跟踪 PMS 的构造函数(第一阶段):
// 源码路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java // 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,即编译的 SDK 版本 * 如果没有定义,则 APK 就无法知道自己运行在 Android 哪个版本上 */ if (mSdkVersion <= 0) { Slog.w(TAG, "**** ro.build.version.sdk not set!"); // 打印警告 } mContext = context; mFactoryTest = factoryTest; // 假定为false,即运行在非工厂模式下 mOnlyCore = onlyCore; // 假定为false,即运行在普通模式下 // mMetrics 用于存储与显示屏相关的一些属性,例如屏幕的宽/高尺寸,分辨率等信息 mMetrics = new DisplayMetrics(); /* * :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: * Settings 是一个非常重要的类,该类用于存储系统运行过程中的一些设置,我们后面会重点分析这个类!!! * :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: */ mSettings = new Settings(mPackages); /* * :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: * addSharedUserLPw 函数做了什么?这是我们接下来要分析的重点!!! * :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: */ 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); // 第一阶段结束
What a fuck!!!刚进入构造函数,我们就遇到了第一个较为复杂的数据结构 Settings 及它的 addSharedUserLPw 函数。
初识Settings
上面我们提出了一个问题: addSharedUserLPw 函数做了什么? 接下来,准备开始分析 addSharedUserLPw 函数,从上面截取一段代码:
// 源码路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
从上面源码可以看到,我们为 addSharedUserLPw 传递了 4 个参数:
:sparkles: android.uid.system :字符串
:sparkles: Process.SYSTEM_UID :值为1000
:sparkles: ApplicationInfo.FLAG_SYSTEM :标志
:sparkles: ApplicationInfo.PRIVATE_FLAG_PRIVILEGED :特权Apk
在进入对 addSharedUserLPw 函数的正式分析之前,我们先介绍一下 SYSTEM_UID 的相关知识。
UID/GID介绍
UID 为 “用户ID” 的缩写,GID 为 “用户组ID” 的缩写。一般来说,每一个进程都会有一个对应的 UID(即标示该进程属于哪个用户,不同用户有不同权限)。一个进程也可分属不用的用户组(每个用户都有对应的权限)。
如上所述, UID/GID 和进程的权限有关 。
在 Android 平台中,系统定义的 UID/GID 在 Process.java 文件中,如下所示(列举部分):
// 源码路径:frameworks/base/core/java/android/os/Process.java /** * Defines the UID/GID under which system code runs. */ public static final int SYSTEM_UID = 1000; // 系统 进程使用的 UID/GID,值为1000 /** * Defines the UID/GID under which the telephony code runs. */ public static final int PHONE_UID = 1001; // Phone 进程使用的 UID/GID,值为1001 /** * Defines the UID/GID for the user shell. * @hide */ public static final int SHELL_UID = 2000; // shell 进程使用的 UID/GID,值为2000 /** * Defines the UID/GID for the log group. * @hide */ public static final int LOG_UID = 1007; // 使用 LOG 的进程所在的组的 UID/GID,值为1007 /** * Defines the UID/GID for the WIFI supplicant process. * @hide */ public static final int WIFI_UID = 1010; // 供WIFI 相关进程使用的 UID/GID,值为1010 /** * Defines the UID/GID for the mediaserver process. * @hide */ public static final int MEDIA_UID = 1013; // mediaserver 进程使用的 UID/GID,值为1013 /** * Defines the UID/GID for the NFC service process. * @hide */ public static final int NFC_UID = 1027; // NFC 相关的进程的 UID/GID,值为1027 /** * Defines the start of a range of UIDs (and GIDs), going from this * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning * to applications. */ public static final int FIRST_APPLICATION_UID = 10000; // 第一个应用 Package 的起始 UID 为 10000 /** * Last of application-specific UIDs starting at * {@link #FIRST_APPLICATION_UID}. */ public static final int LAST_APPLICATION_UID = 19999; // 系统所支持的最大的应用 Package 的 UID 为 19999
addSharedUserLPw
了解完 UID/GID,接下来就该分析 addSharedUserLPw 函数了!
// 源码路径:frameworks/base/services/core/java/com/android/server/pm/Settings.java // final ArrayMap<String, SharedUserSetting> mSharedUsers = new ArrayMap<String, SharedUserSetting>(); SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) { // mSharedUsers是一个 ArrayMap,key 为字符串,值为 SharedUserSetting 对象 SharedUserSetting s = mSharedUsers.get(name); if (s != null) { if (s.userId == uid) { return s; } ... ... return null; } /* * 创建一个新的 SharedUserSetting 对象,并设置 userId 为 uid * * :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: * SharedUserSetting 是什么?有什么作用?接下来我们也会重点讨论!!! * :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: :boom: * */ s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags); s.userId = uid; if (addUserIdLPw(uid, s, name)) { mSharedUsers.put(name, s); // 将 name 与 s 键值对添加到 mSharedUsers 中保存 return s; } return null; }
从以上代码我们看到,Settings中有一个 mSharedUsers 成员,该成员存储的是 【“字符串” 与 “SharedUserSetting” 键值对】 ,也就是说以字符串为 key 得到对应的 SharedUserSetting 对象。
那么 SharedUserSetting 是什么?创建它的目的是什么?接下来我们继续分析!
SharedUserSetting
为了解释 SharedUserSetting,我们拿 SystemUI 作为例子来讨论这个问题。
我们看下 SystemUI 的 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.systemui" android:sharedUserId="android.uid.system" coreApp="true">
在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个关键点需要注意:
:maple_leaf: XML 中 sharedUserId 属性指定了一个字符串,它是 UID 的字符串描述,故对应数据结构中也应该有这样一个字符串,这样就把代码和 XML 中的属性联系起来了。
:maple_leaf: 在 LINUX 系统中,真正的 uid 是一个整数,所以该数据结构中必然有一个整型变量。
:maple_leaf: 多个 Package 可声明同一个 sharedUserId,因此该数据结构必然会保存那些声明了相同 sharedUserId 的 Package 的某些信息。
以上三点现在只需要理解,可能你很迷茫,但是心里有数就行,通过下面慢慢分析再回头看就会清晰很多!!!
对 SharedUserSetting 我们做个总结:
:sparkles: Settings 类定义了一个 mSharedUsers 成员,它是一个 ArrayMap,以字符串(如:android.uid.system)为key,对应的 Value 是一个 SharedUserSetting 对象。
final ArrayMap<String, SharedUserSetting> mSharedUsers = new ArrayMap<String, SharedUserSetting>();
:sparkles: 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 ArraySet<PackageSetting> packages = new ArraySet<PackageSetting>();
:sparkles: 每个 Package 有自己的权限设置。权限的概念由 PackageSeting 类表达。该类继承自 PackageSettingBase 类,PackageSettingBase 又继承自 SettingBase。
public final class PackageSetting extends PackageSettingBase {} public abstract class PackageSettingBase extends SettingBase {}
:sparkles: Settings 中还有两个成员,一个是 mUserIds,另一个是 mOtherUserIds,这两位成员的类型分别是 ArrayList 和 SparseArray。其目的是以 UID 为索引,得到对应的 SharedUserSetings 对象。在一般情况下,以索引获取数组元素的速度,比以 Key 获取 ArrayMap 中元素的速度要快很多。
private final ArrayList<Object> mUserIds = new ArrayList<Object>(); private final SparseArray<Object> mOtherUserIds = new SparseArray<Object>();
What a fuck, again!!!
是不是看懵了?那我告诉你一个捷径: 对应源码多看几遍,你肯定能看懂!!!或者你也可以有个概念,继续下面的征程,等回头再来看,也是不错的方法!!!
addUserIdLPw
我们刚刚虽然搞了个插曲,分析了 SharedUserSetings 对象,但是大前提是我们在分析 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); if (s != null) { if (s.userId == uid) { return s; } ... ... return null; } s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags); s.userId = uid; if (addUserIdLPw(uid, s, name)) { mSharedUsers.put(name, s); return s; } return null; }
Ok,记忆找回!那么下面就要继续分析源码中的 addUserIdLPw 函数了,它的功能就是将 SharedUserSettings 对象保存到对应的数组中,代码如下:
// 源码路径:frameworks/base/services/core/java/com/android/server/pm/Settings.java private boolean addUserIdLPw(int uid, Object obj, Object name) { // uid 不能超出限制 if (uid > Process.LAST_APPLICATION_UID) { // 系统所支持的最大的应用 Package 的 UID return false; } if (uid >= Process.FIRST_APPLICATION_UID) { // 第一个应用 Package 的起始 UID int N = mUserIds.size(); // 计算索引,其值是 uid 和 FIRST_APPLICATION_UID 的差 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 保存应用 Package 的 uid 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 的分析我们暂时告一段落。除了重点分析了 UID/GID 以及 SharedUserId 方面的知识,还见识了几个重要的数据结构,希望你能通过 SystemUI 的实例能够理解这些数据结构存在的目的。
XML 文件扫描
分析完 PMS 构造函数前期工作的第一阶段后,接下来就要继续回到构造函数中分析剩下的代码:
// 源码路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java public 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)) { ... ... } else { mDefParseFlags = 0; mSeparateProcesses = separateProcesses.split(","); } } else { mDefParseFlags = 0; mSeparateProcesses = null; } // 创建一个 Installer 对象,该对象和 Native 进程 installd 交互,以后分析 installd 时再来讨论它的作用 mInstaller = installer; ... ... // 获取当前设备的显示屏信息 getDefaultDisplayMetrics(context, mMetrics); SystemConfig systemConfig = SystemConfig.getInstance(); :boom::boom::boom::boom: // 接下来重点关注的函数 mGlobalGids = systemConfig.getGlobalGids(); mSystemPermissions = systemConfig.getSystemPermissions(); mAvailableFeatures = systemConfig.getAvailableFeatures(); mProtectedPackages = new ProtectedPackages(mContext); synchronized (mInstallLock) { // writer synchronized (mPackages) { // 创建一个 HandlerThread 对象,实际就是创建一个带消息循环处理的线程,该线程的工作是:程序的安装和卸载等,后面分析程序安装时会跟它亲密接触 mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); mHandlerThread.start(); // 以 HandlerThread 线程的消息循环(Looper对象)为参数创建一个 PackageHandler,该 Handler 的 handleMessage 函数将运行在此线程上 mHandler = new PackageHandler(mHandlerThread.getLooper()); ... ... File dataDir = Environment.getDataDirectory(); // mAppInstallDir 指向 /data/data 目录 mAppInstallDir = new File(dataDir, "app"); mAppLib32InstallDir = new File(dataDir, "app-lib"); mAsecInternalPath = new File(dataDir, "app-asec").getPath(); // mDrmAppPrivateInstallDir 指向 /data/app-private 目录 mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); // 创建一个 UserManager 对象,支持多个 User,每个 User 将安装自己的应用 sUserManager = new UserManagerService(context, this, new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages); ... ... mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false)); :boom::boom::boom::boom: // 接下来重点关注的函数
Ok,我们又列出了一部分源码。可见以上代码除了创建了几个对象以外,还有两个重要的需要关注的函数,这也是我们接下来分析的重点!!!
SystemConfig
我们先来分析 SystemConfig systemConfig = SystemConfig.getInstance() 函数!
// 源码路径:frameworks/base/core/java/com/android/server/SystemConfig.java public static SystemConfig getInstance() { synchronized (SystemConfig.class) { if (sInstance == null) { sInstance = new SystemConfig(); } return sInstance; } }
查看它的构造函数:
// 源码路径:frameworks/base/core/java/com/android/server/SystemConfig.java 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); // Allow Vendor to customize system configs around libs, features, permissions and apps int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | 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 around libs, features and apps 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 readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES); readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES); }
我们发现个很神奇的现象, 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 函数解析 "/system/etc/permissions" 目录下的文件吗?
这些文件似乎都是 XML 文件。你也许有个疑问?该目录下都有哪些 XML 文件?这些 XML 文件中有些什么内容呢? 以我手中的 pixel 为例 :
sailfish:/system/etc/permissions $ ls -al ls -al total 168 drwxr-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.xml sailfish:/system/etc/permissions $
既然我们上面一直在说 platform.xml 这个文件,那就看下 platform.xml 包含什么东东:
<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 的映射关系。如下面声明的 BLUETOOTH_ADMIN 权限,它对应的用户组是 net_bt_admin。 注意,该文件中的 permission 标签只对那些需要通过读写设备(蓝牙/cameta)/创建 socket 等进程划分了 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> <permission name="android.permission.BLUETOOTH_STACK" > <group gid="bluetooth" /> <group gid="wakelock" /> <group gid="uhid" /> </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 为 audioserver,那么就 赋予它 WAKE_LOCK 的权限,其实就是把它加到对应的用户组中 --> ... ... <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="audioserver" /> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="audioserver" /> <assign-permission name="android.permission.WAKE_LOCK" uid="audioserver" /> <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" /> <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" /> ... ... <!-- This is a list of all the libraries available for application code to link against. --> <!-- 系统提供的 Java 库,应用程序运行时必须要链接这些库,该工作由系统自动完成 --> <library name="android.test.mock" file="/system/framework/android.test.mock.jar" /> <library name="android.test.runner" file="/system/framework/android.test.runner.jar" /> <library name="javax.obex" file="/system/framework/javax.obex.jar" /> <library name="org.apache.http.legacy" file="/system/framework/org.apache.http.legacy.jar" /> ... ... </permissions>
platform.xml 文件中主要使用了如下 4 个标签 :
:sparkles: permission 和 group 用于建立 Linux 层 gid 和 Andrid 层 permission 之间的映射关系。
:sparkles: assign-permission 用于向指定的 uid 赋予相应的权限。这个权限由 Android 定义,用于字符串表示。
:sparkles: library 用于指定系统库。当应用程序运行时,系统会自动为这些进程加载这些库。
不知道你是否已经产生了疑问?设备上的 /system/etc/permission 目录中的文件是从哪里来的?我们直接告诉你答案:在编译阶段由不用硬件平台根据自己的配置信息复制相关文件到目标目录中的来的。(这个具体我们不讨论,有兴趣的读者可以自行查阅)
XML分析到此为止!接下来继续跟源码... ...
readPermissionFromXML
前面我们说过: readPermissions 函数其实就是调用 readPermissionFromXml 函数解析 "/system/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, "<group> 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<String> perms = mSystemPermissions.get(uid); if (perms == null) { perms = new ArraySet<String>(); 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 中的标签转换成对应的数据结构!!!具体不在深入,后面可能我还会回来补充!!!
readLPw
readLPw 函数的功能也是解析文件,不过这些文件的内容却是在 PMS 正常启动之后生成的。我们这边仅仅简单的介绍一下与 readLPW 相关的文件信息。文件的具体位置在 Settings 构造函数中指明:
Settings(File dataDir, Object lock) { mLock = lock; mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock); mSystemDir = new File(dataDir, "system"); // 指向 /data/system 目录 mSystemDir.mkdirs(); // 创建该目录 FileUtils.setPermissions(mSystemDir.toString(), FileUtils.S_IRWXU|FileUtils.S_IRWXG |FileUtils.S_IROTH|FileUtils.S_IXOTH, -1, -1); /* * 一共 new 了 6个 文件 * packages.xml 和 packages-backup.xml 为一组,用于描述系统中所安装的 Package 信息,其中 backup 是临时文件。 * PMS 先把数据写到 backup 中,信息都写成功后再改名成非 backuo 的文件。其目的是防止在写文件过程中出错,导致信息丢失。 * packages-stopped.xml 和 packages-stopped-backup.xml 为一组,用于描述系统中强制停止运行的 Package 的信息,backup也是临时文件。 * 如果此处存在临时文件,表明此前系统因为某种原因中断了正常流程。 * packages.list 列出了当前系统中应用级 Package 的信息。 */ mSettingsFilename = new File(mSystemDir, "packages.xml"); mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml"); mPackageListFilename = new File(mSystemDir, "packages.list"); FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID); final File kernelDir = new File("/config/sdcardfs"); mKernelMappingFilename = kernelDir.exists() ? kernelDir : null; // Deprecated: Needed for migration mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml"); mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); }
【小结】
:sparkles: packages.xml :PMS 扫描完目标文件夹后会创建该文件。当系统进行程序安装、卸载和更新等操作时,均会更新该文件。该文件保存了系统中与 Package 相关的一些信息。
:sparkles: packages.list :描述系统中存在的所有非系统自带的 APK 的信息。当这些程序有变动时,PMS 就会更新该文件。
:sparkles: packages-stopped.xml :从系统自带的设置程序中进入应用程序页面,然后在选择强制停止某个应用时,系统会将该应用的相关信息记录到此文件中。也就是该文件保存系统中被用户强制停止的 Package 的信息。
readLPw 的函数功能就是解析其中的 XML 文件的内容,然后建立并更新对应的数据结构,例如停止的 Package 重启之后依然是 stopped 状态。
第一阶段工作总结
所做工作:扫描并解析 XML 文件,将其中的信息保存到特定的数据结构中。
第一阶段扫描的 XML 文件与权限及上一次扫描的到的 Package 信息有关,它为 PMS 下一阶段的工作提供了重要的参考信息。
后续内容
本篇文章开启了 PackageManagerService 的源码分析征程!已经分析的部分也仅仅是 PMS 构造函数的一部分,所以后面会有一系列的文章补充,但是每篇文章都会循序渐进的详细分析,争取梳理清楚 PMS 的整体架构(基于 Android 8.1源码)。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 深入理解 FilterChainProxy【源码篇】
- 深入理解 WebSecurityConfigurerAdapter【源码篇】
- 深入koa2源码
- 深入理解channel:设计+源码
- 深入浅出Semaphore源码解析
- 深入剖析Vue源码 - 组件基础
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Dreamweaver CS3 Bible
Joseph W. Lowery / Wiley / May 21, 2007 / $49.99
Book Description Learn to create dynamic, data-driven Web sites using the exciting enhancements in the Dreamweaver CS3 version. You get a thorough understanding of the basics and then progress to l......一起来看看 《Dreamweaver CS3 Bible》 这本书的介绍吧!