文章目录
什么是 PMS
PMS(PackageManagerService)是 Android 提供的包管理系统服务,它用来管理所有的包信息,包括应用安装、卸载、更新以及解析 AndroidManifest.xml。通常情况下我们不会把 PMS 单独的拆分出来讲解,因为 PMS 最主要的是提供给 AMS(ActivityManagerService)服务。
你是否有考虑过为什么我们手机开启启动时会很慢?这是因为 在手机启动时 PMS 会在这段时间处理 apk 解析,至少有 70% 的启动时间耗费在 PMS 解析上,所以这也是为什么手机开机启动比较慢的原因之一。
从解析的角度上,可以理解为 PMS 保存了后续提供给 AMS 所需要的数据,它是具有保存应用数据的缓存。
AndroidManifest.xml 的作用
当手机开机的时候,系统启动 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 中所有的类文件信息,这个数据量是庞大的,而且解析也会很慢,手机启动速度更慢。
PMS 的 apk 解析流程
PMS 的启动过程
在 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 解析 apk 流程
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 版本开始使用线程池放到子线程去解析,加快了手机启动速度。
PackageParser 类源码解析
根据上面的分析,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 解析过程有什么作用?
了解了 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 变量,将我们解析的数据添加进去,这样就实现了动态加载。