PKMS 分析.md

PKMS 的分析从两个角度来看, 一个是Client进程另一个是Server进程:
在IPackageManager.aidl文件中, 申明了所有Client和Server之间有业务交流的接口, 在经过编译后会生成一个面向Server端的Stub 类,Stub 所实现的onTransact()函数就是用于响应来自Client端的远程调用; 同时还会生成一个Stub.Proxy 类是面向Client端的.

其中Stub.Proxy会实现所有的在AIDL文件中定义的接口, 但是实际的实现逻辑却不在Proxy内, 它实际的工作只是序列化了客户端传过来的参数, 然后通过mRemoteBinder对象的transact()函数把序列化后的参数发送出去;
通过Binder驱动, 把参数回调到了Stub的onTransact()函数, 进而真正的调用到在Server端实现的逻辑函数, 此时完成了从Client–>Server的函数调用. 上述的流程总结就是client通过获得一个server的代理接口,通过代理对server进行接口调用。
那么Server端拿到了结果, 如果把结果返回给Client的呢? 也就是Server–>Client的流程.
在client端调用代理接口的时候,不仅传入的是参数列表的序列化对象, 还传入了一个_result用于存储调用结果的序列化坑位对象, 而调用transact()是一个同步的过程, 只要阻塞结束, client就可以从_result中取出调用结果了.

所以从Client调用一次远程binder通信, 流程可以理解为如下:
Client需要持有一个AIDL文件生成的Proxy对象, 持有的方式一般是通过绑定远程Service获得的, 具体如下:
bindService()传入的connection对象在建立起连接后会返回一个IBinder对象, 可以通过调用IInterface.Stub.asInterface(Ibinder)函数把Ibinder对象转化成一个Stub.Proxy对象. 接着客户端就可以利用这个proxy对象调用远程接口. 客户端在通过proxy.callRemoteFunc(params)的时候,

在SystemServer的run()内, 会对一些服务执行初始化操作, 其中包括了PKMS, 直接调用PKMS的入口函数main(), 调用自己的构造函数并添加到系统服务中去, 接着SystemServer会调用PKMS的优化dex包的函数performBootDexOpt() 目的是为了提升运行的效率; 最后SystemServer会调用PKMS的systemReady()函数, 告知系统进入就绪状态。

PKMS的构造函数中执行一些比较重的work, 诸如启动了一个扫描指定文件夹的操作:

1
2
3
4
5
6
7
8
9
10
11
12
synchronized (mInstallLock) {
synchronized (mPackages) {
...
File dataDir = Environment.getDataDirectory();
mAppDataDir = new File(dataDir, "data");//指向/data/data目录
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");//指向/data/user目录
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");//指向/data/app-private目录
}
}

接着就是获取permissions的操作, 简述流程就是: 遍历…/etc/permissions文件夹, 解析此文件下的所有XML文件, 根据各个权限的标签(gid, library(对应的是一个个的jar文件路径), feature)进行了归类, 最终存储于map结构中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void readPermissions() {
// 从.../etc/permission文件下读取权限列表
File libraryDir = new File(Environment.getRootDirectory(), "etc/permissions");
if (!libraryDir.exists() || !libraryDir.isDirectory()) {
Slog.w(TAG, "No directory " + libraryDir + ", skipping");
return;
}
if (!libraryDir.canRead()) {
Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
return;
}
// Iterate over the files in the directory and scan .xml files
for (File f : libraryDir.listFiles()) {
// We'll read platform.xml last
if (f.getPath().endsWith("etc/permissions/platform.xml")) {
continue;
}
if (!f.getPath().endsWith(".xml")) {
Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
continue;
}
if (!f.canRead()) {
Slog.w(TAG, "Permissions library file " + f + " cannot be read");
continue;
}
readPermissionsFromXml(f);
}
// Read permissions from .../etc/permissions/platform.xml last so it will take precedence
final File permFile = new File(Environment.getRootDirectory(),
"etc/permissions/platform.xml");
readPermissionsFromXml(permFile);
}

接着就是扫描各个package的内容了, 优先处理的是系统级别的dex包了, 首先遍历上述的mSharedLibrarires结构, 它存储了系统级别的库文件. 通过调用mInstaller.dexopt(lib, Process.SYSTEM_UID, true) 函数执行优化系统库文件(实际上是通过执行 dexopt apkpath uid boolean 命令完成jar的优化的. ). 紧接着是framework目录下的所有jar包也会被执行一次dexopt操作.
紧接着就是把/system/frameworks(系统库) /system/app(系统apk) /vendor/app(第三方厂商apk) 三个系统包路径下调用scanDirLI()–> scanPackageLI() 方法了, 通过一个PackageParser对象来生成一个parsed的package对象, 完成了从物理文件(apk)到内存映射数据结构的转换过程, 这个过程主要是解析apk的root路径下的manifest.xml文件, parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME) 说明了解析的文件就是Manifest.xml, 读取出所有的manifest内的数据. 包括Provider/Service/Activity/Permission 都是把对应的标签内的信息映射到实际的数据结构中来.

1
2
3
4
5
6
7
8
9
10
int N = pkg.providers.size();
N = pkg.services.size();
N = pkg.receivers.size();
N = pkg.activities.size();
N = pkg.permissionGroups.size();
N = pkg.permissions.size();
N = pkg.instrumentation.size();
for(int i=0;i<N;i++){
mActivities/mReceivers/mServices...put(array[i]);
}

以上的简化代码就是遍历每一个Manifest文件中的标签数据并转化成一个个的Service(Activity)IntentResolver对象,实现了物理文件转内存里的数据结构. 所以可见整个流程可以理解成, scanDirLI()遍历系统的所有apk/JAR文件夹路径, 然后找到每一个单独的文件通过scanPackageLI()来解析单个的apk文件(其实就是解析Manifest.xml), 取出Manifest.xml中所有有用的信息, 最终暴露出每一个package对象的数据。