ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android PackageManagerService (Pkms) 核心机制与源码深度解析

Android PackageManagerService (Pkms) 核心机制与源码深度解析 先澄清一个事情PackageManagerService圈内习惯直接叫Pkms在很多系统开发岗位描述里也叫PMS。它是Android Framework层里最接近“系统命脉”的一个服务凡是涉及APK的安装、卸载、权限校验、Intent查询、组件解析底层都要走到它这里。可以这么说——在你点击一个App图标启动它之前Pkms已经完成了对这个应用包的全部“身家调查”。这篇文章想写给三类人看一是刚切入Android系统开发、天天在system_server里迷路的Framework工程师二是做应用市场、自动化打包、系统预装定制的开发者需要精确知道包管理链路做了什么三是纯粹对源代码有好奇心的Android老手想搞清楚Pkms为什么值得读。我会按下面这条主线展开先说明Pkms在整个系统里的坐标和核心职责再把APK从安装到能被启动的全链路拆开逐个环节吃透关键代码逻辑最后聊几个我自己实际排查过的消息/卡顿/权限问题给你一份可以直接抄作业的思路。1. Pkms是什么为什么它如此关键我们先不急着看代码先把Pkms在Android系统里的“位置感”建立起来。SystemServer启动时会按阶段拉起一堆服务Pkms在“引导服务”阶段就会被创建它依赖ActivityThread、installd、SystemConfig这些底层能力同时又给ActivityManagerService、WindowManagerService、应用进程提供包信息支撑。1.1 包管理服务的职责范围Pkms不是一个“只负责安装”的服务它实际管的事情至少有这几块扫描并维护系统内所有APK的包信息包括包名、版本号、Activity/Service/Receiver组件声明、权限声明执行APK的安装、卸载、更新、禁用、隐藏等生命周期操作管理运行时权限的授予与撤销维护权限状态持久化为其他系统服务提供包信息查询能力比如AMS需要知道某个Activity是否exported、Ams启动进程前要找Pkms确认uid参与Intent显式/隐式解析通过IntentResolver把Intent和组件匹配起来处理SELinux上下文、底层文件安装路径协同installd完成文件层面操作简单来说没有Pkms整个系统里甚至找不到任何一个可以启动的Activity。1.2 服务间的位置关系Pkms不是孤岛。它和installd通过Binder跨进程通信所有的磁盘文件操作创建目录、拷贝APK、chown、dex优化都由installd在native层完成。Pkms和AMS的互动也极其频繁比如AMS要启动一个应用时会先从Pkms拿到ApplicationInfo里面包含apk路径、uid、targetSdk等关键信息。从线程模型上看Pkms自己就运行在system_server进程中是一个线程但Pkms内部大量逻辑是串行执行的——所以一旦某个操作卡住整个系统界面会出现“等待”或者ANR这也是Pkms问题排查最大的难点。2. 从安装请求到应用可用Pkms核心链路拆解这一部分我们沿着“APK怎么从文件变成系统可调用的应用”这条主线走一遍重点看链路上每个关键节点Pkms做了什么判断和操作。2.1 安装方式的演进和边界Android的安装接口经历了几个阶段早期直接用pm install命令行、应用内Intent.ACTION_VIEW打开安装包到后来Android 5.0引入PackageInstaller会话机制再到Android 11对安装权限做了更严格限制。现在几乎所有正规安装都走PackageInstaller。PackageInstaller的核心思想就是把安装过程拆成两段第一段是写入数据应用把这个APK的数据写入到系统分配的session里第二段是提交安装commit之后系统才真正执行扫描和注册。这种设计与早期直接传文件路径的方式相比提升了安全性也解决了多包/拆分APK的安装问题。2.2 installStage到prepareForInstall的完整路径以PackageInstaller.commit为例调用链大致是Pkms.installStage收到安装请求通过mHandler切换到安装线程PMS_STAGE_STRATEGY执行installStage内部逻辑PackageInstallerService维护session状态commit时触发commitLocked调用Pkms.installStage将session数据交给PackageInstallerSessionPackageInstallerSession.sealAndValidate进行包校验、签名验证真正进入installLocked执行scanPackageTracedLI、reconcilePackageLocked、commitPackageLocked这里最值得关注的是scanPackageTracedLI它是所有安装路径的汇总站也是Pkms最核心的函数。新安装、系统启动扫描、静默更新——所有包都必须经过它来“注册”进Pkms内部数据结构。2.3 scanPackageLI的内部秘密scanPackageTracedLI内部Pkms干了几件关键事情解析APK用PackageParser把AndroidManifest.xml解析成Package对象包括Activity、Service、Provider、Receiver、权限声明全部加载校验包名和版本如果和现有包冲突按REPLACE标志决定是更新还是报错分配uid若是新装的应用Pkms会通过Settings.getNextAvailableUidLocked获得一个uid权限就绑定在这个uid上收集grantedPermissions把manifest里请求的权限交给权限管理器统一校验持久化数据把包信息写入/data/system/packages.xml和packages.list这是系统重启后恢复包状态的关键也在这一步Pkms会把primaryCpuAbi和secondaryCpuAbi确定下来。如果你装了一个arm64的APK到arm32设备上某些功能运行出问题多半是这个abi判断逻辑跟预期不符。2.4 installd与磁盘操作Pkms本身不会去移动文件它只下命令。真正干活的是installd。在installLocked中Pkms会通过Installer.installApk、createUserData等接口触发installd执行在/data/app/package/目录下创建lib和oat子目录将APK拷贝到指定位置设置目录owner为分配的uid执行dex2oat或设备上的编译器生成odex文件这里有一步容易被忽略freeCache的逻辑。当存储空间不足时Pkms会先在installd侧触发缓存清理这是之前某些手机上“安装应用时系统自动清理垃圾”的底层来源。3. 包与uid之间的关系以及权限管理的底层设计权限管理一直是Pkms里最容易被误解的部分。很多人以为权限判断是“包名匹配”实际底层全靠uid。一个uid可以对应多份权限表权限绑定在uid和package的组合上。3.1 权限数据结构和存储权限的静态定义存储在/system/etc/permissions/目录下这些xml文件在系统启动时被SystemConfig读取构造出全局权限映射。运行时授予的权限状态被持久化在/data/system/runtime-permissions.xml。Pkms内部用permissionManager统一管理权限操作。当应用调用checkPermission时查的是RuntimePermissionState而不是直接看manifest。这个过程会经过索引优化避免每次权限判断都全量扫描。我的一个实际体会在定制系统里如果你要为系统应用预授予运行时权限最好改default-permissions.xml比在代码里强行grant要干净得多还能避免用户在设置页看到“已授权但亮不起来”的怪异状态。3.2 签名校验和包覆盖规则签名校验是安装链路上的安全性核心。Pkms支持v1JAR签名、v2APK Signature Scheme v2、v3轮换签名三种验证方式。collectCertificates会把APK里的签名证书全部取出来组成一个证书链数组之后compareSignatures判断两个签名是否一致。系统应用和普通应用相互覆盖时有非常严格的规则只有签名一致才允许覆盖安装。实际工作的一个常见坑是预装应用签名变了结果点击升级永远报INSTALL_FAILED_UPDATE_INCOMPATIBLE而且log里只给一个泛泛的签名不匹配排查起来很抓狂。3.3 系统应用与特权应用的差异Pkms内部对系统应用packageFlags包含FLAG_SYSTEM有特殊处理路径。系统应用的安装不是走PackageInstaller流程而是在系统启动扫描时直接注册到Pkms里。这也意味着系统应用不能通过标准接口卸载——即使adb的pm uninstall也仅仅是把包标记为FLAG_SYSTEMFLAG_UPDATED_SYSTEM_APP状态“禁用”而已APK还躺在/system/app或者/product/app里。有些定制的场景需要“干掉”一个系统应用单靠Pkms是无能为力的得去改分区镜像文件——这也是很多做系统裁剪的同学第一次接触Pkms会产生的误解。4. Intent查询和组件解析Pkms面向应用层的前台能力如果说安装链路是Pkms“后端”的工作那么Intent查询就是Pkms“前台”每天被调用最多的能力。任何一个startActivity、startService、bindService只要不是显式指明组件系统都要在Pkms里过一遍IntentResolver。4.1 IntentResolver的工作机制IntentResolver本质是多个map的集合按action、type、scheme索引到候选组件列表。查询时先按action过滤再按type、category、data匹配最后做优先级比较。Android的隐式Intent解析之所以能高效完成完全靠这套索引结构不然每次用手机打开链接都要全量遍历所有应用声明性能早崩了。Pkms为每个Package都注册了三个resolvermActivityIntentResolver、mServiceIntentResolver、mProviderIntentResolver。这就是queryIntentActivities和queryIntentServices等接口背后的数据源。4.2 一个Intent从发出到匹配的流程resolveIntent是核心入口。假设用户点击了一个http链接Pkms先把Intent里的action和uri解析出来对所有已注册Activity的intent-filter做匹配打分返回得分最高的ComponentName如果多个应用分值相同系统会弹出“打开方式”选择框这里有个容易踩的坑——如果某个应用在manifest里声明了android:priority它会在解析过程中获得额外加成甚至能“抢走”本该由系统默认处理的ACTION。这类问题排查起来最头疼因为Pkms日志里只显示最后选中的组件不会告诉你为什么另一个组件分值更低。我遇到过一次类似问题最后是模块扫描APK的intent-filter才发现某应用把priority设成了999。4.3 “默认应用”的维护逻辑Pkms还维护了一套默认应用数据比如默认浏览器、默认桌面、默认拨号器。这部分由RoleManager和DefaultAppsController配合Pkms实现。它把“谁能成为默认应用”的裁决权与包解析分开避免出现两个浏览器都把自己声明为默认时Pkms不知道听谁的。实际做系统开发时如果你要在出厂状态指定某个应用作为默认桌面不能只靠Pkms还得在RoleManager的权限配置和default-app的role里同时声明。两者缺一就会出现第一次开机时Pkms能解析到桌面但系统始终走“选择应用”弹窗。5. 数据持久化packages.xml里藏着的秘密Pkms的持久化数据是系统稳定的基石之一。重启之后Pkms必须把所有已安装包的信息重建出来否则应用列表、权限状态、图标排列全乱套。这项工作依赖三个文件/data/system/packages.xml、/data/system/packages.list、/data/system/packages-stopped.xml旧版本中还有packages-backup.xml作备份。5.1 packages.xml的结构和写入时机packages.xml保存包名、版本、uid、权限授权、安装时间等属性可以把每个packages.xml条目看作一个Package的“身份证存档”。它不是在每次包变化时都实时写入而是通过Settings类里的writeLPr在关键节点安装、卸载、权限变更后触发。一个很容易被忽视的点packages.xml的写入是状态持久化的“最终落点”如果设备在写入过程中断电Pkms会依赖packages-backup.xml恢复。这个设计看起来很基础但真的救过很多次“重启后应用突然消失”的现场。5.2 包状态恢复流程系统启动时Settings.readLPw会先读取packages.xml重建mPackages、mSettings等内存结构。然后Pkms会执行一次全量包扫描把磁盘上/system、/vendor、/product、/data/app等目录下的APK与内存数据比对。一旦发现磁盘上有的包没在packages.xml里会以扫描结果为准把它加入反之如果packages.xml中有记录但磁盘文件被清掉了Pkms会清理掉对应记录。这套“归集-对齐-修正”的流程保证即便packages.xml和真实文件系统不一致系统也能自愈。实际操作中如果你在开发阶段频繁对APK做删除文件操作比如调试动态加载在app目录里手工删dex就容易触发这种重建最终表现为设置里应用还在点进去却说“应用未安装”。因为Pkms的包信息还是内存态防御手段是顺手调用一下pm clear或者pm uninstall做兜底。6. 多用户空间下的包管理策略多用户是Pkms里一个比较复杂的主题。Android 5.0之后开始支持多用户每个用户都有自己的应用数据、权限状态但底层的APK文件是共享的并不每个用户copy一份。6.1 用户空间和应用的可见性Pkms内部通过UserManager管理用户状态并用UserHandle标记包数据的归属。mSettings里为每个用户保存了一份UserState包含每个用户对当前包的installed、stopped、disabled状态。权限授予也是按用户维度分别记录的。这解释了为什么一个应用在“机主”空间里被卸载了但在“访客空间”里还留着缓存数据甚至还在桌面上Pkms对每个用户独立管理包的启用状态虽然有FLAG_HIDDEN之类的控制但实际卸载只会删掉当前用户目录下的数据。6.2 user0与system用户的特殊性user0设备所有者有着最高权限。很多包管理操作只允许user0执行比如pm install、动态权限授予这是出于安全考虑。开发多用户场景时最容易遇到的坑是在user0上用adb安装的应用切到访客空间看不到。这不是bug而是Pkms在安装时就把包标记为FLAG_USER_0_ONLY是可被pm install --user参数覆盖的。所以做多用户定制的时候安装命令要显式指定用户id才能让应用在所有用户下可见。7. 实际项目中的Pkms疑难杂症排查最后分享几个我在实际项目里碰到过、并且真正靠源码定位解决的Pkms问题每个问题都附上排查思路可以作为你排错时的参考模板。7.1 手机重启后部分应用消失现象设备某些应用在重启后从桌面消失设置-应用里也找不到但/data/app下APK文件还在。排查链路检查logcat里Pkms扫描阶段的输出看看有没有类似Reconciling failed package的报错用dumpsys package 包名看包状态如果显示stoppedtrue、installedfalse多半是持久化信息被重置了打开packages.xml检查对应记录是否存在。不存在说明Pkms启动时没有成功把包重新注册进mPackages检查APK目录是否有被其他进程改动过的痕迹比如oat目录被删掉导致scan失败一个常见原因是APK放在/data/app/下但恰好目录下出现了不完整的临时文件Pkms扫描时把整个目录判定为非法包而跳过。处置方式是清除这个目录后重新安装。7.2 INSTALL_FAILED_UPDATE_INCOMPATIBLE这类错误很经典。原因通常是系统里已存在同一个包名但签名不同的包。Pkms的安装流程里会先查mPackages里是否已有同名包有则进入更新流程更新流程强制校验签名一致性。排查时不能只看报错文案要抓PackageManagerException里的详细reason code。实际项目里出现过一种情况包名完全一致、签名也一致但还是报这个错最后发现是因为上次更新的APK版本号为0与已安装版本相同Pkms按“降级”逻辑拒绝了更新。解法是在manifest里把versionCode调高或者在安装时显式允许降级。7.3 权限被授予但应用拿不到这种问题会让人非常抓狂。设置里明明授权了应用运行时说没有权限。排查时先分清楚是“应用级权限”还是“运行时权限”。应用级权限由Pkms在安装时校验签名后自动授予运行时权限则由PermissionManager管理。很多时候问题出在SELinux域上Pkms虽然已经把权限标记为granted但应用进程的SELinux上下文不允许它真正访问资源这时logcat里会出现avc denied。这种场景下光看Pkms日志是查不出来的必须拉dmesg或者audit2allow辅助分析。7.4 应用解析慢或桌面加载卡顿Pkms不直接负责桌面的UI但桌面在拉应用列表时会频繁调用Pkms的queryIntentActivities和getInstalledPackages。如果Pkms内部有锁竞争或长时间持锁桌面就会有明显卡顿。曾经定位过一个问题开机后桌面首次加载要20秒用dumpsys package和systrace查发现Pkms在反复进行dexopt。原因是系统里装了很多targetSdk 29以下的老应用系统升级后需要为这些应用重新生成odex。优化方案是调高dexopt的预编译阈值并将部分应用改成pm compile -m speed-profile。8. 深入Pkms源码时的必读文件如果前面这些内容激起了你自己翻代码的兴趣我建议你从这几处开始读它们是我认为Pkms源码里含金量最高的部分services/core/java/com/android/server/pm/PackageManagerService.java核心逻辑所在代码量巨大建议用IDE的outline功能按方法名跳转services/core/java/com/android/server/pm/Settings.java持久化状态管理纠结packages.xml写什么、什么时候写读它services/core/java/com/android/server/pm/ComputerEngine.java高版本/PackageManagerServiceUtils.java各种查询和工具逻辑services/core/java/com/android/server/pm/Installer.javaPkms和installd通信的桥里面每个方法对应native的一个命令core/java/android/content/pm/PackageParser.java负责解析AndroidManifest.xml想要深挖组件扫描从它入手读这套代码有个非常实用的技巧先在代码里搜Log.i(TAG和Slog日志输出把高频日志点串起来就能得到一条完整的执行轨迹。很多系统开发者排斥看日志觉得打印少但对Pkms这种体量的类日志本身就是把脉络理清楚的最好教材。9. 一个完整的Pkms调试环境搭建建议调Pkms这类系统服务开发环境很重要。推荐直接用aosp_car_x86_64或aosp_x86_64编译出来的模拟器镜像方便快速实验和抓Log。基础调试命令顺手记一份adb shell dumpsys package 包名查看特定包的完整信息包括uid、权限、路径、安装状态adb shell pm list packages列出所有已安装包adb shell pm install -r -t path覆盖安装保留数据adb shell pm disable-user --user 0 包名禁用用户应用adb shell cmd package compile -m speed 包名手动触发dex优化adb shell am monitor监听启动活动排查Intent匹配问题如果你做的是系统预装相关的开发建议编译后直接挂persist.sys.disable_rescuetrue避免救援机制在包异常时自动清理数据。这个参数不常出现在公开文档里但对调试Pkms相关问题特别有用。Pkms这个服务看着庞大其实核心脉络非常清晰一套安装链路、一套扫描机制、一套权限体系、一处持久化存储。只要把这四条主线理清楚再去读具体代码基本不会被里面大量细节绕晕。我自己每次接手新的系统定制项目遇到包相关的问题第一件事永远是打开Pkms超级方法清单按调用关系往下跟——这套方法论帮我在各种“诡异问题”里节省了很多时间。希望这篇文章也能帮你把Pkms这块硬骨头啃下来。
返回列表