Android ANR (Application Not Responding)
當你的 App UI 突然卡住,且幾秒鐘後彈出一個「App 無回應」的對話框時,這就是所謂的 ANR (Application Not Responding)。這對使用者體驗來說是致命的,通常會導致使用者直接解除安裝。
本文將深入探討 ANR 的觸發機制、如何診斷原因以及最佳的預防實踐。
什麼是 ANR?
在 Android 中,系統會監控應用的回應速度。如果應用在特定時間內無法處理輸入事件(如觸控、按鍵)或執行特定的系統回呼,系統就會判定該應用已「失去回應」。
觸發 ANR 的門檻規則
- 輸入事件 (Input Dispatching):當觸控或按鍵事件在 5 秒內 未得到回應。
- BroadcastReceiver:前台廣播在 10 秒內 未處理完畢。
- Service:前台服務在 20 秒內 未啟動完成。
為什麼會發生 ANR?
ANR 的本質是 主執行緒 (Main Thread / UI Thread) 被阻塞 (Blocked)。主執行緒負責處理 UI 更新、繪製與使用者互動。一旦主執行緒在忙於其他事情,就無法即時處理 UI 事件。
常見的阻塞原因
- 耗時的 I/O 操作:在主執行緒讀取大型檔案或寫入資料庫。
- 網路請求:直接在主執行緒發起網路連線(雖然現代 Android 已禁止此行為,但在舊代碼中仍可見)。
- 密集的 CPU 計算:例如進行複雜的圖片模糊處理或大量資料排序。
- 死鎖 (Deadlock):主執行緒在等待一個被其他執行緒持有的鎖。
- 未優化的 Layout:過於複雜的佈局導致繪製時間過長。
如何診斷 ANR?
當 ANR 發生時,我們需要找出「是哪一行程式碼卡住了主執行緒」。
1. 檢視 Logcat
當 ANR 發生時,系統會在 Logcat 中輸出相關訊息。搜尋關鍵字 ANR 或 ActivityManager,通常能看到導致 ANR 的進程名稱。
2. StrictMode (嚴苛模式)
在開發階段,你可以啟用 StrictMode,當偵測到你在主執行緒進行網路或磁碟操作時,它會讓 App 崩潰或噴出警告。
fun onCreate() {
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build()
)
}
super.onCreate()
}
3. 分析 traces.txt
當發送 ANR 時,系統會將所有執行緒的狀態寫入 /data/anr/traces.txt(新版 Android 為 anr_ 開頭的檔案)。
你可以透過 Android Studio 的 Device Explorer 匯出此檔案,查看 main 執行緒當下的呼叫堆疊 (Stack Trace)。
如何預防 ANR?
預防 ANR 的黃金準則就是:永遠不要在主執行緒執行耗時任務。
1. 使用協程 Coroutines
這是目前 Android 開發的首選方案。透過 withContext(Dispatchers.IO) 將耗時任務切換到背景執行緒。
fun loadData() {
viewModelScope.launch {
// 在背景執行緒讀取資料庫
val data = withContext(Dispatchers.IO) {
repository.fetchHeavyData()
}
// 自動切回主執行緒更新 UI
uiState.value = data
}
}
2. 善用 WorkManager
對於需要在 App 關閉後仍繼續執行的長任務(如上傳大型圖片),應使用 WorkManager 而非 Service。
3. 優化資料庫查詢
在 Room 中,務必使用非同步查詢(回傳 Flow 或 suspend),並為常用查詢欄位建立索引 (Index)。
總結
ANR 是提醒開發者應更有效率地使用執行緒的信號。保持主執行緒的純粹與輕量,僅處理 UI 更新與簡單的邏輯,是打造流暢 Android 應用程式的不二法門。