升级依赖后出现android.app.ResourcesManager相关ANR死锁问题求助
问题背景
近期在Google Play Console中发现应用的ANR量大幅上升,不同触发原因的报告存在一个共同点:主线程和工作线程都被阻塞,且至少有一个线程始终阻塞在访问android.app.ResourcesManager的相关方法上。
示例1
"main" prio=5 tid=1 Blocked
at android.app.ResourcesManager.getAdjustedDisplay (ResourcesManager.java:353) at android.app.ContextImpl.getDisplayNoVerify (ContextImpl.java:2559) at android.view.WindowManagerImpl.getDefaultDisplay (WindowManagerImpl.java:164) at org.n277.lynxlauncher.helper.DeviceUtilities.updateSystemHelper (DeviceUtilities.java:225) at org.n277.lynxlauncher.views.DragContainerFrameLayout.onApplyWindowInsets (DragContainerFrameLayout.java:231) at android.view.View.dispatchApplyWindowInsets (View.java:11399) at android.view.ViewGroup.dispatchApplyWindowInsets (ViewGroup.java:7393) at android.view.ViewGroup.newDispatchApplyWindowInsets (ViewGroup.java:7418) at android.view.ViewGroup.dispatchApplyWindowInsets (ViewGroup.java:7400) at android.view.ViewGroup.newDispatchApplyWindowInsets (ViewGroup.java:7418) ...
"Lynx-Worker" prio=5 tid=17 Blocked
at android.app.ResourcesManager.getOrCreateResources (ResourcesManager.java:865) at android.app.ResourcesManager.getResources (ResourcesManager.java:982) at android.app.ActivityThread.getTopLevelResources (ActivityThread.java:2411) at android.app.ApplicationPackageManager.getResourcesForApplication (ApplicationPackageManager.java:1527) at android.app.ApplicationPackageManager.getDrawable (ApplicationPackageManager.java:1277) at android.app.ApplicationPackageManager.loadUnbadgedItemIcon (ApplicationPackageManager.java:2684) at android.app.ApplicationPackageManager.loadItemIcon (ApplicationPackageManager.java:2655) at android.content.pm.PackageItemInfo.loadIcon (PackageItemInfo.java:516) at android.content.pm.ComponentInfo.loadDefaultIcon (ComponentInfo.java:256) at android.app.ApplicationPackageManager.loadUnbadgedItemIcon (ApplicationPackageManager.java:2687) at android.app.ApplicationPackageManager.loadItemIcon (ApplicationPackageManager.java:2655) at android.content.pm.PackageItemInfo.loadIcon (PackageItemInfo.java:516) at android.content.pm.LauncherActivityInfo.getIcon (LauncherActivityInfo.java:117) at org.n277.lynxlauncher.visual.c.d.v (SourceFile:1) at org.n277.lynxlauncher.visual.c.d.u (SourceFile:3) ...
主线程调用windowManager.getDefaultDisplay().getSize()方法获取屏幕尺寸用于测量(适配Android R之前的设备),工作线程则通过LauncherActivityInfo获取应用图标。
示例2
"main" prio=5 tid=1 Blocked
at android.view.WindowManagerImpl.getCurrentBounds (WindowManagerImpl.java:241) at android.view.WindowManagerImpl.getCurrentWindowMetrics (WindowManagerImpl.java:235) at org.n277.lynxlauncher.helper.DeviceUtilities.getUsableHeight (DeviceUtilities.java:190) at org.n277.lynxlauncher.views.FullScreenFrameLayout.onMeasure (FullScreenFrameLayout.java:114) at android.view.View.measure (View.java:25532) at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6957) at android.widget.FrameLayout.onMeasure (FrameLayout.java:194) at android.view.View.measure (View.java:25532) at android.widget.RelativeLayout.measureChildHorizontal (RelativeLayout.java:735) ...
"Lynx-Worker" prio=5 tid=17 Blocked
at android.app.ResourcesManager.createResources (ResourcesManager.java:846) at android.app.ResourcesManager.getResources (ResourcesManager.java:947) at android.app.ActivityThread.getTopLevelResources (ActivityThread.java:2296) at android.app.ApplicationPackageManager.getResourcesForApplication (ApplicationPackageManager.java:1672) at android.app.ApplicationPackageManager.getDrawable (ApplicationPackageManager.java:1437) at android.app.ApplicationPackageManager.loadUnbadgedItemIcon (ApplicationPackageManager.java:2950) at android.app.ApplicationPackageManager.loadUnbadgedItemIcon (ApplicationPackageManager.java:2953) at android.app.ApplicationPackageManager.loadItemIcon (ApplicationPackageManager.java:2929) at android.content.pm.PackageItemInfo.loadIcon (PackageItemInfo.java:271) at android.content.pm.LauncherActivityInfo.getIcon (LauncherActivityInfo.java:119) at org.n277.lynxlauncher.visual.c.d.v (SourceFile:1) at org.n277.lynxlauncher.visual.c.d.u (SourceFile:3) ...
本次主线程调用windowManager.getCurrentWindowMetrics()获取屏幕尺寸用于测量,工作线程依然是通过LauncherActivityInfo获取应用图标。
示例3
和示例2的场景完全一致,只是额外多了一个普通线程也卡在和工作线程完全相同的调用栈上。
已知条件
该问题在上次应用更新前从未出现,未修改过线程相关的逻辑,仅升级了以下几个依赖:
- buildToolsVersion 从30.0.0升级到30.0.2
- com.android.billingclient:billing 从3.0.3升级到4.0.0
- androidx.appcompat:appcompat 从1.2.0升级到1.3.0
- androidx.recyclerview:recyclerview 从1.1.0升级到1.2.1
目前判断属于多线程访问android.app.ResourcesManager类时产生的死锁问题,怀疑WindowManagerImpl.getCurrentBounds内部也访问了ResourcesManager,只是调用栈没有明确显示。
现在的矛盾点:将应用图标加载逻辑移到主线程会阻塞主线程,LauncherActivityInfo.getIcon方法确实需要访问对应目标应用的资源(而非当前调用方应用的资源),且工作线程还承载了很多其他任务,大量调用的API方法都可能隐式内部访问资源。
解决方案
该问题是AppCompat 1.3.0版本引入的已知缺陷,根源是该版本开始主线程初始化Resources全局配置时会加锁,而子线程访问第三方应用资源时也会请求ResourcesManager的全局锁,两者锁竞争甚至形成死锁最终触发ANR。
可行解决办法按优先级排序:
- 降级或升级AppCompat版本
直接将androidx.appcompat:appcompat回退到1.2.0版本即可消除问题,是成本最低的验证方案。如果需要使用高版本特性,可升级到1.4.0及以上版本,该问题在后续正式版中已修复。 - 缓存屏幕参数减少主线程锁请求
屏幕宽高、可用高度这类参数不会高频变化,可在应用启动主线程空闲阶段一次性计算完成后缓存,后续布局测量逻辑直接读取缓存值,不需要每次都调用getDefaultDisplay或getCurrentWindowMetrics,大幅降低主线程请求ResourcesManager锁的频次。 - 第三方资源访问加统一串行锁
如果暂时不能调整依赖版本,给所有子线程调用LauncherActivityInfo.getIcon这类需要访问外部资源的逻辑加统一的串行锁,确保同一时间只有一个子线程请求ResourcesManager锁,降低和主线程锁竞争的概率。注意不要把主线程逻辑加入该锁范围,避免形成新的死锁。 - 预加载应用图标缓存
应用启动阶段分批预加载常用应用的图标存入内存/磁盘缓存,后续列表渲染时直接读取缓存,减少工作线程运行过程中实时加载图标的频次。
内容的提问来源于stack exchange,提问作者nils277

