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

news2024/12/25 12:26:20

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      }

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

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

3 总结

我们再来简单总结下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通信获取。
    在这里插入图片描述

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Acwing.846 数的重心(DFS)

题目 给定一颗树&#xff0c;树中包含n个结点&#xff08;编号1~n)和n-1条无向边。 请你找到树的重心&#xff0c;并输出将重心删除后&#xff0c;剩余各个连通块中点数的最大值。 重心定义:重心是指树中的一个结点&#xff0c;如果将这个点删除后&#xff0c;剩余各个连通块中…

Java项目实战——Linux入门

文章目录 一、Linux安装1.1、安装方式介绍1.2、网卡设置1.3、安装SSH连接工具1.4、Linux和windows目录结构对比1.5、Linux目录结构 2、Linux常用命令2.1、Linux命令初体验2.2、使用技巧2.3、命令格式2.4、文件目录操作命令文件目录操作命令ls小知识 文件目录操作命令cat文件目录…

数据倾斜排查

一、问题现象 租户反馈&#xff0c;任务执行时长加长&#xff0c;执行过程中任务卡在 99%&#xff0c;大概率是出现了数据倾斜 二、排查过程 数据倾斜大多数都是大 key 问题导致的。排查方法如下&#xff1a; 1.时间判断 reduce 的时间比其他 reduce 时间长的多&#xff0c;大…

基于STM32的户外环境监测系统的设计

目录 1 引言 1.1 本课题的研究意义 1.2 本课题的研究现状 1.3本课题的发展趋势和研究可行性 1.4本课题主要研究工作 2 系统的概述和相关原理 2.1 系统的概述 2.1.1 总体设计的方案 2.1.2 总体框图 2.2 相关理论 2.2.1 STM32平台 2.2.2 WIFI模块 3 硬件电路设计 8 3…

解决页面等比缩放问题

近些年可视化数据大屏技术早已成熟&#xff0c;在市场上相关技术也是五花八门&#xff1b;通常情况是自行开发&#xff0c;要不找技术比较成熟大厂定制&#xff0c;或者使用较成熟的低代码平台实现。 技术门槛比较低&#xff0c;不过在数据大屏项目实施过程中会发现&#xff0c…

《移动互联网技术》第一章 概述: 掌握移动互联网的基本概念和组成

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

密码找回安全总结-业务安全测试实操(28)

撞库攻击 撞库是黑客通过收集互联网已泄露的用户和密码信息,生成对应的字典表,尝试批量登录其他网站后,得到一系列可以登录的用户名和密码组合。由于很多用户在不同网站使用的是相同的账号和密码,因此黑客可以通过获取用户在 A 网站的账户从而尝试登录B网站,这就可以理解为…

Linux--时间相关的指令:date、cal

一、data显示 date 指定格式显示时间&#xff1a; date %Y:%m:%d date 用法&#xff1a; date [OPTION]... [FORMAT] 1.在显示方面&#xff0c;使用者可以设定欲显示的格式&#xff0c;格式设定为一个加号后接数个标记&#xff0c;其中常用的标记列表如下 %H : 小时(00..2…

threejs动画

个人博客地址: https://cxx001.gitee.io 前面我们所用的模型大都是静态的&#xff0c;没有动画&#xff0c;没有生命。这节我们将赋予它们生命。 动画本质是通过改变物体的旋转、缩放、位置、材质、顶点、面以及其它你所能想到的属性来实现的。这些其实在前面章节示例里或多或…

git 版本控制从入门到精通

文章目录 1、git安装1.1、Linux安装1.2、Windows安装1.3、MAC安装 2、配置git3、git命令使用4、git远程服务器5、提交到远端服务器6、commit合并7、创建分支8、命令练习记录 1、git安装 1.1、Linux安装 在linux上我们建议你用二进制的方式来安装git&#xff0c;可以使用发行版…

electron报错Error: Object has been destroyed

问题描述 在 Electron 中&#xff0c;当一个窗口被销毁后&#xff0c;与该窗口相关联的 JavaScript 对象也会被销毁&#xff0c;再次访问已被销毁的窗口对象时&#xff0c;会导致 Error: Object has been destroyed 错误。 例如之前在写多窗口pinia状态同步 / 多窗口样式同步的…

Redis【实战篇】---- 分布式锁

Redis【实战篇】---- 分布式锁 1. 基本原理和实现方式对比2. Redis分布式锁的实现核心思路3. 实现分布式锁版本一4. Redis分布式锁误删情况说明5. 解决Redis分布式锁误删问题6. 分布式锁的原子性问题7. Lua脚本解决多条命令原子性问题8. 利用Java代码调试Lua脚本改造分布式锁 1…

Python 利用深度学习识别空间推理验证码(一)

注意:本文会比较长,因为空间推理验证码本身比较复杂,我会详细的讲解,我是如何一步一步拆分空间推理的思想去实现的,另外,这里只介绍第一种思想来解决空间推理验证码,实际上,解决该验证码的方法也比较多,这第一种,我会讲解的比较简单,通俗易懂。 注意:下面数据集使用…

Redis主从/哨兵机制原理介绍

目录 ​编辑 一、主从复制 1.1 什么是主从复制 1.2 主从复制的作用 1.3 主从复制原理 1.3.1 全量复制 1.3.2 增量复制 1.3.3 同步流程 二、哨兵机制 2.1 哨兵机制介绍 2.1.1 集群逻辑图 2.1.2 哨兵机制实现的功能 2.2 哨兵机制原理 2.2.1 监控 2.2.2 下线 2.2.2.1 下线流程 2.…

C# csc构建dll 和 csc构建时指定dll

新建一个mydll.cs&#xff1b; using System; using System.Collections.Generic; using System.Linq; using System.Text;namespace myDLL {public class MyMath{public int add(int x, int y){return x y;}public int sub(int x, int y){return x - y;}} } 用下图命令构建…

MySQL高可用

MySQL高可用 一、高可用 1.什么是MHA MHA&#xff08;MasterHigh Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。 MHA 的出现就是解决MySQL 单点的问题。 MySQL故障切换过程中&#xff0c;MHA能做到0-30秒内自动完成故障切换操作。 MHA能…

LeetCode·每日一题·2490. 回环句·模拟

作者&#xff1a;小迅 链接&#xff1a;https://leetcode.cn/problems/circular-sentence/solutions/2325227/mo-ni-zhu-shi-chao-ji-xiang-xi-by-xun-ge-x65e/ 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 著作权归作者所有。商业转载请联系作者获得授权&#xff0…

fusionpbx简介

概述 fusionpbx是以freeswitch作为底层框架开发而成的开源PBX&#xff0c;在freeswitch的基础上&#xff0c;优化了GUI的易用性。 fusionpbx可用作高可用性的单租户或基于域的多租户 PBX、运营商级交换机、呼叫中心服务器、传真服务器、voip服务器、语音邮件服务器、会议服务…

admin配置k8s

系列文章目录 文章目录 系列文章目录一、实验1.实验要求2.3. 所有节点安装docker4.所有节点安装kubeadm&#xff0c;kubelet和kubectl5.部署K8S集群6./所有节点部署网络插件flannel7./在master节点查看节点状态 总结 一、实验 1.实验要求 master&#xff08;2C/4G&#xff0c…

Maven的安装过程

参考地址 https://www.cnblogs.com/hanliukui/p/16842734.html 注意下载包体是bin的包体&#xff1a;Maven – Download Apache Maven 然后ok