【Android Framework系列】第4章 PMS原理

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      }

3 PMS解析

PMS解析主要做了三件事:

1. 遍历/data/app和/system/app文件夹,找到apk文件
2. 解压apk文件
3. dom解析AndroidManifest.xml文件,将xml信息存储起来提供给AMS使用

3.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()方法

3.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版本开始使用线程池放到子线程去解析,加快了手机启动速度

3.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      }

到这里解析三步流程已经完成:

  1. 通过遍历/data/app/system/app文件夹找到apk文件路径;
  2. 把找到的路径传入PackageParser对象的parsePackage()方法对apk的AndroidManifest.xml进行dom解析;
  3. 然后根据不同标签解析信息存储到Package类的对应字段并缓存到内存中,如:四大组件、权限等信息。方便后续AMS直接从PMS的Package缓存中获取使用。
    在这里插入图片描述

4 总结

我们再来简单总结下PMS:
PMS是包管理系统服务,用来管理所有的包信息,包括应用安装卸载更新以及解析AndroidManifest.xml。手机开机后,它会遍历设备上/data/app//system/app/目录下的所有apk文件,通过解析所有安装应用的AndroidManifest.xml,将xml中的数据(应用信息权限四大组件等)信息都缓存到内存中,后续提供给AMS等服务使用。

PMS的整体流程:

  1. 手机开机,内核进程启动init进程init进程启动SeriviceManager进程启动Zygote进程Zygote进程启动SystemServerSystemServer进程启动AMSPMS,并注册到ServiceManager
  2. PMSSystemServer初始化后,开始扫描/data/app//system/app/目录下的所有apk文件,获取每个apk文件AndroidManifest.xml文件,并进行dom解析
  3. 解析AndroidManifest.xml应用信息权限四大组件等数据信息转换为Java Bean缓存到内存中
  4. AMS需要获取apk数据信息时,通过ServiceManager获取到PMS的Binder代理通过Binder通信获取。
    在这里插入图片描述

5 面试题

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文件中添加信息)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/7338.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

易基因: RRBS揭示基于DNA甲基化驱动基因的肾透明细胞癌预后模型的鉴定和验证|项目文章

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 肾细胞癌&#xff08;RCC&#xff09;是最常见的肾癌亚型&#xff0c;每年超400万例新发病例&#xff0c;是泌尿系统恶性肿瘤导致的第二大死因。2%-70%的RCC为透明细胞RCC&#xff08;Cl…

HotSpot 垃圾收集器

HotSpot 垃圾收集器 HotSpot 虚拟机提供了多种垃圾收集器&#xff0c;每种收集器都有各自的特点&#xff0c;虽然我们要对各个收集器进行比较&#xff0c;但并非为了挑选出一个最好的收集器。我们选择的只是对具体应用最合适的收集器。 新生代垃圾收集器 Serial 垃圾收集器&am…

Spring Boot 整合 分布式搜索引擎 Elastic Search 实现 我附近的、酒店竞排

文章目录 ⛄引言一、我附近的酒店⛅需求分析⚡源码编写 二、酒店竞价排名⌚需求分析⏰修改搜索业务 ✅效果图⛵小结 ⛄引言 本文参考黑马 分布式Elastic search Elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中…

账号安全总结-业务安全测试实操(27)

电子邮件账号泄露事件 电子邮箱业务基于计算机和通信网的信息传递业务,利用电信号传递和存储信息,为用户传送电子信函、文件数字传真、图像和数字化语音等各类型的信息。电子邮件最大的特点是,人们可以在任何地方、任何时间收、发信件,解决了时空的限制,大大提高了工作效…

【Java用法】Java在Linux下获取当前程序路径以及在Windows下获取当前路径对比

Java在Linux下获取当前程序路径以及在Windows下获取当前路径对比 log.info("分隔符&#xff1a;File.separator[{}]", File.separator); log.info("用户主目录&#xff1a;user.home[{}]", System.getProperties().getProperty("user.home")); l…

MySQL中常用查看锁和事务的SQL语句

MySQL中常用查看锁和事务的SQL语句 当我们在使用MySQL数据库时&#xff0c;了解如何查看锁和事务的状态是非常重要的。这些信息可以帮助我们调试和优化数据库性能&#xff0c;以及解决并发访问的问题。在本博客中&#xff0c;我将介绍一些常用的MySQL查询语句&#xff0c;用于查…

JMeter之IP欺骗技术(模拟不同的IP地址并发请求)

目录 前言&#xff1a; 第一步&#xff1a;在负载机上绑定IP地址 第二步&#xff1a;点击高级&#xff0c;添加伪造的IP地址 第三步&#xff1a;新增IP地址复制到文本 第四步&#xff1a;新建参数化请求 第五步&#xff1a;新建压力测试脚本 第六步&#xff1a;配置线程…

Spring Boot日志文件

目录 前言&#x1f36d; 一、日志的作用&#x1f36d; 1、日志真实使用案例&#xff1a;&#x1f349; 二、日志怎么用&#x1f36d; 1、自定义日志打印&#x1f349; Ⅰ、在程序中得到日志对象&#x1f353; 常见的日志框架说明&#xff08;了解&#xff09;&#x1f35…

GAD7980/CL1680/AD7980详解与开发说明

目录 1 概述2 GAD7980简介3 用法时序4 参数计算与参数解释4.1 采样率4.2 转换时间4.3 采集时间5 采样数值折算6 设计注意事项7 代码demo 1 概述 本文用于讲述GAD7980的功能与用法&#xff0c;以及其中一些参数的计算方法&#xff0c;用法时序&#xff0c;输出数值等等&#xf…

Django学习笔记-VS Code本地运行项目

截止到上一章节&#xff1a;Django 学习笔记-Web 端授权 AcWing 一键登录&#xff0c;我们的项目一直是部署在云服务器上&#xff0c;包括编写代码以及调试运行也是在云服务器上&#xff0c;现在我们尝试将其放回本地运行。 CONTENTS 1. 将项目传到本地2. 虚拟环境配置3. 修改项…

postgresql 获取建表信息

通过函数获取 创建自定义函数 CREATE OR REPLACE FUNCTION tabledef(text,text) RETURNS text LANGUAGE sql STRICT AS $$ WITH attrdef AS (SELECT n.nspname, c.relname, c.oid, pg_catalog.array_to_string(c.reloptions || array(select toast. || x from pg_catalog.un…

照片jpg大小kb如何修改?图片在线压缩大小怎么处理?

最近需要在各种报名平台上传照片的小伙伴比较多&#xff0c;难免会遇到需要压缩jpg图片的情况&#xff0c;那么怎么才能将jpg图片压缩&#xff08;https://www.yasuotu.com/jpg&#xff09;呢&#xff1f;今天介绍一个图片在线压缩大小的方法&#xff0c;不用下载任何软件就可以…