熟悉 MultiProcess 开发的你应该不会对 AMS#getRunningAppProcesses 这个接口感到陌生,虽然从高级 Android 版本开始,App 层已经无法获取其他 UID 应用的进程信息,但也多进程 App 也经常需要获取同个 UID 下其他 Process 的状态,这也是这个接口现在存在的意义。然而这个标准接口却一直埋着一个 Null-check 的坑,时不时就会导致 NPE Crash,而且还会反复在同个坑里跌倒。
现有问题
根据现在最新 master 分支的 AOSP 代码,AMS#getRunningAppProcesses 这个 API 依然没有添加 @Nullable 注解(而同个类下其他 API 都分别带有 Nullable/NonNull 注解),所以导致每次调用这个接口的时候,IDE 都不会发出 Null-check 的 Inspection,时间一久,是个正常人都会忘了添加 Null-check 代码。
不巧的是,如果没有特别深刻的印象,这个接口从语义上,很容易就让人产生天然的 NonNull 的错觉:毕竟,在 App 内获取 App 所有进程的信息,至少也得包含当前调用进程自己的信息吧。然而就是这么反直觉,这个 API 确实是会返回 null 的:
1 | /** |
其实原因也不是很复杂,AMS 进程所保持的 App 的 ProcessRecord 清单,并不是严格跟 App 进程的声明周期对应的。比如有个 AMS 检查 App 相机权限的 AOSP Bug 就是因为 App 进程切换太频繁,导致 AMS 里持有的该 App 的 ProcessRecord 记录实际上是上个 pid 的旧数据,从而导致 AMS 没有给当前 pid 的 App 进行正确的相机权限授权,最终导致了该 App 出现相机黑屏。(具体 Bug Report 见:Fix race condition in UidRecord cleanup)
所以,如果不给 AMS#getRunningAppProcesses 加上 @Nullable,我感觉这里的 NPE 悲剧会反复重演。实际上我自己就是如此,而且我这里可不是故意把情况说得那么糟糕,毕竟 Google 自己挖的这个坑,ta 自己也跳了不少次了。
AOSP 现有调用清单
截止目前,在 AOSP master 分支发现以下几处非 UnitTest 的 Null-check 遗漏代码:
- com.android.server.media.MediaResourceMonitorService.MediaResourceMonitorImpl#getPackageNamesFromPid
- com.android.server.tv.tunerresourcemanager.TunerResourceManagerService#checkIsForeground
- com.android.packageinstaller.wear.WearPackageIconProvider#isSystemApp