第1章:ANR现象识别

当用户操作 App 时,如果界面卡住超过 5 秒即触发 ANR(Application Not Responding)。本章聚焦于如何从日志和堆栈中准确识别出因主线程直接操作 Room 数据库导致的 ANR,并排除其他假阳性原因。

1.1 现象特征速查

症状典型表现
界面冻结点击无响应、滑动停滞、系统弹出“应用无响应”对话框
ANR 类型通常为 Input dispatching timed out 或 Executing service
触发时机列表加载、详情查询、批量写入后界面未及时刷新
用户感知从点击到 ANR 对话框出现通常 > 5s
快速判断:如果用户报告“点击某个按钮后卡住几秒才弹出对话框”,极大概率是主线程执行了耗时操作,Room 是常见的嫌疑对象。

1.2 日志关键字排查表

获取 ANR 日志(/data/anr/traces.txt 或 logcat 中 ANR in 所在行)后,按以下表格逐一匹配:

关键字含义匹配优先级
ANR in xxx (package)发生 ANR 的进程包名高
Reason: Input dispatching timed out主线程未及时处理输入事件高
CPU usageCPU 负载信息,辅助判断是否因系统负载过高中
DALVIK THREADS (or ART THREADS)Java 线程堆栈开始标记中
at com.xxx.room.dao + suspendAllocation主线程堆栈中直接出现 Room DAO 调用最高

1.3 主线程堆栈特征判断规则

使用以下 三步判断规则 确认 ANR 由主线程操作 Room 导致:

  1. 规则 A(主线程路径):堆栈最外层是 android.app.ActivityThread$H.handleMessage 或 android.view.Choreographer.doFrame。
  2. 规则 B(Room 调用链):堆栈中存在 androidx.room 包下的 DAO 调用,如 RoomDatabase.query、suspendAllocation、SharedSQLiteStatement.execute。
  3. 规则 C(耗时阻塞):堆栈底部停留在 android.database.sqlite.SQLiteConnection.nativeExecute 或 java.lang.Thread.sleep 等阻塞点。
注意:如果主线程堆栈中看到 BinderProxy.transact 或 MessageQueue.nativePollOnce,说明主线程空闲或等待 IPC,此时 ANR 原因可能是其他进程或系统负载,应暂时排除 Room 嫌疑,先检查系统资源或跨进程调用。

1.4 典型日志与堆栈示例

以下是从真实 ANR 日志中摘录的主线程堆栈片段:

"main" prio=5 tid=1 Blocked
  at java.lang.Thread.sleep(Thread.java:-2)
  at android.database.sqlite.SQLiteConnection.nativeExecute(Native Method)
  at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:550)
  at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:629)
  at android.database.sqlite.SQLiteDatabase.execute(SQLiteDatabase.java:548)
  at androidx.room.RoomDatabase.query(RoomDatabase.java:236)
  at com.example.app.data.room.dao.UserDao_Impl.getUsers(UserDao_Impl.java:45)
  at com.example.app.viewmodel.UserViewModel$loadUsers$1.invokeSuspend(UserViewModel.kt:22)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(BaseContinuationImpl.java:33)
  at android.os.Handler.dispatchMessage(Handler.java:106)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2020)
  at android.os.Looper.loop(Looper.java:193)
  at android.app.ActivityThread.main(ActivityThread.java:6949)
解读:
  • tid=1 为主线程。
  • UserDao_Impl.getUsers 直接在主线程上执行 Room DAO 操作。
  • nativeExecute 为 SQLite 实际耗时调用的阻塞点。
  • 符合规则 A+B+C,确认是 主线程操作 Room 查询引发的 ANR。

1.5 现象识别故障树

下图帮助你在接到 ANR 报告后快速定位到“是否主线程 Room 问题”这一分支:

主线程 Room ANR 现象识别故障树 ANR 对话框出现 日志有 ANR in 包名 日志无 ANR in 包名 主线程堆栈 其他线程堆栈 系统 ANR / 跨进程问题 nativeExecute 阻塞点 非 SQLite 阻塞(Binder等) 排除 Room 嫌疑 堆栈包含 Room DAO 无 Room 调用链 确认:主线程 Room ANR 查找其他阻塞原因

1.6 快速判定清单

拿到 ANR 报告后,按以下清单逐一勾选:

  1. ☐ 日志中是否包含 ANR in 关键字,且包名匹配?
  2. ☐ 主线程堆栈(tid=1)顶部是否处于阻塞状态?
  3. ☐ 主线程堆栈中是否存在 Room、SQLite 相关调用?
  4. ☐ 阻塞是否发生在 nativeExecute、suspendAllocation 或 acquire 方法?
  5. ☐ 排除其他原因:MessageQueue.nativePollOnce(空闲等待)、BinderProxy.transact(IPC 等待)?