第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 usage | CPU 负载信息,辅助判断是否因系统负载过高 | 中 |
DALVIK THREADS (or ART THREADS) | Java 线程堆栈开始标记 | 中 |
at com.xxx.room.dao + suspendAllocation | 主线程堆栈中直接出现 Room DAO 调用 | 最高 |
1.3 主线程堆栈特征判断规则
使用以下 三步判断规则 确认 ANR 由主线程操作 Room 导致:
- 规则 A(主线程路径):堆栈最外层是
android.app.ActivityThread$H.handleMessage或android.view.Choreographer.doFrame。 - 规则 B(Room 调用链):堆栈中存在
androidx.room包下的 DAO 调用,如RoomDatabase.query、suspendAllocation、SharedSQLiteStatement.execute。 - 规则 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 问题”这一分支:
1.6 快速判定清单
拿到 ANR 报告后,按以下清单逐一勾选:
- ☐ 日志中是否包含
ANR in关键字,且包名匹配? - ☐ 主线程堆栈(tid=1)顶部是否处于阻塞状态?
- ☐ 主线程堆栈中是否存在
Room、SQLite相关调用? - ☐ 阻塞是否发生在
nativeExecute、suspendAllocation或acquire方法? - ☐ 排除其他原因:
MessageQueue.nativePollOnce(空闲等待)、BinderProxy.transact(IPC 等待)?