好的!以下是一篇基於你提供的 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 秒

BroadcastReceiver.onReceive() 執行超時

Service Timeout

20 秒

Service.onCreate() / onStartCommand() 執行超時

⚠️ 本文重點分析最常見的 主線程阻塞型 ANR


二、ANR 分析五步法

步驟 1️⃣:確定 ANR 發生時間

目標:精確定位 ANR 觸發的時間點。

方法:在 event_loglogcat 中搜索關鍵詞:

# 方法一:查看 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 線程狀態為 Blockedwaiting 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"); // 阻塞主線程!
}
  • 解決:移至 AsyncTaskHandlerThreadCoroutine

🧠 場景 3:系統內存不足(如原文案例)

  • 表現kswapd0 CPU 高,應用雖未直接耗 CPU,但因系統卡頓導致無法及時調度
  • 解決
  • 優化內存使用(避免 Bitmap 泄漏、大對象緩存)
  • 使用 LeakCanary 檢測內存泄漏
  • 監控 dumpsys meminfo 中的 PSSJava Heap

📡 場景 4:Binder 調用阻塞

  • 表現main 線程卡在 BinderProxy.transactNative()
  • 解決:避免跨進程調用耗時操作;設置超時

步驟 5️⃣:復現與驗證

強制觸發 ANR(調試用)

# 向目標進程發送 SIGQUIT,生成 traces.txt
adb shell kill -3 <PID>

使用 Profiler 實時監控

  • 在 Android Studio 中打開 Profiler
  • 監控 CPUMemoryNetwork
  • 查看主線程是否長時間處於非 Runnable 狀態

三、總結:ANR 分析 Checklist

步驟

關鍵動作

工具/命令

1. 定位時間

搜索 am_anr

adb logcat -b events | grep am_anr

2. 查看負載

分析 CPU usage

logcatANR in ... 後的 CPU 統計

3. 分析 trace

檢查 main 線程棧

adb pull /data/anr/traces.txt

4. 根因排查

鎖、IO、內存、Binder

代碼審查 + dumpsys

5. 驗證修復

復現 + Profiler

Android Studio Profiler / kill -3

💡 預防建議

  • 主線程只做 UI 更新,絕不執行網絡、數據庫、文件讀寫
  • 使用 StrictMode 檢測主線程違規操作
  • 對第三方 SDK 初始化做異步封裝

通過這套標準化流程,你可以像偵探一樣,從海量日誌中抽絲剝繭,精準定位 ANR 根因。希望這篇指南能成為你解決性能問題的得力助手!