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.onStartJob または JobService.onStopJob から戻らない場合、またはユーザーが開始するジョブが開始され、アプリが JobService.onStartJob の呼び出しから数秒以内に JobService.setNotification を呼び出さなかった場合。Android 13 以前を対象とするアプリの場合、ANR は認識されず、アプリに報告されません。Android 14 以降を対象とするアプリでは、ANR は認識され、アプリに報告されます。

アプリで ANR が発生する場合、このドキュメントのガイダンスが問題の診断と解決に役立ちます。

ANR を診断する

ANR を診断する際は、次のような一般的なパターンを探します。

  • メインスレッドで I/O に関連するアプリの処理が遅くなっている。
  • メインスレッドでアプリの計算に長時間かかっている。
  • メインスレッドが別のプロセスへの同期バインダー呼び出しを行っており、その別のプロセスが復帰するまでに長時間かかっている。
  • メインスレッドが、別のスレッドで発生している長時間実行オペレーションの同期ブロックを待機してブロックされている。
  • メインスレッドが、プロセス内または Binder 呼び出しを介して、別のスレッドとデッドロック状態になっている。メインスレッドは、単に長時間実行オペレーションが終了するのを待機しているのでなく、デッドロック状態になっています。

ANR の原因を特定するには、次の手法が有用です。

HealthStats

HealthStats は、ユーザーおよびシステムの合計時間、CPU 時間、ネットワーク、無線データ、画面のオン/オフ時間、ウェイクアップ アラームをキャプチャして、アプリケーションの健全性に関する指標を提供します。これは、全体的な CPU 使用率とバッテリーの消耗の測定で役立ちます。

デバッグ

Debug を使用すると、開発中の Android アプリを検査できます。これには、トレースや割り当てのカウントでアプリ内のジャンクや遅延を特定することなどが含まれます。Debug を使用して、ランタイム メモリとネイティブ メモリのカウンタ、および特定のプロセスのメモリ使用量を特定する際に役立つメモリ指標を取得することもできます。

ApplicationExitInfo

ApplicationExitInfo は Android 11(API レベル 30)以降で使用でき、アプリの終了の理由に関する情報を提供します。これには、ANR、メモリの不足、アプリのクラッシュ、CPU の過剰な使用、ユーザーの中断、システムの中断、実行時の権限の変更などが含まれます。

厳格モード

StrictMode を使用すると、アプリの開発中にメインスレッド上の誤った I/O オペレーションを見つけやすくなります。StrictMode は、アプリレベルまたはアクティビティ レベルで使用できます。

バックグラウンド ANR ダイアログを有効にする

Android では、デバイスの [開発者向けオプション] で [すべての ANR を表示] が有効になっている場合にのみ、ブロードキャスト メッセージの処理に時間がかかりすぎるアプリの ANR ダイアログが表示されます。そのため、アプリでパフォーマンスの問題が発生していても、必ずしもバックグラウンド ANR ダイアログがユーザーに表示されるとは限りません。

再コンポーズのボトルネック

Android Studio ProfilerLayout Inspector を使用して、再コンポーズのボトルネックを特定します。詳しくは、Jetpack Compose のパフォーマンスをご覧ください。

トレース ファイルを pull する

Android では、ANR が発生するとトレース情報が保存されます。以前の OS リリースでは、デバイスに /data/anr/traces.txt ファイルが 1 つあります。新しい OS リリースでは、複数の /data/anr/anr_* ファイルがあります。デバイスまたはエミュレータから ANR トレースにアクセスするには、ルートで Android Debug Bridge(adb)を使用します。

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 やデータベース呼び出しなど)をトリガーすることがよくあります。

長時間実行される I/O オペレーションは UI レイヤから離して実行します。ViewModelwithContext(Dispatchers.IO) を使用するか、データレイヤーRepository を使用することをおすすめします。

デッドロック

お互いに相手の保持するリソースを必要とする 2 つのスレッドがあり、スレッドが待機状態に入ると、デッドロックが発生します。アプリのメインスレッドがこの状況にある場合、ANR が発生する可能性があります。

デッドロックはコンピュータ サイエンスでよく研究されている現象で、デッドロックを回避するために使用できるデッドロック防止アルゴリズムがあります。

詳細については、Wikipedia のデッドロックデッドロック防止アルゴリズムをご覧ください。

Kotlin と Compose を使用する場合、プリミティブ ロックをノンブロッキング コルーチン Mutex(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 は次の場合に発生します。

  • かなりの時間が経過してもブロードキャスト レシーバが onReceive メソッドの実行を完了しなかった。
  • ブロードキャスト レシーバが goAsync を呼び出したが、PendingResult オブジェクトで finish の呼び出しに失敗した。

アプリが BroadcastReceiveronReceive メソッドで短いオペレーションのみ実行するようにします。ただし、ブロードキャスト メッセージの結果として、アプリがもっと複雑な処理を必要とする場合は、タスクが数秒以内に完了すると予想される場合は ViewModel(Kotlin コルーチン、スコープ、ディスパッチャーの機能を活用)、状態ホルダー、数秒以上かかると予想される場合は WorkManager にタスクを委ねる必要があります。

GameActivity

GameActivity ライブラリにより、C または C++ で記述されたゲームとアプリの事例研究で ANR が削減されました。既存のネイティブ アクティビティを GameActivity に置き換えると、UI スレッドのブロックを減らし、ANR の発生をある程度防ぐことができます。

ANR の詳細については、アプリの応答性を維持するをご覧ください。スレッドの詳細については、スレッド化によるパフォーマンスの向上をご覧ください。

参考情報

コンテンツの閲覧

  • 注: JavaScript がオフになっている場合はリンクテキストが表示されます
  • 過度の wakeup