「アプリケーション応答なし」イベントと「過度の wakeup」という安定性に関する主要な 2 つの問題を詳しく分析して、アプリの機能と品質を向上します。
はじめに
この記事は CodeProject のスポンサー向けの「Product Showcase」セクションに掲載されているものです。同セクションの記事は、デベロッパーにとって役立つ有用な製品やサービスに関する情報の提供を目的としています。
アプリのデベロッパーにとって、ユーザーの満足度が高いことに勝る成功の指標はありません。この目標を達成する最善の方法は、ユーザーが利用したくなる優れたアプリを提供することです。「優れた」アプリとは一体何を指すのかというと、機能とアプリの品質の 2 つに要約されます。前者は最終的にデベロッパーの創造力とビジネスモデルの選択に左右されるのに対し、後者は客観的に測定して改善することが可能です。
昨年 Google が実施した社内調査によると、Play ストアの 1 つ星レビューの 40% 以上で指摘されていたのは「アプリの安定性」の問題でした。一方で、品質やパフォーマンスの良いアプリは高く評価されて好意的なレビューが付けられ、その結果 Google Play でのランキングが向上し、インストール数の上昇につながっています。それだけではなく、ユーザーはこうした高品質のアプリに、より多くの時間とお金を費やす傾向にあります。そのため、アプリの安定性に関する問題の解決が、アプリの成功に大きな影響を及ぼします。
客観的な品質の指標を提供し、アプリの安定性に関する問題をデベロッパーが簡単に特定して対応できるよう、Google では Play Console に [Android Vitals] セクションを新たに追加しました。このセクションでは、アプリのパフォーマンスと安定性に関する問題を確認できます。コードに新たな測定ツールやライブラリを追加する必要はありません。アプリがさまざまな端末上で実行されている間、Android Vitals はアプリのパフォーマンスに関する匿名の指標データを収集して、幅広い規模の情報をデベロッパーに提供します。同じような規模の情報を他の方法で収集することは難しく、たとえハードウェア ラボでテストを実施したとしても困難です。
Android Vitals がデベロッパーに通知できる問題には、クラッシュ、アプリケーション応答なし(ANR)、レンダリング時間などがあります。こうした問題はいずれも、ユーザーの利便性とアプリに対するイメージに直接影響します。さらに、ユーザーが特定のアプリと直接結び付けない可能性のある、アプリの不正な動作のカテゴリが 1 つ存在します。電池の消耗が予想以上に早い場合がそれに該当します。
この記事では、次の 2 つの問題について詳しく見ていきます。
- 過度の wakeup : 電池寿命に影響を与え、すぐに端末を充電できない場合はユーザーの端末の利用を妨げることがあります。この種の動作は、ユーザーがアプリをすぐにアンインストールする原因となる可能性があります。
- アプリケーション応答なし(ANR)イベント : こうしたイベントは、アプリの UI がフリーズしたときに記録されます。フリーズの発生時にアプリがフォアグラウンドで動作していれば、ユーザーに対して、アプリを閉じるか、アプリから応答があるまで待つかを尋ねるダイアログが表示されます。ユーザーの視点からは、この動作はアプリがクラッシュしたのと同じです。アプリがすぐにアンインストールされるとは限りませんが、ANR が続くと、ユーザーが別のアプリを探す可能性は高くなります。
過度の wakeup
まず、wakeup とはどのようなもので、どのような場合に「過度」になるのでしょうか。
電池寿命を延ばすために、Android 搭載端末では画面がオフになると、メイン CPU コアをオフにしてディープ スリープモードに入ります。ユーザーが端末を復帰させない以上、端末をできるだけ長くスリープモードにしておくことが望ましい状態です。ただし、アラームが鳴ったときや新しいチャット メッセージが届いたときのように、CPU を復帰させてユーザーに通知することが重要なイベントもあります。
こうした通知は wakeup アラームで処理できますが、後で説明するとおり、この方法は使用しないでください。wakeup は、ユーザーの注意を重要なイベントに引き付ける良い手段のように思われますが、wakeup の回数が多すぎると電池寿命に影響を与えます。
Android Vitals では過度の wakeup がどのように表示されるか
アプリの wakeup 回数が多すぎるかどうかは、Android Vitals で簡単に確認できます。アプリの動作について収集した匿名の指標データを使用して、端末がフル充電されてからの 1 時間あたりの wakeup が 10 回を超えるユーザーの割合が表示されます。赤いアイコンが表示されていないか確認することが重要です。このアイコンは、アプリが不正な動作のしきい値を超えていることを示します。このしきい値は、対象のアプリが Google Play で動作に問題のある一連のアプリの中に含まれており、動作を改善する必要があることを表します。
wakeup アラームの代替手段
一定時間の経過後や特定の時刻に端末を復帰させる主な方法として、AlarmManager API を使用して RTC_WAKEUP フラグや ELAPSED_REALTIME_WAKEUP フラグでアラームをスケジュールする方法があります。この機能はなるべく使用せず、他のスケジューリング方法や通知方法ではうまく機能しない場合にのみ使用することが重要です。wakeup アラームの使用を検討している場合は、以下の点を考慮してください。
- ネットワークからデータを受信した際に情報を表示する必要がある場合は、Firebase Cloud Messaging などの実装によるプッシュ メッセージの使用を検討します。この方法を使うと、新しいデータがないか定期的にポーリングするのではなく、必要な場合にのみアプリが復帰するようになります。
- プッシュ メッセージを使うことができず、定期的なポーリングを行う場合は、JobScheduler か Firebase JobDispatcher(アカウント データの場合は SyncManager も含む)の使用を検討します。こうした API は AlarmManager よりもはるかに上位の API であり、スマートなジョブ スケジューリングに役立つ以下のような利点があります。
- 一括処理: システムを何度も復帰させてジョブを実行するのではなく、複数のジョブを一括処理することで、端末をより長時間スリープ状態にしておくことができます。
- 条件に応じた動作: ネットワークが利用可能かどうかや電池の充電状態などの条件を指定して、その条件を満たす場合にのみジョブを実行するようにできます。条件を使用すると、不必要な端末の復帰とアプリの実行を防止できます。
- 持続性と自動復帰: ジョブに持続性を持たせる(再起動時も含む)とともに、失敗時に自動的に再試行できます。
- Doze への対応: Doze モードやアプリ スタンバイによって課された制約がない場合にのみ、ジョブが実行されます。
プッシュ メッセージングやジョブ スケジューリングの使用が不適切な場合のみ、AlarmManager を使用して wakeup アラームをスケジュールする必要があります。言い換えれば、wakeup アラームが必要となるのは、ネットワークやその他の条件に関わらず、特定の時刻にアラームを鳴らす場合のみです。
Android Vitals で過度の wakeup が表示された場合の対処方法
過度の wakeup に対処するには、アプリ内で wakeup アラームをスケジュールしている場所を特定し、アラームのトリガーされる頻度を減らします。
アプリで wakeup アラームをスケジューリングしている場所を特定するには、Android Studio で AlarmManager クラスを開き、[RTC_WAKEUP] または[ELAPSED_REALTIME_WAKEUP] を右クリックして [使用箇所を検索] を選択します。これにより、プロジェクト内での該当するフラグの使用箇所がすべて表示されます。それぞれの使用箇所で、よりスマートなジョブ スケジューリングの方法に変更できないか確認します。
また、[使用箇所を検索] のオプションでスコープを [プロジェクトとライブラリ] に設定して、依存関係で AlarmManager API を使用しているかどうかも確認できます。使用している場合は、別のライブラリの使用を検討するか、作成者に問題を報告する必要があります。
wakeup アラームを使用しなければならない場合、アラームに以下のような適切なタグを付けることで、Play Console に有用な分析データが表示されます。
- アラームのタグ名に、パッケージ名、クラス名、メソッド名のいずれかを含めます。これはソース内でアラームが設定された場所の特定に役立ちます。
- アラーム名には Class#getName() を使用しないでください。Proguard によって難読化される可能性があります。代わりに、ハードコーディングされた文字列を使用します。
- アラームのタグには、カウンタやその他の固有の ID を追加しないでください。システムによってタグが削除され、有用なデータとして集約できなくなる可能性があります。
アプリケーション応答なし
次に、アプリケーション応答なし(ANR)と、この問題がどのようにユーザーに影響するかを見てみましょう。
ユーザーの視点では、ANR は、自分がアプリを操作しようとしているのにインターフェースがフリーズしている状態です。インターフェースがフリーズしたまま数秒経過した後、このまま待つか、アプリを強制終了するかを尋ねるダイアログが表示されます。
アプリ開発の視点では、ANR は、アプリがディスク I/O やネットワーク I/O といった長時間の処理によってメインスレッドをブロックしている場合に発生します。メインスレッド(UI スレッドとも呼ばれます)は、ユーザー イベントへの応答と、画面の描画内容の更新(1 秒間に 60 回)を担っています。そのため、メインスレッドの動作を遅延させる可能性のある処理はすべて、バックグラウンド スレッドに移すことが重要です。
Android Vitals では ANR がどのように表示されるか
Android Vitals では、アプリの ANR イベントについて収集された匿名の指標データを使用して、ANR に関する詳細がいくつかのレベルで表示されます。メインの画面にはアプリの ANR アクティビティの概要が表示されます。1 回以上 ANR が発生した 1 日のセッションの割合が、過去 30 日間とその前の 30 日間についてそれぞれ個別のレポートで表示されます。不正な動作のしきい値も併せて表示されます。
詳細ビュー([ANR 発生率] ページ)には、ANR 発生率の推移の詳細と、アプリのバージョン、アクティビティ名、ANR の種類、Android のバージョン別の ANR の詳細が表示されます。このデータは、APK バージョン コード、サポートされている端末、OS バージョン、期間で絞り込むことができます。
[ANR とクラッシュ] セクションではさらに詳細なデータを確認できます。
ANR が起こる一般的な原因
前述のとおり、ANR はアプリのプロセスがメインスレッドをブロックした場合に発生します。こうしたブロックはさまざまな理由で発生しますが、一般的な原因は次のとおりです。
- メインスレッドでディスクやネットワークの I/O を実行している。これは ANR が発生する最も一般的な原因です。ほとんどのデベロッパーは、ディスクやネットワークに対するデータの読み取りや書き込みはメインスレッドで実行すべきでないと理解していますが、時として、その誘惑に駆られます。通常の条件下でディスクから数バイトのデータを読み込んでも ANR の原因になるおそれはまずありませんが、それでも、こうした処理はおすすめしません。ユーザーの端末のフラッシュ メモリが低速な場合や、他の複数のアプリによる読み取りや書き込みが同時に発生し、端末が高負荷状態で、その間自分のアプリの「簡単な」読み取り処理がキュー内で待機しなければならない場合は、どうなるでしょうか。メインスレッドでは I/O を実行しないでください。
- メインスレッドで長時間の計算を実行している。メモリ内での計算処理について考えてみましょう。RAM は長いアクセス時間に悩まされることはなく、小さな処理であればまず問題ありません。しかし、ループ内で複雑な計算処理やサイズの大きいデータセットの処理を開始すると、メインスレッドをブロックするおそれがあります。ピクセル数が多い大きな画像をサイズ変更したり、大量の HTML テキストを解析したうえで TextView に表示したりすることを検討してください。通常、アプリではこうした処理をバックグラウンドで実行することをおすすめします。
- メインスレッドから別のプロセスに対する同期 binder 呼び出しを実行している。ディスクやネットワークの操作と同様に、プロセスの境界をまたいだブロッキング呼び出しを行うと、プログラムの実行は、自分のアプリでは制御できない別のプロセスに渡されます。そのプロセスがビジー状態である場合や、アプリのリクエストに応答するためにディスクやネットワークへのアクセスが必要な場合は、どうなるでしょうか。さらに、別のプロセスにデータを渡す場合はデータのバイト配列化や復元が必要となるため、その処理にも時間が必要です。プロセス間呼び出しはバックグラウンド スレッドから行うことをおすすめします。
- 同期を使用している。高負荷の処理をバックグラウンド スレッドに移した場合でも、メインスレッドと通信して計算処理の進行状況や結果を表示する必要があります。マルチスレッド プログラミングは簡単ではないうえ、通常、ロックのために同期を使用する場合、実行がブロックされないようにするのは困難です。最悪の場合、スレッド同士が互いに処理の完了を待ち続けるデッドロックとなるおそれもあります。自分で同期を設計することは避けるようおすすめします。Handler などの特定目的向けソリューションを利用して、不変のデータをバックグラウンド スレッドからメイン スレッドに渡してください。
ANR の原因を特定する方法
ANR の原因の特定は容易ではありません。
URL クラスを例に説明します。2 つの URL が同じかどうかを判別するメソッド
URL#equals がブロックの原因となる可能性はあるでしょうか。SharedPreferences についてはどうでしょうか。バックグラウンドで
getSharedPreferences から値を読み込む必要がある場合、メインスレッドで getSharedPreferences を呼び出せるでしょうか。いずれの場合も、処理が長時間のブロックの原因となる可能性があります。
メインスレッドでのブロックの原因となる呼び出しを排除したら、必ず StrictMode をオフにしてから、Play ストアでアプリを公開してください。
過度の wakeup や ANR を排除すると、アプリの品質と利便性が改善されて、高い評価や好意的なレビューの獲得につながり、インストールが増加します。Android Vitals をチェックすることで、対応の必要な問題の有無を素早く簡単に確認できます。コード内のこうした問題を特定して解決する作業は必ずしも簡単ではありませんが、さまざまなツールや手法を活用してこの作業を効率的に進めることができます。
Android Vitals の活用方法はこれ以外にもあり、そうした機能についてさらに次回の記事でご紹介します。
Android Vitals についてご意見がございましたら、#AskPlayDev を付けてツイートしていただければ、
@GooglePlayDev から返信いたします。このアカウントでは定期的にニュースや Google Play で成功するためのヒントを紹介しています。