まず、2つのことについて説明します。
例外とは、プログラムの実行中に発生し、本来のプログラムの動作を中断させるものです。そして、例外処理とは、例外に対応するための処理のことです。
Androidでは、例外を処理しないとアプリケーションがクラッシュし、恐ろしい「App keeps stopping」ダイアログが表示されることになります。
クラッシュし続けるアプリを使う人はいないですよね。ですので、例外処理を行うことは非常に重要なことであるといえます。
この記事では、例外を処理するいくつかの方法と、「処理されない例外」を処理する方法を紹介します。
また、これらの例外をキャッチする方法も紹介します。今回の記事における例については、Kotlinで解説します。
例外の階層構造
すべての例外は、Throwableのサブクラスです。ドキュメントによると…
”Throwableクラスは、Java言語におけるすべてのエラーと例外のスーパークラスです。このクラス(またはそのサブクラスの1つ)のインスタンスであるオブジェクトのみが、Java仮想マシンによってスローされるか、Javaのthrow文によってスローすることができます。”
例外の種類
Javaでは、例外は主に大きく2つに分けられます。チェックされた例外と、チェックされていない例外の2種類です。
チェックされた例外: コンパイル時にチェックされる例外です。例外を投げるメソッドを呼び出す場合は、呼び出し側として例外を処理するか、throws句で宣言する必要があります。
チェックされる例外の例: IOException、NoSuchMethodException、ClassNotFoundException
チェックされていない例外: コンパイル時にチェックされない例外のことです。
チェックされていない例外の例: NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException。
興味深いのは、Kotlinにはチェックされた例外がないということです。次のコードをご覧ください。
このメソッドを呼び出したら、どうなると思いますか?「実行時例外が発生する」と思ったあなたは正しいです。
別のコードを考えてみましょう。
doSomethingメソッドを呼び出すと、ClassCastExceptionが発生します。
これらのスニペットを実行したい場合は、ここで実行することができます。
とあるシナリオ(処理のパターン)を定義するために、内蔵の例外クラスでは不十分な場合があります。このような場合、独自の例外を作成することも可能です。
例外処理のさまざまなやりかた
例外について理解したところで、例外をどのように扱えばよいかを見ていきましょう。まずは、例外を投げる小さなコードの一部を考えてみましょう。
このメソッドを呼び出して例外を処理しないと、アプリケーションがクラッシュし、「App keeps stopping」ダイアログが表示されることになります。
この例外を処理する最も簡単な方法は、メソッド呼び出しをtryブロックで囲んで、例外をキャッチすることです。必要であれば、finallyブロックで処理を追加することもできます。
例外をキャッチするだけで、finallyブロックで実行する追加処理がない場合、KotlinはrunCatching関数を提供します。
これらのスニペットを実行したい場合は、ここで実行することができます。
例外を伝播させる
あるメソッドがさまざまな例外を投げる場合、そのうちの一部だけを受け取り、他の例外は上位に伝搬させるというパターンが考えられます。
次のようなコードを考えてみましょう。
try/catch文を使うと、次のようなことができます。
runCatchingを使うと、以下のようなことができます。
ここで重要なことは、例外を伝播させるため、より上位で例外を処理する必要があるということです。
これらのスニペットを実行したい場合は、ここで実行することができます。
Androidの例外処理
上記のテクニックを使って例外を処理するサンプルアプリケーションを考えてみましょう。
このアプリには1つの画面があり、2つのボタンがあります。それらがクリックされると何らかの処理が行われるようになっています。
最初のボタンは必要な例外処理をしており、2番目のボタンをクリックするとアプリがクラッシュします。
コードは非常にシンプルです。
実際のところ、どんなに最善を尽くしてもすべての例外を処理することは非常に困難です。もしかしたら、内部ライブラリの例外が発生するかもしれませんし、稀に処理されない「エッジケースシナリオ」があるかもしれません。
では、アプリケーションがクラッシュする頻度を最小限にするためには、どうすればよいのでしょうか。
Thread.UncaughtExceptionHandlerインターフェイスを使用することで、デフォルトの例外ハンドラにフォールバックすることが可能です。
しかし、デバッグアプリでこれを行うことは決してお勧めしません。なぜなら、クラッシュが発生したらすぐにフィードバックが欲しいからです。
これで、2つ目のボタンをクリックしても、例外を明示的に処理していないにもかかわらず、アプリがクラッシュすることはなくなりました。
これまで、さまざまな例外処理の方法と、デフォルトの例外処理されていない時の例外ハンドラを提供する方法について見てきました。では、さらに一歩進んで、これらの例外を監視する方法を見てみましょう。
例外をレポートする
アプリをリリースしたら、発生したすべてのエラー/例外を追跡したいと思うでしょう。それらのエラー/例外を解消したいですもんね。
SentryのAndroid SDKは、それを実現する方法と、それ以上のものを提供します。
まずアカウントを作成し、アプリケーションを監視するためのプロジェクトをセットアップします。
以下の手順で、Android SDKをAndroidアプリケーションに追加することができます。
セットアップが完了したら、さっそく例外のレポート作成を行いましょう。例外を捉えるためには、次のように呼び出します。
おそらくもうお分かりだと思いますが、すべての例外を記録するために、catchブロックまたはonFailureブロック内でこれを行います。
それでは、handleUncaughtExceptionメソッドに追加してみましょう。
すると、Sentryのダッシュボードで、キャプチャされたすべての例外の詳細について確認できるようになります。
「issue」をクリックすると、詳細なスタックトレースを見ることができます。
また、例外が発生する前に起こった出来事の痕跡を示す「パンくず」も取得できます。
Sentryは、パフォーマンスのモニタリングやプロファイリングのような機能を備えており、エラー処理にとどまりません。
これらについては今後の記事で紹介しようと思います。
ざっくりと振り返ってみましょう。
- 例外とは、プログラムの実行中に発生し、本来のプログラムの動作を中断させます。
- すべての例外は Throwable のサブクラスです。
- 例外には一般的にチェック付きとチェック無しの2種類があります。
- 例外を処理するには、try/catchまたはrunCatchingを使用します。
- Androidでは、すべての例外処理していない例外も処理することができます:Thread.UncaughtExceptionHandlerを使用します。
- SentryのAndroid SDKを使用して例外を報告するには、次のようにします: Sentry.captureException(throwable)
これで、さまざまな例外について、またその対処法やレポート方法について、より深く理解できたかと思います。
また、サンプルアプリケーションの完全なコードは、このリポジトリにあります。
https://github.com/rubenquadros/Sentry-Exception-Handling
Sentryは、アプリケーションコードの健全性を監視するために不可欠です。エラートラッキングからパフォーマンスモニタリングまで、開発者は、フロントエンドからバックエンドまで、アプリケーションをより明確に把握し、より迅速に解決し、継続的に学習することができます。
Sentryは、世界中の350万人以上の開発者と85,000以上の組織に愛され、Disney、Peloton、Cloudflare、Eventbrite、Slack、Supercell、Rockstar Gamesといった世界的有名企業の多くにコードレベルの監視機能を提供しています。
毎月、世界中で人気のサービスやアプリケーションから、数十億件の例外を処理し続けています。
IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談はこちらのフォームからお気軽にお問い合わせください。