27. 源码阅读方法:如何高效阅读 PMS 源码,推荐工具与切入点
说实话,PMS 的源码量是真的大。我第一次硬啃的时候,差点把自己劝退了。几万行代码,各种回调、跨进程通信,光 PackageManagerService.java 这一个文件就够你喝一壶的。
但后来我发现,读源码不是靠蛮力。你得有方法,有工具,还得选对切入点。今天我就把这几年的经验掰开揉碎了讲给你听。
一、先别急着读代码,把骨架搭起来
我个人的习惯是,拿到一个大型源码模块,第一件事不是逐行看,而是先画图。你想想看,PMS 的核心流程就那么几条:安装、卸载、查询、权限管理。你先把这些主干理清楚,再往里面填细节。
这里我画了一张 PMS 的核心架构图,帮你快速建立全局观:
这张图我建议你存下来。每次读代码迷路的时候,回来看看自己在哪个层级,心里就有底了。
二、推荐工具:工欲善其事,必先利其器
读源码的工具,我踩过不少坑。最开始用记事本,后来用 Eclipse,再后来才找到趁手的家伙。这里我直接给你结论:
| 工具 | 用途 | 我的评价 |
|---|---|---|
| Android Studio / IntelliJ IDEA | 主阅读器 | 必须用。跳转、查找、类型层次,一个都不能少 |
| Source Insight | 大型源码浏览 | 老牌工具,适合 AOSP 全量代码。我偶尔切过去用 |
| vim + ctags / cscope | 快速定位 | 如果你习惯命令行,这套组合拳很猛 |
| Draw.io / Excalidraw | 画流程图 | 读懂了就画下来,不然过两天就忘 |
三、切入点:从哪里开始读?
很多新手问我:「PMS 那么大,我该先看哪个文件?」我的回答是:别从 PackageManagerService.java 的第一行开始读。那样你会疯掉的。
我建议你按这个顺序切入:
1. 从 install 流程切入
安装是 PMS 最核心的功能,也是理解整个系统的钥匙。你跟着一个 APK 从 adb install 到安装完成,基本就把 PMS 的骨架摸透了。
关键方法:
// 入口
public void installPackage(String originPath, ...)
// 核心处理
private void installPackageTracedLI(...)
// 权限和设置
private void installNewPackageLIF(...)
// 底层调用
private void installPackageLIF(...)
我曾经在调试一个安装失败的问题时,就是顺着这个链路一路追下去,最后发现是 Installer 层的磁盘空间检查出了 bug。嗯,这种经历会让你对代码印象深刻。
2. 从 query 流程切入
如果你对安装不感兴趣,也可以从查询流程入手。比如 getInstalledPackages() 是怎么拿到已安装应用列表的?这个流程相对简单,适合热身。
// 查询已安装应用
public List<PackageInfo> getInstalledPackages(int flags, int userId)
// 内部实现
private PackageInfo generatePackageInfo(...)
3. 从权限管理切入
权限管理是 PMS 里比较独立的一块。从 Android 6.0 的运行时权限开始,这部分代码改动很大。如果你对权限机制感兴趣,可以从 grantRuntimePermission() 开始看。
PermissionManagerService,它是从 PMS 拆分出来的。读的时候注意两个类之间的交互,别只看一个。
四、阅读技巧:怎么读才高效?
工具和切入点都有了,接下来就是具体怎么读的问题。我总结了几个实用技巧:
- 先读注释,再读代码。 AOSP 的注释质量很高,尤其是 PMS 这种核心模块。注释里往往写了设计意图和注意事项。
- 善用「Find Usages」。 看到一个方法不知道干嘛的,右键 -> Find Usages,看看谁调了它,它调了谁。调用关系一目了然。
- 不要一次读太多。 我给自己定的规矩是:一次只读一个流程,不超过 200 行。读完了画图,画完了再读下一个。
- 关注异常处理。 很多坑都在 catch 块里。我遇到过一个问题,就是某个异常被吞掉了,导致安装失败但没有任何日志。排查了两天才找到。
PackageInstallerSession 中的状态机逻辑。结果调试时发现安装进度卡在某个状态,怎么都过不去。后来才发现是状态转换条件没满足。所以,遇到状态机一定要画状态图!
五、实战:一个完整的阅读示例
光说不练假把式。我们以「安装一个 APK」为例,走一遍完整的阅读流程:
- 找到入口:
PackageManagerService.installPackage() - 跟踪调用链: 这个方法会调用
installPackageTracedLI(),然后到installNewPackageLIF() - 关注关键对象: 在
installNewPackageLIF()中,会创建PackageParser.Package对象,解析 APK 的 AndroidManifest.xml - 权限处理: 接着调用
grantPermissions()为应用授予权限 - 持久化: 最后通过
Settings.writeLPr()将安装信息写入packages.xml
你看,这样走一遍,你对 PMS 的安装流程就有了一个完整的认识。下次遇到安装相关的问题,你就知道该从哪里查起了。
六、总结一下
读 PMS 源码,说白了就是三件事:选对工具、找对切入点、用对方法。我个人觉得,最难的不是代码本身,而是面对庞大代码时的心理压力。你只要记住,再大的系统也是由一个个小模块组成的,一个一个啃,总能啃完。
最后送你一句话:源码面前,了无秘密。但前提是,你得知道怎么读。