搞客户端开发久了的人,多少都遇到过“应用装不上”或者“刚装上就秒闪退”的怪事。查了一圈网络、缓存、存储空间,最后发现真正的原因往往藏在系统层:所有跟APK安装、卸载、升级、权限授予、组件注册相关的操作,最终都会汇聚到 system_server 进程里的一个服务——PackageManagerService,简称 PMS。它管着包信息的“总账本”,也管着安装和管理的全流程执行,算是Android系统里最核心、也最容易让人一头雾水的服务之一。
这篇文章我打算按自己实战排查时的思路来写,把PMS的启动链路、APK安装的完整流程、包管理涉及的数据结构和状态变化,以及日常定位问题时会用的经验都串一遍。不看官方文档散落的细节,也不逐行贴代码,而是讲清楚“它为什么这么设计”和“遇到问题怎么用它”,适合刚转系统开发的工程师,也适合想搞懂安装失败原理的应用层同学。
1. PMS到底在系统里扮演什么角色
1.1 一句话理解PackageManagerService
PackageManagerService并不是一个独立进程,它是运行在 system_server 进程里的一个 Binder 服务,对外通过 IPackageManager 接口给所有应用提供能力。应用层调用的getPackageManager(),底层握住的其实就是这棵Binder树的远端代理。
你可以把它理解成酒店的“前台+档案室”。客人要订房、退房、改房、查房,都得找前台;前台背后有一大堆纸质档案记录每个房间住过谁、什么时候登记的、门禁卡权限开到哪一层。对Android系统来说,每个APK就是一个“客人”,PMS负责登记客人信息、安排房间(UID和数据目录)、发门禁卡(权限和签名校验),还要在客人退房时把档案销掉。
1.2 它负责的几类核心事务
把PMS的活拆开看,主要是下面几块,互相之间有很强的关联性:
- 元信息管理:记录每个已安装包的包名、版本号、UID/GID、签名证书、代码路径、data目录、安装位置等。
- 组件注册:把APK解析出来的 Activity、Service、Receiver、Provider 和 Instrumentation 等组件登记到内部表,供 ActivityManager、Launcher、startActivity 等查询。
- 安装与卸载:主担全量安装、覆盖升级、卸载、清除数据、安装会话管理。
- 权限管理:处理权限申请记录、签名权限校验、运行时权限授予和撤销,以及默认权限规则。
- Intent解析:
queryIntentActivities()、resolveActivity()这类接口的最终执行者,决定一个隐式Intent会命中哪个组件。 - 与installd协同:创建/删除应用数据目录、处理DEX优化,依赖的是一个叫 installd 的native守护进程。
这套职责很重的系统,设计上并非把每个细节都自己做完,而是更像一个“调度中枢”。比如创建目录这种底层操作,PMS自己不碰文件系统,而是通知 installd 去做,原因后面会讲到。
1.3 PMS不在独立进程里,这才是关键
很多刚接触系统源码的同学会有一瞬间的困惑:PMS这么大一个服务,怎么没有自己的独立进程?它跟 ActivityManagerService(AMS)、WindowManagerService(WMS)一样,全部住在 system_server 里。
这个设计有历史原因,也有性能考量。PMS跟AMS之间的交互非常频繁,比如启动Activity时要查包信息、要判断权限,如果拆成两个独立进程,每次都是一次完整Binder跨进程调用,系统关键路径上的延迟会高得离谱。放在同进程里,它们可以直接调用对方的方法,只是需要在锁上做好同步。代价也很明显:system_server是个“巨兽进程”,PMS一旦出现严重的死锁或内存问题,整个系统可能都会重启,而不是只挂掉一个服务。
理解这点之后,再看“为什么安装一个大App时系统偶尔会卡一下”就好理解了:PMS在扫描APK、解析Manifest、写packages.xml的时候,是拿着自己的全局锁的,这个锁会跟AMS的很多操作互相等待。系统整体出现瞬时卡顿,往往就是这些重量级操作在打架。
2. PMS的启动链路:开机时的“清点资产”
2.1 system_server里的启动入口
PMS不是在应用安装时才被创建的,而是开机时由 SystemServer 在startBootstrapServices()阶段启动。这一步非常重要,因为它是整个系统“包知识”的起点。启动入口是静态方法PackageManagerService.main(),传入几个关键参数:installer(连到installd的桥),factoryTest(是不是工厂测试模式),onlyCore(是否只加载核心包)。
main()方法内部会 new 出一个 PMS 实例,构造函数做完一大堆初始化后,再把自己注册到 ServiceManager,这样其他模块就能通过Binder拿到它。换句话说,系统进程起来之后,PMS是“先整理完自己家里的账本,才开门营业”的。
构造函数里干的事非常多,我按顺序挑重点讲:先初始化 Settings 对象,从/data/system/packages.xml和/data/system/packages.list读入上次关机前的包状态;然后检查系统目录结构;接着逐个扫描系统分区和用户数据分区的APK,把解析结果注册进内存表;最后把权限同步一遍,再写回 packages.xml,保证本次扫描结果不丢。
2.2 扫描目录的顺序为什么不能乱
PMS在开机时会对以下几类目录做扫描,顺序很讲究:
/system/framework:框架级APK和共享库,最先加载。/system/priv-app:特权系统应用,拥有普通系统应用没有的权限。/system/app:普通系统应用。/vendor/app、/product/app、/system_ext/app:厂商和产品分区应用。/data/app:用户安装的应用目录。
顺序之所以不能乱,是因为系统应用要先注册好,才能给后扫描的包提供签名权限和共享库依赖。比如一个三方应用声明了某个签名权限,而这个权限是系统应用定义的,那系统应用必须先完成注册,三方应用的权限校验才能通过。
另外,factoryTest参数决定了是否扫描/data/app。工厂测试模式只加载核心系统包,不加载用户数据,这也是产线测试机器启动更快的原因之一。正常情况下,扫描完系统包,还会继续扫描用户分区,并对开机前没有完成的DEX优化任务做收尾。
2.3 首启与二次开机的差别
第一次开机和后面每次开机的开销差别巨大。首次开机/data分区是空的,PMS没有任何历史包袱,每个系统APK都要从头解析一遍Manifest,还要给大量框架包做DEX优化,所以首启慢是正常的。二次及以后的开机,PMS可以借助上次扫描生成的缓存和优化结果,把一部分工作省掉,但仍然会重新解析APK,并不是完全走“读缓存”这条路。
不少ROM团队在优化开机速度时,都会集中在PMS扫描这块做文章,比如增加并行扫描线程、调整DEX优化时机、延迟部分预装应用到后台再解析。我自己调过一台低端机,仅仅是把不必要的预装应用从/system/app挪到/data/app按需安装,开机时间就肉眼可见地快了不少。背后的道理就是:PMS扫描的包越少、优化任务越分散,开机压力就越小。
3. APK安装流程拆到最细
3.1 安装入口:PackageInstaller会话机制
说到安装,很多人第一反应是adb install,但走到系统内部,几乎所有安装入口都收敛到 PackageInstaller 这套会话机制上。它从Android 5.0开始引入,设计思路很像一个“事务”:先创建会话,再往里写APK数据,最后提交,提交失败可以整段回滚。
整个流程拆开是这样的:
- 安装器(比如应用商店,或者我们通过
pm install触发的 shell 层)调用IPackageInstaller的createSession(),传入包名、安装模式、大小、安装位置等参数,得到一个 sessionId。 - 拿 sessionId 打开
PackageInstaller.Session,通过它的 OutputStream 把APK内容写进去。此时文件只会落到一个临时暂存目录,通常是/data/app-staging/vmdlXXX.tmp。 - 数据写完,安装器调用
commit(),真正触发PMS做校验和安装。
这种设计的好处非常明显:大数据量的传输和PMS核心逻辑解耦,安装器可以先下载完整APK再提交;一个会话还能传入多个split APK;提交前可以随时放弃而不污染系统状态。对普通开发者来说,这个模型也解释了为什么“文件还没拷贝完就commit”会导致INSTALL_FAILED_SESSION_INVALID这类错误。
3.2 会话提交后发生了什么
commit()被调用后,PackageInstallerSession 会先做一次“安全检查”,确认会话里的文件真实存在、大小匹配、包名跟SessionParams一致。随后PMS会把这次提交包装成一个安装任务,交给内部的消息循环PackageHandler去排队处理。
PackageHandler 是PMS里面一个容易被人忽略的角色,它维护了一个安装请求队列。所有安装操作不会直接在当前线程执行,而是投递到INIT_COPY消息里排队。这样做的好处是安装请求可以被串行化,避免多个安装任务同时改/data/app导致文件状态错乱。在实际调试中,如果你同时adb install两个App,后一个往往会等前一个完成,这就是PackageHandler在排队。
任务轮到之后,handleStartCopy就上场了:把暂存目录里的APK文件拷贝到正式的/data/app/包名-一串随机后缀/目录下,同时解析一遍基础信息,校验签名,最后进入installPackageTracedLI这个核心方法。
3.3 解析APK:从二进制Manifest到对象
APK本质是一个ZIP包,AndroidManifest.xml是二进制XML格式,不能简单地用文本解析器读。PMS内部使用的解析器是 PackageParser,它专门负责把这套二进制格式读成人能看懂的包对象AndroidPackage。
解析过程不是只读一个Manifest那么简单,它要处理非常多的字段:包名、版本号、minSdk/targetSdk、uses-permission、application下的各种组件、sharedUserId、installLocation、以及 split 分包配置等。遇到有 split APK 的应用,还要解析出 base APK 和各个 feature/config split,最后组合成一个完整的包视图。
这里有个经典坑:Manifest里如果写了系统不认识的新标签,旧版本Android往往会忽略;但如果是标签属性值非法,比如minSdkVersion不是整数,解析阶段就会直接抛异常,安装中断,返回INSTALL_PARSE_FAILED_MANIFEST_MALFORMED。所以当你看到一个安装错误带PARSE_FAILED字样,第一反应应该是:APK的Manifest文件本身有问题,而不是系统空间不够。
3.4 逐项校验:签名、ABI、版本、SharedUserId
解析出包信息后,真正的“审查环节”才开始,PMS会做一整套兼容性和安全性检查。我用实际项目里遇到的问题一个个说:
- 签名校验:安装新包时,如果是覆盖旧版本,必须保证新旧签名一致,否则返回
INSTALL_FAILED_UPDATE_INCOMPATIBLE。Android 7.0之后,系统优先使用 APK Signature Scheme v2/v3 的签名信息做整体校验,老应用没有v2签名才会回退到v1的JAR签名校验。targetSdk越高的应用,对签名方案的要求就越严格。 - ABI检查:APK的
lib/目录里如果有 native so,PMS会跟设备支持的ro.product.cpu.abi列表做匹配。旧设备经常出现“APK在测试机上能装,在真机上 INSTALL_FAILED_NO_MATCHING_ABIS”,基本都是so的ABI目录不匹配。 - 版本检查:覆盖安装时,如果新版本号低于旧版本,默认会拒绝,除非显式带上
INSTALL_ALLOW_DOWNGRADE标志。 - SharedUserId冲突:APK声明了
sharedUserId,如果跟已有包不是同一个签名,会直接被拒绝,因为同一个共享UID组里的包必须签名一致才能互相“信任”。 - 存储空间和目录状态:检查
/data分区可用空间、目标安装位置是否合法。空间不足时返回INSTALL_FAILED_INSUFFICIENT_STORAGE。
这些检查看似零散,但设计上有个共同目标:在改动系统状态之前,把一切能拒绝的理由都找到。这样才能保证安装过程要么成功,要么保持系统原状,不会出现装到一半发现签名不对,留下半残文件的局面。
3.5 扫描与注册:组件正式“上户口”
校验通过后,PMS会调用scanPackageTracedLI()把包完整注册进系统。这一步是安装流程里最重的一环:
- 检查内存里是否已经有同名包,有的话走更新逻辑,没有就走新增逻辑。
- 目标ABI确定后,通过
mInstaller创建或更新应用数据目录/data/user/0/包名/,设备保护存储则是/data/user_de/0/包名/。 - 把解析出来的组件注册进
mActivities、mServices、mReceivers、mProviders这些内部表,之后任何 Intent 查询都能命中它。 - 把包加进
mPackages这个以包名为key的Map,同时在mSettings里生成或更新对应的PackageSetting。 - 如果有 sharedUserId,还要归并到共享用户组,统一分配 UID。
- 更新权限记录,把Manifest里声明的权限、gid等同步给 PermissionManagerService。
- 处理DEX优化任务,
performDexOpt()或通过后台任务执行 dex2oat。 - 最后把
mSettings写回/data/system/packages.xml,发广播通知系统内外:这个包已经可用了。
很多开发者以为“安装成功”就是文件被拷贝了,其实PMS视角里的成功是整套索引全部更新完毕。文件拷贝只是前菜,组件注册、权限绑定、数据目录创建、持久化、广播通知,每一步不到位,应用都算不上真正“安装好了”。
3.6 installd:真正落盘的那个家伙
前面好几次提到 installd,这里单独展开。既然PMS是系统进程,权限已经很高了,为什么创建目录这种事还要麻烦另一个守护进程?答案是SELinux和用户数据隔离。
/data/user/0/包名这种目录不是随便建一个文件夹就行,它需要按照system_app、untrusted_app、platform_app等不同的SELinux domain设置标签,还要设置正确的UID、GID、目录权限。这些涉及内核级安全的操作,如果都放到 system_server 里做,等于把高风险的文件系统操作暴露在核心进程里,出问题就是大问题。
installd 是一个独立的native守护进程,以root身份运行,通过Binder接收 PMS 发来的命令,执行createAppData、restoreconData、clearAppData、dexopt等底层操作。PMS只负责“决定”要不要建目录、建在哪个用户下,installd 负责“执行”并保证结果符合安全策略。这个分层也是Android“系统服务只逻辑处理,底层操作用单独进程”这一思想的典型代表。
4. 包管理:安装之外的“改状态”功夫
4.1 PMS的核心数据结构
做PMS相关开发,最需要先认识的是这几个内存数据结构:
mPackages:ArrayMap<String, AndroidPackage>,包名到包解析结果的映射,是所有包信息查询的第一站。mSettings:持久化配置载体,管着所有包的状态,包括安装路径、版本、disabled状态、共享UID组等。PMS每次关键变更后都会把mSettings写盘。PackageSetting:对应一个已安装包,是“这次安装留下的档案卡”。里面包括codePath、resourcePath、pkgFlags、uid、versionCode、signatures、enabledSetting(是否被禁用)、suspend(是否被冻结)等。mActivities/mServices/mReceivers/mProviders:组件索引表,按组件名查Activity、Service、Receiver、Provider是它干的事。
这些结构之间的关系可以理解为:mPackages是“活的包信息”,mSettings是“落盘的包档案”,组件索引表是“对外提供检索的目录”。排查问题时,如果dumpsys里包还在但组件查不到,大概率是组件注册表出问题,这不常见,但一旦发生,表现就是“应用设置里能看到包,Launcher却找不到图标,startActivity也报错”。
4.2 packages.xml与packages.list
/data/system/packages.xml是PMS持久化状态的心脏。里面记录了每个包的代码路径、版本号、UID、签名摘要、权限列表、enabled状态等。PMS每次安装、卸载、更新、授权都会更新这个文件。注意它是先写备份文件packages-backup.xml,再替换主文件,防止写一半断电导致文件损坏。如果你在调试中手动改坏了这个文件,轻则所有包状态丢失,重则系统起不来。
/data/system/packages.list则偏向给native层使用,内容更精简:一行一个包,字段包括包名、UID、GID、SELinux label、data目录等。installd、netd这些native守护进程都以它为重要输入。
实操建议:在排查“重启后包信息不对”这种问题前,先备份packages.xml,改错了好恢复。另外不要用文本编辑器直接乱改,格式稍微不对,PMS解析失败就会回退到备份甚至重置状态。
4.3 升级、卸载、清除数据对状态的影响
同样是对包做操作,完整安装、覆盖升级、卸载对PMS状态的影响完全不同:
- 完整安装:新增UID,新增
PackageSetting,文件进入/data/app,packages.xml增加一条记录。 - 覆盖升级:
PackageSetting里的版本号和codePath会被更新,但UID和data目录保持不变,所以应用数据能保留下来。这也是为什么“升级不上数据丢失”的诉求通常要靠备份机制,而不是靠PMS。 - 卸载:PMS从
mPackages里移除Package对象,从mSettings里移除PackageSetting,调用 installd 删除用户数据目录。但pm uninstall -k带-k参数时,会保留数据目录,只删代码和注册信息。 - 清除数据:
pm clear或设置里的“清除存储”,不会移除包注册信息,只清空/data/user/0/包名下的数据,并重置权限状态。
我在实际项目里遇到过一种场景:某应用被卸载后重新安装,竟然还能看到旧数据。排查下来是安装时调了pm uninstall -k,虽然界面显示应用没了,但数据目录还在。对普通用户这可能是个隐私隐患,对系统集成商这就是一个明确的产品规则:是否需要持久化数据,要在卸载流程里显式决定,不能把“卸载”和“清数据”完全划等号。
4.4 禁用与停用:不是删除的“软卸载”
PMS还支持一种“半卸载”状态:禁用。pm disable-user --user 0 com.xxx可以把某个应用置为DISABLED_USER,表现是从Launcher消失、无法启动,但包还留在系统里。对应的还有pm enable恢复。
这里有个容易混淆的点:DISABLED状态分好几种,包括DISABLED_UNTIL_USED。后者是系统为了“预装应用首次被使用后再启用”设计的状态,常见于一些默认关闭的系统组件。查看包当前状态可以直接dumpsys package com.xxx,里面有一行enabled=字段。
禁用不等于卸载,这是排查问题时特别值得记住的:如果一个预装应用“不见了”,先用pm list packages -d看看是不是被禁用,而不是直接怀疑安装失败。
4.5 权限授予与安装的关系
APK安装完成后,Manifest里声明的权限会被解析进权限系统。从Android 6.0开始,危险权限不再在安装时全部授予,而是运行时由用户决定。但这不代表PMS就完全不管了:普通权限会在安装时自动授予,签名权限会根据安装者签名和系统签名是否匹配做自动判定,并被记录在权限管理服务里。
安装流程中,grantPermissions那一步会遍历APK声明的权限,逐个决定是“默认授予”还是“等待运行时申请”。对于预装应用,系统还可能通过default-permissions.xml预授权一些敏感权限。很多“首启就要定位权限,不授权连默认页都进不去”的现象,其实都是目标Sdk版本和权限模型共同决定的。
调试时,adb shell pm grant 包名 权限名和pm revoke 包名 权限名是绕开弹窗直接改权限状态的好工具。改完后重启会失效,但排查权限问题非常快。
5. 日常排查:错误码速查与实战经验
5.1 dumpsys package的正确打开方式
adb shell dumpsys package是PMS体检的第一入口,但直接输出非常长,学会缩小范围比会看全量更有用。
dumpsys package com.xxx:只看指定包的信息,包含安装用户、data目录、版本、signing keys、请求的权限等。dumpsys package packages:只看包名列表和基础标识,适合确认包是否被识别。dumpsys package permissions:查看权限授予状态,排查“这个权限到底给没给”。dumpsys package dexopt:查看DEX优化状态,能发现“包还在但odex没生成”一类的问题。dumpsys package installer:查看当前是否有残留的安装会话。
我通常的流程是:先pm list packages | grep 包名确认包是否在,再用dumpsys package 包名看细节,最后根据错误码决定要不要继续查 logcat。大部分安装问题在dumpsys package的输出里就露馅了。
5.2 常见安装错误码对照表
下面是我整理的高频错误码,按我遇到的频率排了序:
| 错误码 | 含义 | 常见触发原因 |
|---|---|---|
| INSTALL_FAILED_UPDATE_INCOMPATIBLE | 覆盖更新不兼容 | 新旧包签名不一致、组件冲突 |
| INSTALL_FAILED_ALREADY_EXISTS | 包已存在 | 安装了同名包且未带替换标志 |
| INSTALL_FAILED_SIGNATURE_MISMATCH | 签名不匹配 | 同一个包名用不同证书签两次 |
| INSTALL_FAILED_VERSION_DOWNGRADE | 版本降级 | 新版本号低于已安装版本 |
| INSTALL_FAILED_NO_MATCHING_ABIS | 没有匹配的ABI | APK只含armeabi,设备是纯arm64 |
| INSTALL_FAILED_INSUFFICIENT_STORAGE | 存储空间不足 | /data分区满或inode耗尽 |
| INSTALL_FAILED_DEXOPT | DEX优化失败 | dex2oat异常、ART缓存损坏 |
| INSTALL_PARSE_FAILED_MANIFEST_MALFORMED | Manifest解析失败 | APK的Manifest二进制格式损坏 |
| INSTALL_FAILED_INVALID_APK | APK无效 | 文件损坏、不是合法ZIP |
| INSTALL_FAILED_USER_RESTRICTED | 用户受限 | 多用户策略禁止该用户安装 |
| INSTALL_FAILED_SESSION_INVALID | 安装会话无效 | commit前数据未写完或会话过期 |
保存这张表很有用。看到错误码先定位到“哪一类”,再去查具体原因,比直接翻源码高效得多。
5.3 几个实战排查案例
我随手分享几个真实遇到的案例。
案例一:某渠道包升级一直失败,报INSTALL_FAILED_UPDATE_INCOMPATIBLE。第一反应怀疑签名,但检查打包脚本发现是重签了,没问题。再细看,新旧包居然声明了不同的sharedUserId,旧包属于sharedUserId=A,新包改成了B。系统认为这属于包身份变化,直接拒绝覆盖。解决方式是让渠道方沿用同一个sharedUserId。
案例二:应用在低端机上安装后闪退,logcat里报“didn't find class”。先怀疑混淆和分包,但最后查dumpsys package dexopt发现这个包压根没有生成odex,安装时DEX优化被跳过了。清除ART缓存重启设备后恢复正常。这类问题常见于OTA升级后遗留的缓存损坏。
案例三:预装应用第一次开机后不显示。第一反应是扫描失败,但看pm list packages里包是存在的,再查dumpsys package才发现enabled=3(DISABLED_UNTIL_USED)。这是预装配置里带了android:enabled和pm disable-until-used的典型结果,不是安装问题。
5.4 logcat快速定位技巧
PMS相关的日志标签主要有PackageManager、PackageManagerService、PackageInstaller、DexoptWrapper、installd。定位安装问题,我建议第一次就开启这几个标签的过滤:
adb logcat -s PackageManager PackageManagerService PackageInstaller
观察日志里是否出现Package [com.xxx] (123) added或者installPackage等关键字。如果日志停在某个校验阶段迟迟不动,再用adb shell dumpsys package installer看会话状态,判断是不是卡在文件暂时区或者等待某个广播。
给个独家小技巧:遇到“安装过程卡死”的问题,不要急着杀pm进程,先抓一份dumpsys package installer和主线程堆栈,看看PackageHandler队列里排了多少任务。我见过不少“卡死”其实是排队任务太多,并不是真的死锁。
6. 我踩过的一些坑与体会
6.1 别在主线程碰PMS
普通应用开发里,PackageManager的接口虽然有个BinderStub的包装,但很多调用走到PMS之后都会做磁盘扫描或全表遍历,耗时不可控。getInstalledPackages()、queryIntentActivities()这种接口在包特别多的设备上,几百毫秒很正常。在主线程直接调,流畅度直接被毁。
我自己写系统工具时,凡是涉及全量查询的,一律放到子线程,并且做好结果缓存。PMS内部有大量锁,一个慢查询还可能导致其他调用排队,牵一发动全身。
6.2 安装慢、卡、反复失败未必是APK问题
常见的“安装失败”背后,可能是/data分区 inode 满了、包管理数据库损坏、SELinux标签不正确、甚至低端机器上 dex2oat 排队过长。遇到安装问题,我现在的检查顺序是:先看错误码,再看dumpsys,然后查logcat,最后才怀疑APK本身。顺序反过来,很容易在应用层折腾半天才发现是系统状态异常。
6.3 写系统代码时的一些习惯
做PMS相关定制时会踩到锁的坑。PMS内部有很多锁,像是包信息锁和设置锁,调用顺序如果不一致,两个线程互相等待就可能死锁。我的习惯是:任何自己写的代码里,持有PMS相关锁后绝不再做Binder调用。Binder调用是异步的,可能跨进程、可能阻塞,在锁里做,等于把整个系统大门锁死等着电话铃响。
另外,改包状态类的操作,尽量走系统提供的“事务式”入口,比如PackageInstaller会话,而不是直接改packages.xml。绕过PMS的约束写文件,短期看着能用,下次系统重启或做一次清理就原形毕露。
结尾
做Android系统开发这些年,PMS算是我反复“踩进去”又“爬出来”最深的一个模块。它不像业务代码那样有明确的产品逻辑,更像一张巨大的状态机,把APK的每一点状态变化都记录得清清楚楚。我个人的体会是:别想着一下子读懂所有源码,先把它“管什么、怎么启动、怎么安装、怎么记录状态”这条主线串起来,再带着具体问题去查,会顺畅很多。
最后再分享一个不知道算不算冷门的技巧:当你怀疑应用“装了但没完全装好”时,直接对比pm path 包名返回的路径和/data/app下真实的目录是否存在。命令返回正常但目录消失,基本就是文件系统状态跟PMS内存表不一致,这种问题靠应用日志查不出来,只能在系统层定位。PMS就是一栋大楼的档案室,很多诡异现象,从它这里找答案往往最快。