ANR

當 Android 應用程式的 UI 執行緒遭封鎖的時間過長,會觸發「應用程式無回應」(ANR) 錯誤。如果應用程式在前景執行,系統會向使用者顯示對話方塊,如圖 1 所示。ANR 對話方塊可讓使用者選擇強制退出應用程式。

向使用者顯示的 ANR 對話方塊。
圖 1. 向使用者顯示的 ANR 對話方塊

ANR 會對使用者造成困擾,是個問題,發生原因是應用程式負責更新 UI 的主要執行緒無法處理使用者輸入事件或繪圖。如要進一步瞭解應用程式的主要執行緒,請參閱「程序和執行緒總覽」。

當發生以下任一種情形,應用程式就會觸發 ANR:

  • 輸入分派作業逾時:如果應用程式未在 5 秒內回應輸入事件 (例如按鍵或螢幕觸控事件)。
  • 執行服務:如果應用程式宣告的服務在幾秒內無法完成執行 Service.onCreateService.onStartCommand/Service.onBind
  • 未呼叫 Service.startForeground如果應用程式使用 Context.startForegroundService 在前景啟動新服務,但服務未在 5 秒內呼叫 startForeground
  • 廣播意圖:如果 BroadcastReceiver 未在一定時間內完成執行。如果應用程式在前景有任何活動,逾時時間為 5 秒。
  • JobScheduler 互動:如果 JobService 未在幾秒內從 JobService.onStartJobJobService.onStopJob 傳回;或者,如果由使用者發起的工作啟動了,而您的應用程式在呼叫 JobService.onStartJob 後的幾秒內並未呼叫 JobService.setNotification。如果應用程式指定 Android 13 以下版本,則 ANR 不會發出通知,也不會回報給應用程式。如果指定 Android 14 以上版本,ANR 會發出通知,也會回報給應用程式。

如果您的應用程式發生 ANR,可以按照本文提供的指南診斷及修正問題。

診斷 ANR

以下是 ANR 常見的模式,可供您在診斷時參考:

  • 應用程式在主執行緒進行含有 I/O 的作業時速度過慢。
  • 應用程式在主執行緒進行長時間的運算。
  • 主要執行緒向另一個程序發出同步繫結呼叫,而該程序回傳花費的時間過長。
  • 由於等待其他執行緒上進行長時間作業的同步區塊,導致主要執行緒遭到封鎖。
  • 主要執行緒在程序中或透過繫結呼叫和另一個執行緒形成死結。主要執行緒不只是等待長時間作業完成,而是處於死結狀態。

以下技巧可以幫助您判斷 ANR 的發生原因。

HealthStats

HealthStats 會擷取各項總計資料,包括使用者和系統時間、CPU 作業時間、網路、無線電統計資料、螢幕開啟/關閉時間和喚醒時間,藉此提供應用程式運作狀態的指標。這有助於評估整體 CPU 使用率與電池耗電。

偵錯

Debug 可協助檢查開發期間的 Android 應用程式,包括追蹤和配置計數,以識別應用程式中的卡頓和延遲。您還可以使用 Debug 取得執行階段和原生記憶體計數器,以及有助於識別特定程序記憶體用量的記憶體指標。

ApplicationExitInfo

ApplicationExitInfo 支援 Android 11 (API 級別 30) 以上版本,並提供應用程式結束原因等相關資訊。這包括 ANR、低記憶體、應用程式當機、額外的 CPU 使用率、使用者中斷、系統中斷和執行階段權限變更。

嚴格模式

使用 StrictMode 可以幫助您在應用程式開發期間找出主要執行緒上意外發生的 I/O 作業。您可以在應用程式或活動層級使用 StrictMode

啟用背景 ANR 對話方塊

只有在裝置的「開發人員選項」中已啟用「Show all ANRs」的情況下,Android 才會在應用程式處理廣播訊息時間過長時,顯示 ANR 對話方塊。因此,即使應用程式發生效能問題,使用者也不一定會看到背景 ANR 對話方塊。

重新組合瓶頸

使用 Android Studio 分析器版面配置檢查器追蹤重新組合瓶頸。詳情請參閱「Jetpack Compose 效能」。

擷取追蹤檔

發生 ANR 時,Android 會儲存追蹤資訊。在較舊的 OS 版本中,裝置上會有一個 /data/anr/traces.txt 檔案。在較新的 OS 版本中,則會有多個 /data/anr/anr_* 檔案。您可以使用 Android Debug Bridge (adb) 做為根層級,存取裝置或模擬器中的 ANR 追蹤記錄:

adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>

您可以使用裝置上的「取得錯誤報告」開發人員選項,或在開發機器上使用 adb bugreport 指令,在實體裝置上擷取錯誤報告。詳情請參閱「擷取及查看錯誤報告」。

修正問題

找出問題後,您可以使用本章節提到的方法修復常見問題。

主要執行緒上的過慢程式碼

在程式碼中,找出應用程式主執行緒忙碌時間超過 5 秒的部分。請尋找應用程式中可疑的用途,並嘗試重現 ANR。

常見問題是在可組合函式中直接執行長時間執行的工作:

@Composable
fun BadList(rawStrings: List<String>) {
    // Math or sorting inside the composable runs on EVERY recomposition pass!
    val heavilyProcessedList = rawStrings
        .filter { it.isNotBlank() }
        .map { it.uppercase().reversed() }
        .map { it.computationallyHeavyFunction() }
.sortedBy { it.length }
    LazyColumn { items(sortedList) { Text(it) } }
}

// Modern Compose-first fix
@Composable
fun GoodList(viewModel: MyViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    // UI simply renders state; no heavy processing allowed here
    LazyColumn { items(uiState.sortedData) { Text(it) } }
}

主執行緒上的 I/O

造成主要執行緒作業緩慢的常見原因,是在主執行緒執行 I/O 作業,而這也可能導致 ANR。在 Compose 中,開發人員嘗試衍生初始狀態時,經常會意外觸發磁碟讀取 (例如 SharedPreferences 或資料庫呼叫)。

在 UI 層以外執行長時間執行的 I/O 作業。在 ViewModel 中使用 withContext(Dispatchers.IO),或是在資料層使用 Repository

死結

當某執行緒需要的資源被另一個執行緒佔據而必須進入等待狀態,但是這另一個執行緒也正在等待第一個執行緒佔據的資源時,就會發生死結。如果應用程式的主執行緒有這種情形,就可能發生 ANR。

死結現象已在電腦科學領域中經過充分研究,目前已有演算法可用於防範這項問題。

詳情請參閱維基百科上的「死結」和「死結防範演算法」內容。

使用 Kotlin 和 Compose 時,您可以將原始鎖替換為非封鎖協同程式互斥鎖 (Mutex.withLock),藉由「暫停」執行環境,而非凍結 UI 執行緒,防止執行緒遭到封鎖。例如:

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

// Modern non-blocking concurrency state architecture
class SecureDataRepository {
    private val mutex = Mutex()

    suspend fun safeUIAccess() {
        // If locked, the main thread suspends seamlessly, preventing an ANR
        mutex.withLock {
            performSafeOperation()
        }
    }
}

過慢的廣播接收器

應用程式可透過廣播接收器回應廣播訊息,例如啟用/停用飛航模式或變更連線狀態。當應用程式處理廣播訊息的時間過長,就會發生 ANR。

當有以下情形時,就會發生 ANR:

應用程式在 BroadcastReceiveronReceive 方法內應該只能執行短作業。不過,如果應用程式由於廣播訊息而需要進行更複雜的處理作業,如果工作預計最多需要幾秒鐘,則應將工作延後到 ViewModel (運用 Kotlin 協同程式、範圍和調度器的強大功能)、任何類型的狀態容器,或延後到 WorkManager (如果工作預計需要幾秒鐘以上)。

GameActivity

根據個案研究,在以 C 或 C++ 編寫的遊戲和應用程式中,GameActivity 程式庫可減少 ANR 情形。如果您將現有原生活動替換為 GameActivity,可以減少 UI 執行緒封鎖情況,並防止某些 ANR 發生。

如要進一步瞭解 ANR,請參閱「讓應用程式維持正常回應速度」。如要進一步瞭解執行緒,請參閱「透過執行緒提升效能」。

其他資源

Views content