1 PMS简介
PMS(PackageManagerService)
是Android提供的包管理系统服务
,它用来管理所有的包信息
,包括应用安装
、卸载
、更新
以及解析AndroidManifest.xml
。通过解析每个安装应用的AndroidManifest.xml
,将xml中的数据全部都保存起来,后续提供给AMS所需要的数据,它是具有保存应用数据的缓存。
我们都知道AndroidManifest.xml
定义了apk中所有的四大组件
、权限
等等信息,它是一个定义文件。PMS对apk的解析最主要的就是去扫描/data/app和/system/app目录下的apk文件
,找到apk包中的AndroidManifest.xml
,然后解析AndroidManifest.xml的信息保存到系统内存中
,这样AMS在需要应用数据时,就能找到PMS快速的从内存中拿到相关信息。
我们知道Android设备安装的应用越多,开机启动的速度就越慢。原因是安装的应用多了,自然PMS
的解析耗时就会增加,在开机启动的耗时中70%
的都是在PMS
的解析上,如果说优化开机启动速度
,不妨从PMS
入手。
本文基于Android10(Q)
的源码做分析
2 PMS启动
在Android系统所有的核心服务都会经过SystemServer
启动,PMS也不例外,SystemServer
会在手机开机时启动运行。关于SystemServer是如何启动的可以查看文章【Android Framework系列】第3章 Zygote进程相关和【Android车载系列】第10章 系统服务-SystemServer源码分析(API28)
我们知道PMS是在SystemServer进程中被启动,下面我们来看看具体是怎么启动的PMS:
/frameworks/base/services/java/com/android/server/SystemServer.java
348 public static void main(String[] args) {
349 new SystemServer().run();
350 }
......
370 private void run() {
......
507 // Start services.
508 try {
509 traceBeginAndSlog("StartServices");
510 startBootstrapServices();
511 startCoreServices();
512 startOtherServices();
513 SystemServerInitThreadPool.shutdown();
514 } catch (Throwable ex) {
515 Slog.e("System", "******************************************");
516 Slog.e("System", "************ Failure starting system services", ex);
517 throw ex;
518 } finally {
519 traceEnd();
520 }
......
543 }
......
623 private void startBootstrapServices() {
......
734 try {
735 Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");
736 mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
737 mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
738 } finally {
739 Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
740 }
741 mFirstBoot = mPackageManagerService.isFirstBoot();
742 mPackageManager = mSystemContext.getPackageManager();
......
818 }
/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
2283 public static PackageManagerService main(Context context, Installer installer,
2284 boolean factoryTest, boolean onlyCore) {
2285 // Self-check for initial settings.
2286 PackageManagerServiceCompilerMapping.checkProperties();
2287
2288 PackageManagerService m = new PackageManagerService(context, installer,
2289 factoryTest, onlyCore);
2290 m.enableSystemUserPackages();
2291 ServiceManager.addService("package", m);
2292 final PackageManagerNative pmn = m.new PackageManagerNative();
2293 ServiceManager.addService("package_native", pmn);
2294 return m;
2295 }
Zygote进程
调用SystemServer.java
类中main()
方法,在run()
方法中的startBootstrapServices()
方法对PMS等核心服务进行初始化,调用其main()
方法进行创建对应的服务,并将PMS服务添加到ServiceManager
中(AMS也是一样的操作)进行服务的管理。
ServiceManager
只提供了addService()
和getService()
方法,当app进程
需要获取到对应的系统服务
,都会通过ServiceManager
拿到相应服务的Binder
代理,使用Binder通信获取数据。
/frameworks/base/core/java/android/app/ActivityThread.java
2131 @UnsupportedAppUsage
2132 public static IPackageManager getPackageManager() {
2133 if (sPackageManager != null) {
2134 //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
2135 return sPackageManager;
2136 }
2137 IBinder b = ServiceManager.getService("package");
2138 //Slog.v("PackageManager", "default service binder = " + b);
2139 sPackageManager = IPackageManager.Stub.asInterface(b);
2140 //Slog.v("PackageManager", "default service = " + sPackageManager);
2141 return sPackageManager;
2142 }
2 PMS解析
PMS解析主要做了三件事:
1. 遍历/data/app和/system/app文件夹,找到apk文件
2. 解压apk文件
3. dom解析AndroidManifest.xml文件,将xml信息存储起来提供给AMS使用
2.1 遍历/data/app的文件夹
/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
......
// /data/app目录
663 private static final File sAppInstallDir =
664 new File(Environment.getDataDirectory(), "app");
......
2380 public PackageManagerService(Context context, Installer installer,
2381 boolean factoryTest, boolean onlyCore) {
......
// /system/app 目录
2667 final File systemAppDir = new File(Environment.getRootDirectory(), "app");
// 扫描/system/app 目录下的apk文件
2668 scanDirTracedLI(systemAppDir,
2669 mDefParseFlags
2670 | PackageParser.PARSE_IS_SYSTEM_DIR,
2671 scanFlags
2672 | SCAN_AS_SYSTEM,
2673 0);
......
// 扫描/data/app 目录下的apk文件
2914 scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
......
3372 }
......
9003 private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) {
9004 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
9005 try {
9006 scanDirLI(scanDir, parseFlags, scanFlags, currentTime);
9007 } finally {
9008 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
9009 }
9010 }
9011
9012 private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
9013 final File[] files = scanDir.listFiles();
......
9023 try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
9024 mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
9025 mParallelPackageParserCallback)) {
9026 // Submit files for parsing in parallel
9027 int fileCount = 0;
9028 for (File file : files) {
9029 final boolean isPackage = (isApkFile(file) || file.isDirectory())
9030 && !PackageInstallerService.isStageName(file.getName());
9031 if (!isPackage) {
9032 // Ignore entries which are not packages
9033 continue;
9034 }
9035 parallelPackageParser.submit(file, parseFlags);
9036 fileCount++;
9037 }
......
9076 }
9077 }
我们看到这里遍历/data/app
和/system/app
文件夹,找到apk文件,然后通过submit()
方法进行了apk的解析。我们继续往下看submit()
方法
2.2 解压apk文件
/frameworks/base/services/core/java/com/android/server/pm/ParallelPackageParser.java
100 /**
101 * Submits the file for parsing
102 * @param scanFile file to scan
103 * @param parseFlags parse falgs
104 */
105 public void submit(File scanFile, int parseFlags) {
106 mService.submit(() -> {
107 ParseResult pr = new ParseResult();
108 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
109 try {
110 PackageParser pp = new PackageParser();
111 pp.setSeparateProcesses(mSeparateProcesses);
112 pp.setOnlyCoreApps(mOnlyCore);
113 pp.setDisplayMetrics(mMetrics);
114 pp.setCacheDir(mCacheDir);
115 pp.setCallback(mPackageParserCallback);
// 需要解析的apk文件路径
116 pr.scanFile = scanFile;
// 通过PackageParser对apk进行解析
117 pr.pkg = parsePackage(pp, scanFile, parseFlags);
118 } catch (Throwable e) {
119 pr.throwable = e;
120 } finally {
121 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
122 }
123 try {
124 mQueue.put(pr);
125 } catch (InterruptedException e) {
126 Thread.currentThread().interrupt();
127 // Propagate result to callers of take().
128 // This is helpful to prevent main thread from getting stuck waiting on
129 // ParallelPackageParser to finish in case of interruption
130 mInterruptedInThread = Thread.currentThread().getName();
131 }
132 });
133 }
134
135 @VisibleForTesting
136 protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
137 int parseFlags) throws PackageParser.PackageParserException {
138 return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
139 }
将上面找到的apk文件路径传入PackageParser对象的parsePackage()
进行apk的解析。这里要注意:在不同的系统源码版本解析的方式也不相同,在6.0、7.0、8.0版本启动解析的方式还是直接解析的,但在10.0版本开始使用线程池放到子线程去解析,加快了手机启动速度
。
2.3 dom解析AndroidManifest.xml文件
/frameworks/base/core/java/android/content/pm/PackageParser.java
1011 @UnsupportedAppUsage
1012 public Package parsePackage(File packageFile, int flags, boolean useCaches)
1013 throws PackageParserException {
1014 Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
1015 if (parsed != null) {
// 直接返回缓存
1016 return parsed;
1017 }
1018
1019 long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
// apk文件非目录,执行parseMonolithicPackage()
1020 if (packageFile.isDirectory()) {
1021 parsed = parseClusterPackage(packageFile, flags);
1022 } else {
1023 parsed = parseMonolithicPackage(packageFile, flags);
1024 }
......
1036 return parsed;
1037 }
......
1289 @Deprecated
1290 @UnsupportedAppUsage
1291 public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
......
1300 final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
1301 try {
// apk解析方法parseBaseApk()
1302 final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
1303 pkg.setCodePath(apkFile.getCanonicalPath());
1304 pkg.setUse32bitAbi(lite.use32bitAbi);
1305 return pkg;
1306 } catch (IOException e) {
1307 throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
1308 "Failed to get path: " + apkFile, e);
1309 } finally {
1310 IoUtils.closeQuietly(assetLoader);
1311 }
1312 }
1313
1314 private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
1315 throws PackageParserException {
1316 final String apkPath = apkFile.getAbsolutePath();
......
1328 // 开始 dom 解析 AndroidManifest.xml
1329 XmlResourceParser parser = null;
1330 try {
1331 final int cookie = assets.findCookieForPath(apkPath);
1332 if (cookie == 0) {
1333 throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
1334 "Failed adding asset path: " + apkPath);
1335 }
1336 parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
......
1340 final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
......
1351 return pkg;
1352
1353 } catch (PackageParserException e) {
1354 throw e;
1355 } catch (Exception e) {
1356 throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
1357 "Failed to read manifest from " + apkPath, e);
1358 } finally {
1359 IoUtils.closeQuietly(parser);
1360 }
1361 }
......
1913 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1914 private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
1915 String[] outError) throws XmlPullParserException, IOException {
1916 final String splitName;
1917 final String pkgName;
1918
1919 try {
1920 Pair<String, String> packageSplit = parsePackageSplitNames(parser, parser);
1921 pkgName = packageSplit.first; // 包名
1922 splitName = packageSplit.second;
......
1932 }
......
// 将解析的信息(四大组件、权限等)存储到Package
1943 final Package pkg = new Package(pkgName);
.....
1975 return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
1976 }
......
6403 /**
6404 * Representation of a full package parsed from APK files on disk. A package
6405 * consists of a single base APK, and zero or more split APKs.
6406 */
6407 public final static class Package implements Parcelable {
......
// 包名
6409 @UnsupportedAppUsage
6410 public String packageName;
// 应用信息
6453 @UnsupportedAppUsage
6454 public ApplicationInfo applicationInfo = new ApplicationInfo();
6455
// 权限相关信息
6456 @UnsupportedAppUsage
6457 public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
6458 @UnsupportedAppUsage
6459 public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
// 四大组件相关信息
6460 @UnsupportedAppUsage
6461 public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
6462 @UnsupportedAppUsage
6463 public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
6464 @UnsupportedAppUsage
6465 public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
6466 @UnsupportedAppUsage
6467 public final ArrayList<Service> services = new ArrayList<Service>(0);
......
7499 }
到这里解析三步流程已经完成:
- 通过遍历
/data/app
或/system/app
文件夹找到apk文件路径; - 把找到的路径传入PackageParser对象的
parsePackage()
方法对apk的AndroidManifest.xml进行dom解析; - 然后根据不同标签解析信息存储到Package类的对应字段并缓存到内存中,如:四大组件、权限等信息。方便后续AMS直接从PMS的Package缓存中获取使用。
3 总结
我们再来简单总结下PMS:
PMS是包管理系统服务
,用来管理所有的包信息
,包括应用安装
、卸载
、更新
以及解析AndroidManifest.xml
。手机开机后,它会遍历设备上/data/app/
和/system/app/
目录下的所有apk文件
,通过解析所有安装应用的AndroidManifest.xml
,将xml中的数据(应用信息
、权限
、四大组件
等)信息都缓存到内存中,后续提供给AMS
等服务使用。
PMS的整体流程:
- 手机开机,
内核进程
启动init进程
,init进程
启动SeriviceManager进程
和启动Zygote进程
,Zygote进程
启动SystemServer
,SystemServer
进程启动AMS
、PMS
,并注册到ServiceManager
。 PMS
被SystemServer
初始化后,开始扫描/data/app/
和/system/app/
目录下的所有apk文件
,获取每个apk文件
的AndroidManifest.xml
文件,并进行dom解析
。- 解析
AndroidManifest.xml
将应用信息
、权限
、四大组件
等数据信息转换为Java Bean
缓存到内存中
。 - 当
AMS
需要获取apk数据信息
时,通过ServiceManager
获取到PMS的Binder代理
通过Binder通信
获取。
4 面试题
1 PMS 是干什么的,你是怎么理解PMS
包管理,包解析,结果缓存,提供查询接口。
1. 遍历/data/app和/system/app文件夹,找到apk文件
2. 解压apk文件
3. dom解析AndroidManifest.xml文件,将xml信息存储起来提供给AMS使用
2 熟悉PMS源码有什么用处
1. 帮助了解Android包管理系统原理
2. 配合AMS通过Hook技术,实现热更新、插件化等功能。
比如,我们可以通过反射获取到PackageParser对象,再反射调用它的 parsePackage() 传入 apk 路径完成解析获取到 Package 对象,再反射 PMS 的 activities、providers、receivers、services 变量,将我们解析的数据添加进去,这样就实现了动态加载(不需要AndroidManifest.xml文件中添加信息)。