27. 源码阅读方法:如何高效阅读 PMS 源码,推荐工具与切入点

说实话,PMS 的源码量是真的大。我第一次硬啃的时候,差点把自己劝退了。几万行代码,各种回调、跨进程通信,光 PackageManagerService.java 这一个文件就够你喝一壶的。

但后来我发现,读源码不是靠蛮力。你得有方法,有工具,还得选对切入点。今天我就把这几年的经验掰开揉碎了讲给你听。

一、先别急着读代码,把骨架搭起来

我个人的习惯是,拿到一个大型源码模块,第一件事不是逐行看,而是先画图。你想想看,PMS 的核心流程就那么几条:安装、卸载、查询、权限管理。你先把这些主干理清楚,再往里面填细节。

核心原则: 先看流程,再看类,最后看方法。

这里我画了一张 PMS 的核心架构图,帮你快速建立全局观:

PMS 核心架构与阅读切入点 入口层:PackageManagerService 安装流程 卸载流程 查询/解析 PackageInstallerSession InstallArgs / Installer PackageRemovedInfo DeletePackageAction PackageParser PackageSetting / Settings 数据层:Settings(XML持久化) packages.xml / packages.list / package-restrictions.xml 底层:Installer(installd守护进程)

这张图我建议你存下来。每次读代码迷路的时候,回来看看自己在哪个层级,心里就有底了。

二、推荐工具:工欲善其事,必先利其器

读源码的工具,我踩过不少坑。最开始用记事本,后来用 Eclipse,再后来才找到趁手的家伙。这里我直接给你结论:

工具 用途 我的评价
Android Studio / IntelliJ IDEA 主阅读器 必须用。跳转、查找、类型层次,一个都不能少
Source Insight 大型源码浏览 老牌工具,适合 AOSP 全量代码。我偶尔切过去用
vim + ctags / cscope 快速定位 如果你习惯命令行,这套组合拳很猛
Draw.io / Excalidraw 画流程图 读懂了就画下来,不然过两天就忘
我的小技巧: 在 Android Studio 里,把 PMS 相关的文件加到「Bookmarks」里。按 F3 可以快速切换,比来回翻目录快多了。

三、切入点:从哪里开始读?

很多新手问我:「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 块里。我遇到过一个问题,就是某个异常被吞掉了,导致安装失败但没有任何日志。排查了两天才找到。
避坑指南: 我曾经在阅读 PMS 的安装流程时,忽略了 PackageInstallerSession 中的状态机逻辑。结果调试时发现安装进度卡在某个状态,怎么都过不去。后来才发现是状态转换条件没满足。所以,遇到状态机一定要画状态图!

五、实战:一个完整的阅读示例

光说不练假把式。我们以「安装一个 APK」为例,走一遍完整的阅读流程:

  1. 找到入口: PackageManagerService.installPackage()
  2. 跟踪调用链: 这个方法会调用 installPackageTracedLI(),然后到 installNewPackageLIF()
  3. 关注关键对象:installNewPackageLIF() 中,会创建 PackageParser.Package 对象,解析 APK 的 AndroidManifest.xml
  4. 权限处理: 接着调用 grantPermissions() 为应用授予权限
  5. 持久化: 最后通过 Settings.writeLPr() 将安装信息写入 packages.xml

你看,这样走一遍,你对 PMS 的安装流程就有了一个完整的认识。下次遇到安装相关的问题,你就知道该从哪里查起了。

六、总结一下

读 PMS 源码,说白了就是三件事:选对工具、找对切入点、用对方法。我个人觉得,最难的不是代码本身,而是面对庞大代码时的心理压力。你只要记住,再大的系统也是由一个个小模块组成的,一个一个啃,总能啃完。

最后送你一句话:源码面前,了无秘密。但前提是,你得知道怎么读。


公众号:蓝海资料掘金营,微信deep3321