一、ANR 核心信息定位
首先提取 ANR 日志中的关键标识,明确问题边界:
| 关键字段 | 取值内容 | 意义解读 |
|---|---|---|
| ANR 类型 | Input dispatching timed out | 输入事件分发超时(系统向应用投递触摸 / 按键等输入事件,应用 5 秒内未响应) |
| 受影响 Activity | com.mytech.mynavi/.map.ui.page.navi.MainActivity | 前台导航页面(地图类页面,涉及渲染、定位等耗资源操作) |
| 核心异常 | does not have a focused window | 应用无 “聚焦窗口”,输入事件无法投递到具体窗口(直接触发分发超时) |
| 进程状态 | Foreground: Yes, PID=16720, UID=1000 | 前台进程(系统优先分配资源),但仍触发 ANR,说明进程内部或系统负载异常 |
| 进程运行时长 | Process-Runtime: 4983595ms (约 83 分钟) | 长期运行进程,可能存在线程 / 内存泄漏累积问题 |
二、关键日志线索拆解
1. CPU 资源竞争:应用与系统进程双高负载
CPU 使用率日志显示 系统整体负载过高,直接挤压输入事件处理的时间片:
-
目标进程(16720) :CPU 占比 45% (30% 用户态 + 14% 内核态),远超前台进程合理阈值(一般建议 ≤20%),说明进程内存在 密集计算或线程阻塞。
- 用户态高:可能是主线程执行地图渲染、路线计算等耗时业务,或子线程(如
RenderThread、GLThread)抢占 CPU。 - 内核态高:可能是频繁的系统调用(如 Binder 通信、文件 IO、GPU 交互)。
- 用户态高:可能是主线程执行地图渲染、路线计算等耗时业务,或子线程(如
-
系统进程:
system_server(27%):负责窗口管理、输入分发的核心进程,其高负载会直接延迟输入事件从系统到应用的投递。surfaceflinger(12%)、logd(12%):分别负责 UI 合成和日志打印,高占比说明系统 UI 渲染或日志输出存在瓶颈。
结论:CPU 资源被应用业务(地图导航)和系统进程抢占,导致应用主线程无法及时获取时间片处理输入事件。
2. 窗口焦点丢失:Input 事件 “无家可归”
ANR 日志明确标注 “does not have a focused window” ,这是输入分发超时的 直接原因。Android 输入事件仅能投递到 “聚焦窗口”(Focused Window),若窗口无焦点,系统会直接判定为 “无法处理输入” 并触发 ANR。
结合应用场景(导航地图页面),可能的焦点丢失原因:
- 窗口初始化阻塞:
MainActivity在onCreate/onResume中执行 地图初始化(如libmap.so加载瓦片数据)、定位服务绑定 等耗时操作,阻塞Window的初始化流程。Android 中,窗口需通过WindowManager完成addView并调用setFocusable(true)后才能获取焦点,若主线程被业务阻塞,焦点设置会延迟甚至失败。 - SurfaceView 焦点冲突:地图应用常用
SurfaceView进行 GPU 渲染(日志中存在GLThread 534/546),SurfaceView是 “独立窗口”,若其focusable属性配置错误(如抢占主窗口焦点但未正确处理),会导致主窗口(MainActivity)失去焦点。 - 生命周期异常:进程长期运行(83 分钟),可能存在 Activity 重建但窗口未复用 问题(如内存不足导致窗口回收,重建时焦点未恢复)。
3. 线程爆炸:144 个线程引发上下文切换灾难
日志中 DALVIK THREADS 显示进程存在 144 个线程,远超常规应用合理线程数(建议 ≤50 个),主要问题:
-
线程类型冗余:
- 业务线程:
Navi-Thread-Pool-0~12(13 个)、pool-xx-thread-x(20 + 个)、Timer-0~7(8 个),大量线程用于导航计算、定时任务,存在线程池配置不合理(核心线程数过高)。 - 系统 / SDK 线程:
Binder:16720_1~7(7 个)、GLThread(2 个)、RenderThread(1 个)、AdrenoOsUtils(GPU 相关,10 + 个),线程间上下文切换(CPU 调度开销)会占用大量时间片。
- 业务线程:
-
主线程间接阻塞:过多线程抢占 CPU,即使主线程优先级(
nice=-10)高于普通线程,仍可能因 “调度延迟” 无法及时响应输入事件(输入事件需主线程处理,优先级最高但需等待 CPU 调度)。
4. 内存与 Native 层线索:非主因但存在隐患
- 内存压力:
/proc/pressure/memory显示some avg60=0.04、full avg60=0.02,内存压力较低;堆内存43% free (25MB/45MB),无 OOM 风险,内存不足非主因。 - Native 层风险:应用加载大量 Native 库(
libmap.so、libapmnative.so、libGwiVdr.so等),日志中存在Thread-40041等未附加线程在libmap.so中执行nanosleep(Native 睡眠)。若主线程通过 JNI 调用 Native 层耗时操作(如地图路线计算),会直接阻塞输入处理(Native 耗时不触发 Looper 监控,但占用主线程时间)。
5. 主线程状态:看似正常,实则 “消息队列堆积”
Main 线程(tid=1)的调用栈显示:
at android.os.MessageQueue.nativePollOnce(Native method)
at android.os.MessageQueue.next(MessageQueue.java:335)
at android.os.Looper.loop(Looper.java:183)
看似主线程在正常等待消息(Looper.loop),但结合 CPU 高占比,实际是 消息队列中存在耗时消息:
- 主线程正在处理地图更新(如
onLocationChanged后的 UI 刷新)、导航路线重绘等耗时任务,导致后续输入事件(如触摸滑动)被阻塞在队列中,超过 5 秒触发 ANR。
三、根本原因总结
综合以上线索,ANR 的 核心因果链 如下:
- 业务层耗时阻塞窗口初始化:
MainActivity导航页面在主线程执行地图初始化、定位服务绑定,导致窗口(Window)未及时获取焦点(does not have a focused window)。 - CPU 资源竞争加剧延迟:应用 45% CPU 占比(地图渲染、多线程)+ 系统进程高负载(
system_server27%),导致主线程无法获取时间片处理输入事件。 - 线程爆炸放大调度开销:144 个线程引发频繁上下文切换,进一步挤压主线程的输入处理时间。
- 输入事件投递失败:系统因 “无聚焦窗口” 无法投递输入事件,或事件被阻塞在主线程消息队列,最终触发
Input dispatching timed out。
四、排查与解决方案
1. 紧急修复:解决窗口焦点与主线程阻塞
(1)将耗时业务移至子线程,确保窗口快速初始化
-
地图初始化:将
libmap.so加载、地图瓦片预加载、定位服务绑定等操作,通过AsyncTask或 独立线程池 执行,避免阻塞onCreate/onResume(窗口焦点依赖生命周期完成)。// 错误示例:主线程初始化地图 @Override protected void onResume() { super.onResume(); mapView.initMap(); // 耗时操作,阻塞窗口初始化 } // 正确示例:子线程初始化,主线程仅做UI绑定 @Override protected void onResume() { super.onResume(); Executors.newSingleThreadExecutor().submit(() -> { mapView.initMap(); // 子线程执行耗时初始化 runOnUiThread(() -> { mapView.bindUi(); // 主线程更新UI,快速完成窗口就绪 getWindow().getDecorView().requestFocus(); // 主动请求焦点 }); }); } -
主动请求焦点:在窗口初始化完成后,主动调用
getWindow().getDecorView().requestFocus(),确保WindowManager标记窗口为 “聚焦”。
(2)优化主线程消息处理,避免输入阻塞
- 拆分耗时 UI 操作:导航页面的路线重绘、位置更新等高频操作,通过
postDelayed或Choreographer分帧执行,避免单次操作耗时超过 16ms(Android 屏幕刷新率 60fps 对应的单帧时间)。 - 优先级管控:输入事件(如
onTouchEvent)的处理逻辑需极简,避免在输入回调中执行网络请求、数据库查询等耗时操作。
2. 中期优化:降低 CPU 负载与线程数量
(1)线程池规范化,减少冗余线程
- 合并线程池:将
Navi-Thread-Pool、pool-xx-thread-x等业务线程池合并为 1~2 个核心线程池,核心线程数根据 CPU 核心数配置(如Runtime.getRuntime().availableProcessors() - 1)。 - 定时任务统一管理:
Timer-0~7等定时任务(如定位更新、日志打印),通过ScheduledThreadPoolExecutor统一调度,避免单个 Timer 对应一个线程。
(2)优化地图渲染与 GPU 交互
- 减少过度绘制:导航页面避免多层重叠 View,
SurfaceView仅用于地图渲染,其他 UI 元素(如导航信息栏)通过Overlay叠加,减少surfaceflinger合成压力。 - Native 层性能调优:
libmap.so的路线计算、坐标转换等 Native 操作,通过 CPU 多核并行(如pthread多线程)或 GPU 加速(如 OpenGL 着色器)降低用户态 CPU 占比。
3. 长期监控:预防泄漏与负载异常
- 线程泄漏监控:通过
LeakCanary或系统工具(adb shell dumpsys meminfo <pid>)监控线程数量,若长期增长需排查线程池未关闭、HandlerThread未 quit 等问题。 - CPU 负载告警:在应用中集成 CPU 使用率监控(如
Process.getProcessCpuLoad()),当前台进程 CPU 占比持续超过 30% 时,触发降级策略(如暂停非关键任务:日志打印、非实时地图更新)。 - 系统资源监控:通过
adb shell top或dumpsys cpuinfo定期检查system_server、surfaceflinger等系统进程负载,若系统级高负载需联动设备厂商优化系统 ROM。
五、验证方案
- 焦点验证:在
MainActivity的onWindowFocusChanged(true)中打印日志,确认窗口是否成功获取焦点;若未触发该回调,说明窗口初始化存在阻塞。 - 主线程耗时监控:通过
BlockCanary或Android Studio Profiler录制主线程消息执行时间,定位超过 500ms 的耗时消息(如地图初始化、UI 刷新)。 - ANR 复现验证:在高负载场景(如同时开启导航、音乐、蓝牙)下,模拟连续触摸输入,观察是否仍触发 ANR,验证优化效果。