6 月 3 日の「#Android11: The Beta Launch Show」にぜひご参加ください。

クラッシュの検出と診断

Android アプリは、処理されない例外またはシグナルが原因で予期しない終了が発生すると、クラッシュします。Java で作成されたアプリは、Throwable クラスによって表現される処理されない例外をスローすると、クラッシュします。ネイティブ コード言語で作成されたアプリは、処理されないシグナル(SIGSEGV など)が実行時に存在すると、クラッシュします。

アプリがクラッシュすると、Android によってアプリのプロセスが終了され、図 1 に示すように、アプリが停止したことをユーザーに知らせるダイアログが表示されます。

Android デバイスでのアプリのクラッシュ

図 1. Android デバイスでのアプリのクラッシュ

アプリは、フォアグラウンドで実行されていなくてもクラッシュします。バックグラウンドで実行されるブロードキャスト レシーバやコンテンツ プロバイダを含め、あらゆるアプリ コンポーネントがアプリのクラッシュを引き起こす可能性があります。操作中ではないアプリがクラッシュすると、たいていのユーザーは困惑します。

アプリでクラッシュが発生する場合、このページのガイダンスが問題の診断と解決に役立ちます。ネイティブ コード言語で作成されたアプリのクラッシュを診断する方法については、ネイティブ クラッシュの診断をご覧ください。

問題を検出する

デベロッパーは、ユーザーがアプリのクラッシュを頻繁に経験していることを常に把握できるとは限りません。アプリをすでに公開している場合は、Android Vitals が問題を認識するのに役立ちます。

Android Vitals

Android Vitals は、アプリがきわめて頻繁にクラッシュする場合に Play Console を介してデベロッパーにアラートを送信することで、アプリのパフォーマンスの改善をサポートします。Android Vitals は、アプリが次の状態のときに、クラッシュが多すぎると判断します。

  • 1 日のセッションの 1.09% 以上で、クラッシュが 1 回以上発生する。
  • 1 日のセッションの 0.18% 以上で、クラッシュが 2 回以上発生する。

1 日のセッションとは、アプリが使用された 1 日を意味します。Google Play で Android Vitals データが収集される仕組みについては、Play Console のドキュメントをご覧ください。

アプリが頻繁にクラッシュしていることがわかったら、次のステップとして、クラッシュを診断します。

クラッシュを診断する

クラッシュの解決は時として困難です。しかし、クラッシュの根本原因を特定できれば、ほとんどの場合は解決策を見つけられます。

アプリのクラッシュが発生する状況はさまざまです。null 値または空の文字列の検出のように原因が明らかな場合もありますが、無効な引数が API に渡されたとか、マルチスレッド化されたインタラクションが複雑すぎるとかいった、わかりにくい原因による場合もあります。

スタック トレースを確認する

クラッシュを解決するには、まず、クラッシュの発生場所を特定します。Play Console または logcat ツールの出力を使用している場合は、レポート詳細で参照可能なスタック トレースを使用できます。スタック トレースを参照できない場合は、アプリを手動でテストするか、クラッシュを経験しているユーザーに協力を依頼して、logcat の使用中にローカルでクラッシュを再現する必要があります。

次のトレースは、サンプルアプリにおけるクラッシュの例を示しています。

--------- beginning of crash
    AndroidRuntime: FATAL EXCEPTION: main
    Process: com.android.developer.crashsample, PID: 3686
    java.lang.NullPointerException: crash sample
    at com.android.developer.crashsample.MainActivity$1.onClick(MainActivity.java:27)
    at android.view.View.performClick(View.java:6134)
    at android.view.View$PerformClick.run(View.java:23965)
    at android.os.Handler.handleCallback(Handler.java:751)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:156)
    at android.app.ActivityThread.main(ActivityThread.java:6440)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:746)
    --------- beginning of system
    

スタック トレースには、クラッシュのデバッグに不可欠な、次の 2 つの情報が表示されます。

  • スローされた例外のタイプ。
  • 例外がスローされたコードのセクション。

一般的に、スローされた例外のタイプは、何が問題かを知るための強力なヒントになります。例外が IOException か、OutOfMemoryError か、またはそれ以外かを確認して、該当する例外クラスに関するドキュメントを参照します。

スタック トレースの 2 行目には、例外がスローされたソースファイルのクラス、メソッド、ファイル、行番号が示されます。呼び出された関数ごとに、直前の呼び出しサイト(スタック フレームと呼びます)が別の行に表示されます。スタックをたどりながらコードを調べることで、誤った値を渡している場所を見つけられることがあります。コードがスタック トレースに表示されていなければ、どこかで無効なパラメータを非同期処理に渡している可能性があります。たいていの場合、スタック トレースの行を 1 つ 1 つ調べて、使用した API クラスを探し出し、渡したパラメータが正しいか、適切な場所から API を呼び出したかを確認すれば、何が起きたかを把握できます。

ネイティブ アプリでのクラッシュについては、ネイティブ クラッシュの診断をご覧ください。

クラッシュを再現するためのヒント

エミュレータを起動したりデバイスをパソコンに接続したりするだけでは、問題を完全には再現できないかもしれません。多くの場合、開発環境は、帯域幅、メモリ、ストレージなどのリソースに余裕があります。例外のタイプを確認することで、不足しているリソースを特定したり、Android のバージョン、デバイスタイプ、アプリのバージョン間の相関を発見したりすることができます。

メモリエラー

OutOfMemoryError が発生する場合は、まず、メモリ容量が小さいエミュレータを作成します。図 2 は、デバイスのメモリ容量を制御できる AVD Manager 設定を示しています。

AVD Manager のメモリ設定

図 2. AVD Manager のメモリ設定

ネットワーク例外

ユーザーはモバイル ネットワークや Wi-Fi ネットワークの受信可能範囲を頻繁に出入りします。したがって、一般的に、アプリのネットワーク例外は、エラーとしてではなく、予期せず発生する正常な動作状態として扱う必要があります。

ネットワーク例外(UnknownHostException など)を再現する必要がある場合は、アプリがネットワークを使用しようとしたときに機内モードをオンにしてみてください。

また、ネットワーク速度のエミュレーションまたはネットワーク遅延(あるいはその両方)をエミュレータで選択することにより、ネットワーク品質を低下させる方法もあります。AVD Manager の [Speed] と [Latency] の設定を使用するか、コマンドラインで -netdelay フラグと -netspeed フラグを指定してエミュレータを起動します(次の例を参照)。

emulator -avd [your-avd-image] -netdelay 20000 -netspeed gsm
    

この例では、すべてのネットワーク リクエストに対して、20 秒の遅延を設定し、アップロードとダウンロードの速度を 14.4 Kbps に設定しています。エミュレータのコマンドライン オプションの詳細については、コマンドラインからのエミュレータの起動をご覧ください。

logcat で確認する

クラッシュを再現する手順を実施できるようになったら、logcat などのツールを使用してさらに情報を収集します。

logcat の出力には、デベロッパーが出力したログメッセージが、システムによって出力された他のメッセージとともに表示されます。アプリの実行中にログを出力すると CPU の負荷が増えて電池が消耗するため、特別に追加した Log ステートメントをオフにすることを忘れないでください。