ANR(Application Not Responding)は、アプリが5秒以内に入力に応答しない場合に表示されるAndroidのシステム通知です。グリッチ(UIブロッキングを伴わない論理エラー)やラグ(完全停止を伴わない遅延)とは異なり、ANRはオペレーティングシステムによって記録される重大な障害です。Androidは“アプリが応答していません”というダイアログを表示し、閉じるか待つかのオプションを提供します。Android Vitals Documentationによると、ANR率が0.5%を超えるアプリはGoogle Playでの評価が低くなり、おすすめから非表示になる可能性があります。診断には/data/anr/traces.txtの分析、StrictModeの使用、メインスレッドのプロファイリングが含まれます。
重要なポイント
ANR(Application Not Responding)は、アプリが入力に応答しなくなったときに作動するAndroidのユーザー保護メカニズムです。システムはイベント処理時間を追跡します。BroadcastReceiverが10秒以内にonReceiveを完了しない、Serviceが20秒以内にonCreateから戻らない、ContentProviderが15秒以内に応答しない場合、AndroidはANRを生成します。
ANRが発生すると、Androidはすべてのウィンドウの上にシステムダイアログを表示します:「アプリが応答していません。閉じますか、それとも待ちますか?」ユーザーはアプリを閉じるか、回復するのを待つことができます。ANRが頻繁に発生する場合、ユーザーはアプリをアンインストールします。Google PlayはランキングアルゴリズムでANR率(ANRが発生したセッションの割合)を考慮します。
iOSにはシステムダイアログを伴うANRに相当するものはありません。代わりにAppleはWatchdogを使用し、終了コード0x8badf00dでプロセスを終了します。ユーザーはダイアログを表示されず、アプリは単にホーム画面に閉じられます。これによりAndroidのANRはユーザーにとってより顕著ですが、システムにより多くの診断情報を提供します。
ANRは、システムが4つのコンポーネントタイプのいずれかのタイムアウトを追跡するときに発生します。各コンポーネントには独自の制限時間があります。
BroadcastReceiverはメインスレッドで実行されます。onReceiveが同期ネットワークリクエスト、データベースへの長時間書き込み、またはロックの待機を開始すると、10秒以内にANRが発生します。解決策:バックグラウンド処理にはgoAsync()とWorkManagerを使用します。典型的なシナリオは、FCMからプッシュ通知を受信し、Roomに同期的に保存する場合です。
Service.onCreateとService.onStartCommandには20秒の制限があります。サービスがメインスレッドで重い初期化(ライブラリのロード、ネットワークからの設定の読み取り)を開始すると、ANRは避けられません。バックグラウンドでの確実な実行にはIntentService(非推奨)またはWorkManagerを使用します。
ContentProvider.onCreateはApplication.onCreateの前に実行され、15秒の制限があります。プロバイダーがデータベースマイグレーション、辞書のロード、またはネットワークからのSDK初期化を実行すると、アプリ起動時にANRが発生します。解決策:遅延初期化、重い操作をWorkManagerにオフロードします。
Androidはシステムログから専門ライブラリまで、ANR分析のためのいくつかのツールを提供します。
ANRが発生するたびに、Androidはすべてのアプリスレッドのスタックダンプを含む/data/anr/traces.txtファイルを保存します。“main”スレッドを見つけます。スタックの最後のメソッドが原因を示します。典型的なパターン:Thread.sleep()、InputStream.read()、BinderProxy.transact()。デバイスからファイルを抽出するには、スーパーユーザー権限でadbを使用します。
Firebase CrashlyticsはANRを自動的に収集し、トレースとともにダッシュボードに表示します。Android 11+の場合、ANRレポートにはメインスレッドの完全なスタックが含まれます。統合には依存関係の追加とApplication.onCreateでのFirebaseAppの初期化が必要です。
Android StudioのCPU Profilerを使用すると、アプリのトレースを記録し、どのメソッドがCPU時間を消費しているかを確認できます。“Record with method traces”を有効にし、ANRを引き起こすシナリオを再現します。タイムラインには、フリーズ時にメインスレッドで実行されていたメソッドが表示されます。
AndroidでのANR収集のためのFirebase Crashlytics統合の例:
class App : Application() {
override fun onCreate() {
super.onCreate()
FirebaseApp.initializeApp(this)
FirebaseCrashlytics.getInstance()
.setCrashlyticsCollectionEnabled(true)
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build())
}
}
ANRの修正とは、主にすべての長時間操作をメインスレッドからバックグラウンドスレッドに移動することを意味します。各コンポーネントタイプの具体的なテクニックを見てみましょう。
WorkManagerは、バックグラウンド作業に対するGoogleの推奨ソリューションです。デバイスの状態を考慮して、バックグラウンドスレッドでのタスク実行を保証します。Serviceとは異なり、WorkManagerはメインスレッドをブロックせず、アプリの再起動に対して耐性があります。BroadcastReceiverの場合は、goAsync()を使用してPendingResultをWorkManagerに渡します。
すべてのネットワークリクエスト、データベース操作、ファイルI/OはDispatchers.IOで実行します。メインスレッドはUIの更新のみを行うべきです。Activityが破棄されたときにコルーチンを自動キャンセルするにはviewModelScopeを使用します。どのコンテキストでもrunBlocking()は避けてください。現在のスレッドを同期的にブロックします。
ContentProviderが遅い初期化を行う場合は、遅延読み込みメカニズムを使用します。即座にデータを返すプロバイダーを作成し、WorkManagerを介して遅延付きで重い初期化を開始します。これにより、システムが遅延に最も敏感なアプリ起動時のANRを防ぎます。
AndroidでgoAsyncを使用したBroadcastReceiverの正しい使用例:
class FcmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val pendingResult = goAsync()
WorkManager.getInstance(context!!)
.enqueue(OneTimeWorkRequest.from(NotificationWorker::class.java))
pendingResult.finish()
}
}
ANRと戦う最善の方法は、ツールとアーキテクチャ上の決定を通じて開発中にそれらを予防することです。
有効化されたポリシーdetectNetwork()とdetectDiskReads()/detectDiskWrites()を使用したStrictModeは、開発中に潜在的なANRを特定します。DebugビルドではpenaltyDeathを設定します。違反があると即座にクラッシュが発生し、開発者はコミット前に問題を確認できます。
Firebase Performanceは主要な操作の実行時間を追跡し、どのシナリオがANRしきい値を超えているかを示します。各画面とネットワークリクエストにカスタムトレースを設定します。実行時間が3秒を超える場合、最適化が必要な潜在的なANRです。
遅い条件をシミュレートします:iOSまたはAndroid EmulatorでNetwork Link Conditionerを使用してネットワーク速度を制限します。低速メモリのエミュレーションを通じてディスク読み取りを遅くします。ANRは多くの場合、このような条件で顕在化し、高速な開発者デバイスでは見えません。
よくある質問
Androidはメインスレッドのイベント処理時間を明示的に追跡し、ANRダイアログを表示します。iOSはWatchdogを使用し、10~20秒以上フリーズするとアプリを強制的に閉じます。ANRはAndroidアーキテクチャの特徴であり、複数のコンポーネント(BroadcastReceiver、Service)に厳格なタイムアウトがあります。
Android 11+ではadb shell dumpsys dropbox --print data_app_anrでANRダンプを取得できます。Android 10以下では、rootアクセスなしでは/data/anr/traces.txtにアクセスできません。Firebase Crashlyticsを使用してください。Android 11+ではANRレポートを自動的に収集します。
Google PlayはANR率を0.5%未満に推奨しています。つまり、1000セッションあたり5件以下のANRです。率が1%を超えるアプリはGoogle Play Consoleで警告を受け、おすすめから非表示になる可能性があります。理想的にはANR率は0.1%未満であるべきです。
コルーチン自体はスレッドをブロックしません。しかし、コルーチン内でメインスレッドでrunBlockingを実行したり、コルーチンがDispatchers.Mainで起動され長いCPU操作を実行すると、ANRが発生します。I/OにはDispatchers.IO、計算にはDispatchers.Defaultを使用します。
“Slow Network”プロファイルのAndroid Emulatorを使用するか、メインスレッドでThread.sleep(6000)を呼び出すテストを記述します。Debugでアプリを起動し、5秒後にANRダイアログが表示されます。logcatにトレース付きのANRレコードが表示されることを確認します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。