9. 安装流程(下):dex2oat 优化、权限授予、安装完成广播的发送
上一章我们聊到 APK 的解析和签名校验,文件已经拷到了 data 分区。但这时候应用还不能跑——你想想看,Android 用的是 ART 运行时,它不认 DEX 字节码,得先转成本地机器码。这一步,就是 dex2oat 干的活。
9.1 dex2oat:从字节码到机器码
dex2oat 的全称是「Dalvik Executable to Optimized Art Tool」。说白了,它就是个编译器。把 APK 里的 classes.dex 文件,编译成 .oat 文件,也就是 ART 能直接执行的 ELF 格式。
我个人习惯把 dex2oat 分成三个阶段来看:
- 输入准备:读取 DEX 文件,加载到内存
- 编译优化:根据策略做 AOT 或 JIT 编译
- 输出落地:生成 .oat 和 .vdex 文件
这里有个关键参数叫 compiler-filter。它决定了编译到什么程度。我列个表,你一看就明白:
| filter 值 | 含义 | 典型场景 |
|---|---|---|
| verify | 只做字节码验证 | 首次安装、低内存设备 |
| quicken | 验证 + 快速优化 | 部分系统应用 |
| speed | 全量 AOT 编译 | 预置应用、Play Store 下载 |
| speed-profile | 基于 profile 的热点编译 | 日常安装(默认) |
| everything | 所有方法都编译 | 系统镜像构建 |
嗯,这里要注意。Android 7.0 之后引入了 混合编译 模式。不是一股脑全编译,而是先跑 JIT,收集热点方法,再触发 dex2oat 做 profile-guided 编译。这样既保证了首次安装快,又保证了长期运行性能。
核心流程:PackageManagerService 调用 Installer 的 dexopt 方法 → Installer 通过 socket 通知 installd → installd fork 出 dex2oat 进程 → dex2oat 执行编译 → 返回结果给 PMS。
我在项目中遇到过一个问题:某款手机安装大型游戏时,dex2oat 跑了整整两分钟,用户以为卡死了。后来发现是 compiler-filter 被设成了 speed,全量编译导致。改成 speed-profile 后,安装时间降到 15 秒,性能几乎没有损失。
9.2 权限授予:谁可以碰我的数据?
APK 装好了,代码也优化完了。接下来要处理权限。这一步很多人容易忽略——其实权限授予发生在 应用首次启动之前。
权限分两类:
- 安装时权限(Install Permissions):比如
INTERNET、VIBRATE。系统直接授予,不需要用户确认。 - 运行时权限(Runtime Permissions):比如
CAMERA、LOCATION。需要弹窗让用户同意。
在安装流程里,PMS 会做这么几件事:
- 读取 APK 的
AndroidManifest.xml,提取<uses-permission>标签 - 检查每个权限是否在
platform.xml或permissions.xml中定义 - 对于安装时权限,直接写入
packages.xml的<perms>节点 - 对于运行时权限,标记为
granted=false,等用户弹窗时再改
小技巧:如果你在开发系统应用,想跳过运行时权限弹窗,可以在 AndroidManifest.xml 里加 android:installPermission="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"。不过别乱用,这需要系统签名。
我曾经踩过一个坑:某个第三方应用声明了 READ_EXTERNAL_STORAGE,但它的 targetSdkVersion 是 28,按道理应该弹窗。结果用户反馈说没弹窗,直接就能读 SD 卡。查了半天,发现是厂商在 platform.xml 里把这个权限设成了 granted="true"。嗯,这种「隐式授权」在定制 ROM 里很常见,但不符合 CTS 规范。
9.3 安装完成广播:通知全世界
权限搞定了,最后一步是发广播。PMS 会发送 ACTION_PACKAGE_ADDED 或 ACTION_PACKAGE_REPLACED,告诉所有注册了 BroadcastReceiver 的应用:有新兄弟来了。
广播发送的细节,我总结成三步:
- 构造 Intent:带上包名、uid、安装来源等 extra 信息
- 设置 flag:
FLAG_RECEIVER_REGISTERED_ONLY或FLAG_RECEIVER_INCLUDE_BACKGROUND - 调用 sendBroadcast:走 ActivityManagerService 的广播分发逻辑
这里有个容易忽略的点:广播是异步的。PMS 发完广播后,安装流程就返回了。但广播接收器可能还没执行。如果你在广播里做耗时操作(比如写数据库),记得用 goAsync() 或者开线程。
注意:Android 8.0 之后,隐式广播(没有指定包名)大部分被禁用了。如果你的应用监听 ACTION_PACKAGE_ADDED,必须用 registerReceiver 动态注册,不能在 manifest 里静态声明。除非你加了 intent-filter 的 priority 属性——但说实话,这招在 Android 14 上也不灵了。
我记得有一次调试一个 Launcher 应用,它需要监听新安装的应用来更新桌面图标。结果在 Android 12 上,广播死活收不到。后来发现是 Launcher 的 BroadcastReceiver 没有用 ContextCompat.registerReceiver 注册,漏了 RECEIVER_EXPORTED flag。加上就好了。
9.4 整体流程回顾
把这三步串起来,安装流程的后半段就是:
- dex2oat 把 DEX 编译成 OAT
- PMS 解析权限并写入配置文件
- 发送安装完成广播
- 返回安装结果给调用方(比如 PackageInstaller)
下面这张图,是我自己画的流程概览:
你看,整个流程环环相扣。dex2oat 是性能的基石,权限是安全的门槛,广播是生态的桥梁。任何一个环节出问题,轻则应用闪退,重则系统卡死。
我个人建议,如果你在定制 ROM 或者做系统优化,重点关注 dex2oat 的 compiler-filter 配置。很多厂商为了省空间,把 filter 设成 verify,结果应用跑起来卡成 PPT。反过来,如果全设成 everything,首次开机时间又太长。找到平衡点,才是真功夫。
避坑指南:我曾经在 Android 11 上遇到一个 bug——安装应用后广播没发出来。查 log 发现是 PMS 在写 packages.xml 时发生了 IO 异常,导致流程提前 return 了。所以,如果你发现广播收不到,先检查 packages.xml 的完整性。这个文件在 /data/system/ 下,可以用 cat 看看末尾有没有 </packages> 闭合标签。
好了,安装流程的下半段就讲到这里。你理解了 dex2oat 的编译策略、权限的分级授予、以及广播的异步特性,基本上就能应对大部分安装相关的问题了。