PMS(PackageManagerService)是 Android 提供的包管理系统服务,它用来管理所有的包信息,包括应用安装、卸载、更新以及解析 AndroidManifest.xml。通常情况下我们不会把 PMS 单独的拆分出来讲解,因为 PMS 最主要的是提供给 AMS(ActivityManagerService)服务。
你是否有考虑过为什么我们手机开启启动时会很慢?这是因为 在手机启动时 PMS 会在这段时间处理 apk 解析,至少有 70% 的启动时间耗费在 PMS 解析上,所以这也是为什么手机开机启动比较慢的原因之一。
从解析的角度上,可以理解为 PMS 保存了后续提供给 AMS 所需要的数据,它是具有保存应用数据的缓存。
当手机开机的时候,系统启动 PMS 后会去扫描两个目录,分别是存放用户安装的 apk 的目录 /data/app 以及系统安装的 apk 的目录 /system/app。
刚才有提到,PMS 是为了给 AMS 服务的,那 PMS 需要提供哪些数据呢?为什么需要 AndroidManifest.xml?
我们都知道 AndroidManifest.xml 定义了apk 中所有的四大组件、权限等等信息,它是一个定义文件。PMS 对 apk 的解析最主要的就是去扫描到 /data/app 和 /system/app 目录下的 apk 文件,找到 apk 包中的 AndroidManifest.xml,然后解析 AndroidManifest.xml 的信息保存到系统内存中,这样 AMS 在需要应用数据时,就能找到 PMS 快速的从内存中拿到相关信息。
如果没有 AndroidManifest.xml,PMS 的解析就是要保存每个 apk 中所有的类文件信息,这个数据量是庞大的,而且解析也会很慢,手机启动速度更慢。
在 Android 系统所有的核心服务都会经过 SystemServer 启动,PMS 也不例外,SystemServer 会在手机开机时启动运行。关于 SystemServer 是如何启动的可以查看文章 Zygote。
SystemServer.java
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
...
try {
...
startBootstrapServices();
startCoreServices();
startOtherServices();
...
}
...
}
private void startBootstrapServices() {
...
// 启动 AMS
mActivityManagerService = mSystemServiceManager.startService(
ActivityManagerService.Lifecycle.class).getService();
mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
mActivityManagerService.setInstaller(installer);
...
// 启动 PMS
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
mFirstBoot = mPackageManagerService.isFirstBoot();
mPackageManager = mSystemContext.getPackageManager();
...
}
PackageManagerService.java
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
// 创建自己的实例
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
m.enableSystemUserPackages();
// 将 PMS 添加到 ServiceManager,AMS 找 PMS 拿数据时就是通过 ServiceManager 找到 PMS
ServiceManager.addService("package", m);
final PackageManagerNative pmn = m.new PackageManagerNative();
ServiceManager.addService("package_native", pmn);
return m;
}
当 SystemServer 被 Zygote 启动调用了 main() 方法时,执行了 SystemServer 的 run() 方法启动一些核心服务,例如先启动了 AMS 后再启动了 PMS,将 AMS 和 PMS 添加到 ServiceManager,由 ServiceManager 管理这些服务。
ServiceManager 只提供了 addService() 和 getService() 方法,当 app 进程需要获取到对应的系统服务,都会通过 ServiceManager 拿到相应服务的 Binder 代理,使用 Binder 通信获取数据:
例如在 Application、Activity 等地方调用 getPackageManager() 时:
ContextWrapper.java
@Override
public PackageManager getPackageManager() {
// mBase 是 ContextImpl
return mBase.getPackageManager();
}
ContextImpl.java
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
ActivityThread.java
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
//Slog.v("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager;
}
// 通过 ServiceManager 拿到 PMS
IBinder b = ServiceManager.getService("package");
//Slog.v("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b); // binder 通信
//Slog.v("PackageManager", "default service = " + sPackageManager);
return sPackageManager;
}
PMS 的处理流程简单理解就是手机开机时会去扫描两个目录 /data/app 和 /system/app,去解析这两个目录的 apk 文件的 AndroidManifest.xml 生成应用的摘要信息保存为 Java Bean 到内存。
PackageManagerService.java
// data/app 目录
private static final File sAppInstallDir =
new File(Environment.getDataDirectory(), "app");
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
...
// /system/app 目录
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
// 扫描 /system/app 目录下的 apk 文件
scanDirTracedLI(systemAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM,
0);
...
// 扫描 /data/app 目录下的 apk 文件
scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
...
}
private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
scanDirLI(scanDir, parseFlags, scanFlags, currentTime);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = scanDir.listFiles();
...
try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
mParallelPackageParserCallback)) {
// Submit files for parsing in parallel
int fileCount = 0;
for (File file : files) {
// 判断是否是 .apk 后缀的文件
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
// 添加到子线程交给 PackageParser 解析 apk 文件
parallelPackageParser.submit(file, parseFlags);
fileCount++;
}
...
}
}
ParallelPackageParser.java
public void submit(File scanFile, int parseFlags) {
mService.submit(() -> {
ParseResult pr = new ParseResult();
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
try {
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);
pp.setCacheDir(mCacheDir);
pp.setCallback(mPackageParserCallback);
pr.scanFile = scanFile; // 传入待解析的 apk 文件
// 交给 packageParser 解析 apk
pr.pkg = parsePackage(pp, scanFile, parseFlags);
} catch (Throwable e) {
pr.throwable = e;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
...
});
}
protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
int parseFlags) throws PackageParser.PackageParserException {
return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
}
PackageParser.java
public static final String APK_FILE_EXTENSION = ".apk";
public static final boolean isApkFile(File file) {
return isApkPath(file.getName());
}
public static boolean isApkPath(String path) {
return path.endsWith(APK_FILE_EXTENSION);
}
从源码可以看到,PMS 其实就是去扫描 /data/app/ 和 /system/app/ 两个目录下的 apk,判断目录下的文件是否是 apk 也只是简单的判断文件后缀是否是 .apk。然后通过 PackageParser 开始解析 apk。
需要注意的是,在不同的系统源码版本解析的方式也不相同,在 6.0、7.0、8.0 版本启动解析的方式还是直接解析的,但在 10.0 版本开始使用线程池放到子线程去解析,加快了手机启动速度。
根据上面的分析,apk 的解析最终是交给 PackageParser,继续查看是如何解析的:
PackageParser.java
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
// 如果有缓存,直接返回解析后的信息
Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
if (parsed != null) {
return parsed;
}
...
// apk 文件不是目录,所以会走的 parseMonolithicPackage()
if (packageFile.isDirectory()) {
parsed = parseClusterPackage(packageFile, flags);
} else {
parsed = parseMonolithicPackage(packageFile, flags);
}
...
}
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
...
final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
try {
// 解析 apk
final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
pkg.setCodePath(apkFile.getCanonicalPath());
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} catch (IOException e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to get path: " + apkFile, e);
} finally {
IoUtils.closeQuietly(assetLoader);
}
}
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
...
// 开始 dom 解析 AndroidManifest.xml
XmlResourceParser parser = null;
try {
final int cookie = assets.findCookieForPath(apkPath);
...
// ANDROID_MANIFEST_FILENAME 就是 AndroidManifest.xml
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
...
final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
...
return pkg;
} catch (PackageParserException e) {
throw e;
} catch (Exception e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to read manifest from " + apkPath, e);
} finally {
IoUtils.closeQuietly(parser);
}
}
private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {
final String splitName;
final String pkgName;
try {
Pair<String, String> packageSplit = parsePackageSplitNames(parser, parser);
pkgName = packageSplit.first; // 拿到包名
splitName = packageSplit.second;
...
}
...
// 后续的流程就是将 xml 解析的信息如权限、四大组件等信息存到 Package
final Package pkg = new Package(pkgName);
...
return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
}
public final static class Package implements Parcelable {
// 包名
public String packageName;
...
// 申请的权限
public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
// 四大组件
public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
public final ArrayList<Service> services = new ArrayList<Service>(0);
...
}
上面的源码其实很好理解,就是根据传过来的 apk 文件路径先拿到 AndroidManifest.xml,然后开始进行 dom 解析 xml 文件,将不同的标签数据信息存放在 Package 类的不同字段,例如 权限信息、四大组件信息等,将它们都解析好存放到内存中,方便后续 AMS 找到 PMS 拿数据。
在 9.0 版本开始解析结果默认会开启缓存,如果有缓存则直接返回解析后的结果信息,否则就解析每个 apk 文件的 AndroidManifest.xml:
ParallelPackageParser.java
protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
int parseFlags) throws PackageParser.PackageParserException {
// 开启缓存
return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
}
PackageParser.java
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
// 如果有缓存,直接返回
Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
if (parsed != null) {
return parsed;
}
...
}
以上就是 PMS 的 apk 解析流程,简单说就是提前将 AMS 要用的数据信息先解析存到内存,方便能快速定位到 Activity 等信息。
当我们在应用商店下载安装应用或使用 adb install 时也是走的上述的解析过程。
再简单总结下 PMS 的 apk 解析流程:
手机系统启动,Zygote 启动 SystemServer,SystemServer 启动 AMS、PMS,并注册到 ServiceManager
PMS 扫描 /data/app/ 和 /system/app/ 目录下的所有 apk 文件,获取每个 apk 文件的 AndroidManifest.xml 文件,并进行 dom 解析
解析 AndroidManifest.xml 将权限、四大组件等数据信息转换为 Java Bean 记录到内存中
当 AMS 需要获取 apk 数据信息时,通过 ServiceManager 获取到 PMS 的 Binder 代理通过 Binder 通信获取
了解了 PMS 解析 apk 的流程,我们可以根据原理 hook 实现动态装载的功能,使用 PackageParser 将网络下载的一个 apk 文件自己手动解析,然后通过反射添加到 PMS 的内存,实现动态装载功能。
下面的 demo 实现了一个简单的动态加载功能,将一个外部 apk 文件的广播添加到 PMS 的缓存中。
首先准备需要动态添加的广播,该广播放在外部 apk 文件 hook-debug.apk:
public class HookReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("HookReceiver", "hook receiver receive message");
// 给宿主广播发消息
Intent sendIntent = new Intent();
sendIntent.setAction("com.example.demo.main");
context.sendBroadcast(sendIntent);
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hook">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Demo" >
<!-- 清单文件也需要添加广播注册,PackageParser 动态加载时需要使用 -->
<receiver android:name=".HookReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.example.demo.hook" />
</intent-filter>
</receiver>
</application>
</manifest>
将外部 apk 打包出来后,为了方便演示,demo 是将 apk 导入到 cache 目录。接下来是动态解析:
public class HookPackageParser {
public void parse(Context context, File apkFile) throws Exception {
Class<?> packageParserClazz = Class.forName("android.content.pm.PackageParser");
Method parsePackageMethod = packageParserClazz.getDeclaredMethod("parsePackage", File.class, int.class);
parsePackageMethod.setAccessible(true);
Object packageParserObj = packageParserClazz.newInstance();
// 调用 PackageParser.parsePackage() 获取到解析后的 Package
Object packageObj = parsePackageMethod.invoke(packageParserObj, apkFile, PackageManager.GET_RECEIVERS);
// 获取 receivers 成员变量
Field receiversField = packageObj.getClass().getDeclaredField("receivers");
List receivers = (List) receiversField.get(packageObj);
DexClassLoader dexClassLoader = new DexClassLoader(
apkFile.getAbsolutePath(),
context.getDir("plugin", Context.MODE_PRIVATE).getAbsolutePath(),
null,
context.getClassLoader());
Class<?> componentClazz = Class.forName("android.content.pm.PackageParser$Component");
Field intentsField = componentClazz.getDeclaredField("intents");
for (Object receiverObj : receivers) {
String name = (String) receiverObj.getClass().getField("className").get(receiverObj);
try {
BroadcastReceiver hookReceiver = (BroadcastReceiver) dexClassLoader.loadClass(name).newInstance();
List<? extends IntentFilter> filters = (List<? extends IntentFilter>) intentsField.get(receiverObj);
for (IntentFilter filter : filters) {
context.registerReceiver(hookReceiver, filter);
}
} catch (Exception e) {
// ignore
}
}
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkPermission(this);
// 注册一个用于接收 hook 广播发送的消息验证是否动态装载了外部 apk 的广播
IntentFilter filter = new IntentFilter();
filter.addAction("com.example.demo.main");
registerReceiver(new MainReceiver(), filter);
}
private boolean checkPermission(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && activity.checkSelfPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
activity.requestPermissions(new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 1);
}
return false;
}
// 先 hook 外部 apk 的 HookReceiver
public void hookReceiver(View view) {
HookPackageParser packageParser = new HookPackageParser();
File directory = getCacheDir();
String path = directory.getAbsolutePath() + "/hook-debug.apk";
File file = new File(path);
if (!file.exists()) {
throw new RuntimeException("hook apk no exist");
}
try {
packageParser.parse(this, file);
} catch (Exception e) {
e.printStackTrace();
}
}
// hook 后尝试发送广播看是否生效
public void sendBroadcast(View view) {
Intent intent = new Intent();
intent.setAction("com.example.demo.hook");
sendBroadcast(intent);
}
private static class MainReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("MainReceiver", "receive hook receiver message");
}
}
}
PackageParser 需要通过反射获取,再反射调用它的 parsePackage() 传入 apk 路径完成解析获取到 Package 对象,再反射 PMS 的 activities、providers、receivers、services 变量,将我们解析的数据添加进去,这样就实现了动态加载。