好的!以下是一篇基於你提供的 CSDN 博客文章(原文鏈接)整理而成的技術博客。我保留了原文的核心分析流程、關鍵日誌片段和排查思路,並進行了結構優化、語言潤色與邏輯補充,使其更清晰、完整且具備實戰指導價值。
ANR 分析實戰:從日誌定位到根因排查的完整流程(附代碼與命令)
作者:Qwen
參考來源:CSDN《ANR分析實戰》
在 Android 開發中,ANR(Application Not Responding) 是最常見也最棘手的性能問題之一。當主線程被阻塞超過 5 秒(前台)或 10 秒(後台),系統就會彈出“應用無響應”對話框,嚴重影響用户體驗。
本文將通過一個真實案例,帶你走完 ANR 問題分析的標準五步流程,並提供可直接複用的日誌分析命令與代碼示例。
一、ANR 基礎知識回顧
1.1 什麼是 ANR?
ANR 指 Application Not Responding,即應用未在規定時間內響應用户輸入或系統事件。
1.2 常見 ANR 類型
|
類型
|
超時閾值
|
觸發條件
|
|
Input Dispatching Timeout |
5 秒
|
主線程未處理按鍵/觸摸事件
|
|
Broadcast Timeout |
10 秒
|
|
|
Service Timeout |
20 秒
|
|
⚠️ 本文重點分析最常見的 主線程阻塞型 ANR。
二、ANR 分析五步法
步驟 1️⃣:確定 ANR 發生時間
目標:精確定位 ANR 觸發的時間點。
方法:在 event_log 或 logcat 中搜索關鍵詞:
# 方法一:查看 event_log(推薦)
adb logcat -b events | grep "am_anr"
# 示例輸出:
# 01-01 12:00:00.000 1234 5678 I am_anr: [0,12345,com.example.app,952783456,Input dispatching timed out]
關鍵信息:
- 時間戳:
01-01 12:00:00.000 - 進程 PID:
12345 - 包名:
com.example.app - ANR 類型:
Input dispatching timed out
✅ 若日誌完整,也可通過
/data/anr/traces.txt文件的生成時間反推。
步驟 2️⃣:檢查 CPU 與系統負載
目標:判斷是 應用自身問題 還是 系統資源瓶頸。
方法:在 logcat 中搜索 "ANR in" 並查看 CPU usage:
adb logcat | grep -A 20 "ANR in com.example.app"
關鍵日誌片段示例:
CPU usage from 0ms to 5000ms later:
98% TOTAL: 50% kernel + 40% kswapd0 + 8% app
分析要點:
kswapd0佔比過高(>25%):説明系統正在頻繁回收內存,存在 嚴重內存壓力。kernel佔比高:可能因 I/O 阻塞、鎖競爭等內核態問題。- 應用自身 CPU 低但 ANR:很可能是 主線程被鎖阻塞 或 Binder 調用卡住。
📌 在原文案例中,
kswapd0達 40%,kernel達 50%,表明系統處於 低內存狀態,這可能是 ANR 的間接原因。
步驟 3️⃣:分析 Trace 文件(核心步驟)
目標:定位主線程具體卡在哪一行代碼。
獲取 trace 文件:
adb pull /data/anr/traces.txt ./anr_trace.log
分析主線程(main thread)狀態:
在 traces.txt 中找到你的進程(如 PID=5231),查看 main 線程:
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x74a0c7f0 self=0xb40000789c0a8010
| sysTid=5231 nice=-10 cgrp=default sched=0/0 handle=0x789c0e84f8
| state=S schedstat=( 123456 789012 10 ) utm=10 stm=2 core=3 HZ=100
| stack=0x7fd1a8e000-0x7fd1a90000 stackSize=8192KB
| held mutexes=
at java.lang.Object.wait(Native method)
- waiting on <0x033c7910> (a java.lang.Object)
at com.example.app.Initializer.init(Initializer.java:42)
at com.example.app.MainActivity.onCreate(MainActivity.java:15)
關鍵信息解讀:
- 線程狀態:
Native/Blocked/Waiting/Runnable - 阻塞位置:
Initializer.java:42正在等待某個對象鎖 - 調用棧:從
MainActivity.onCreate()→Initializer.init()
✅ 結合 ANR 時間點 和 組件初始化附近 的代碼,可快速縮小問題範圍。
步驟 4️⃣:排查常見根因
根據 trace 和 CPU 信息,常見根因包括:
🔒 場景 1:主線程死鎖或鎖競爭
- 表現:
main線程狀態為Blocked,waiting to lock <xxx> - 解決:避免在主線程持有鎖;使用
ReentrantLock.tryLock()超時機制
💾 場景 2:主線程執行耗時 IO
- 表現:
main線程調用File.read()、SQLiteDatabase.query()等 - 代碼示例(錯誤):
// ❌ 錯誤:在 onCreate 中讀文件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String data = readFile("/sdcard/config.txt"); // 阻塞主線程!
}
- 解決:移至
AsyncTask、HandlerThread或Coroutine
🧠 場景 3:系統內存不足(如原文案例)
- 表現:
kswapd0CPU 高,應用雖未直接耗 CPU,但因系統卡頓導致無法及時調度 - 解決:
- 優化內存使用(避免 Bitmap 泄漏、大對象緩存)
- 使用
LeakCanary檢測內存泄漏 - 監控
dumpsys meminfo中的PSS和Java Heap
📡 場景 4:Binder 調用阻塞
- 表現:
main線程卡在BinderProxy.transactNative() - 解決:避免跨進程調用耗時操作;設置超時
步驟 5️⃣:復現與驗證
強制觸發 ANR(調試用):
# 向目標進程發送 SIGQUIT,生成 traces.txt
adb shell kill -3 <PID>
使用 Profiler 實時監控:
- 在 Android Studio 中打開 Profiler
- 監控 CPU、Memory、Network
- 查看主線程是否長時間處於非
Runnable狀態
三、總結:ANR 分析 Checklist
|
步驟
|
關鍵動作
|
工具/命令
|
|
1. 定位時間
|
搜索 |
|
|
2. 查看負載
|
分析 CPU usage
|
|
|
3. 分析 trace
|
檢查 main 線程棧
|
|
|
4. 根因排查
|
鎖、IO、內存、Binder
|
代碼審查 + |
|
5. 驗證修復
|
復現 + Profiler
|
Android Studio Profiler / |
💡 預防建議:
- 主線程只做 UI 更新,絕不執行網絡、數據庫、文件讀寫
- 使用
StrictMode檢測主線程違規操作- 對第三方 SDK 初始化做異步封裝
通過這套標準化流程,你可以像偵探一樣,從海量日誌中抽絲剝繭,精準定位 ANR 根因。希望這篇指南能成為你解決性能問題的得力助手!