クラッシュ

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 日のセッションは、アプリが使用された日を意味します。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 つの情報を取得できます。

  • スローされた例外のタイプ。
  • コードのどの部分で例外がスローされたか。

スローされた例外のタイプは、通常、クラッシュの原因を特定するための非常に強力なヒントになります。例外が IOExceptionOutOfMemoryError、またはそれ以外かを確認して、該当する例外クラスに関するドキュメントを確認します。

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

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

クラッシュの再現に関するヒント

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

メモリエラー

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

AVD Manager でのメモリ設定

図 2. AVD Manager でのメモリ設定

ネットワーク例外

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

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

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

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

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

logcat による確認

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

logcat の出力には、デベロッパーが出力した他のログメッセージが、システムから出力された他のメッセージとともに表示されます。アプリの実行中のログ出力は CPU を浪費し、電池を消耗させるため、追加した余分な Log ステートメントは必ず無効にしてください。