Google Play ConsoleでのANRの調査


Google Play Consoleでは、アプリのクラッシュやANRの発生ログを確認することができます。
ANRは、クラッシュに比べると発生原因や再現手順を比較的追いにくい場合が多いです。

私も、最初はANRのログをどう追えばいいかわからなくて苦労したので、Google Play ConsoleでのANRの調査についてまとめました。

ANRの基本的な知識

念のため、はじめにANRの基本についても書いておきました。
基本的な内容なので、読み飛ばしていただいでも結構です。

ANRとは

ANRは、Application Not Respondingの略です。
その名の通り、アプリが応答していないことをユーザーに伝えるための仕組みです。

ANRが発生すると、「アプリケーション応答なし」のダイアログが表示されます。
「ボタンをタップしたらフリーズした…」といったときに、表示されるダイアログがANRのダイアログです。

ANRの発生条件

ANRの発生条件は以下の2つです。

  • 入力イベント(キーの押下や画面タッチなどのイベント)に対する応答が 5 秒以内にない
  • BroadcastReceiverの実行が 10 秒以内に終了しない

ANRの発生要因

ANRは、UIスレッドで実行時間の長い処理を行って処理をブロックしてしまうことが要因で起こります。

UIスレッドは、画面の描画などGUI関連の操作に使用されるスレッドですが、タッチイベントなどユーザーの操作に対してフィードバックを与えるようなイベント処理も行います。

このフィードバックが遅くなると、ユーザーから「なぜか反応しない。固まった。フリーズしたああああ」など異常事態に感じられてしまいます。

なので、素早く処理をしてあげたいところですが、UIスレッドはスレッドなので、同時には1つの処理しかできません。

つまり、このUIスレッドでユーザーのタッチ反応とは別で、長い処理(ネットワーク通信やデータベース関連の処理など)を行ってしまうと、ユーザが画面をタッチしても、タッチのイベント処理がこの長い時間のかかる処理が終わるのを待ってから処理されてしまう...ことになります。

そのために、UIスレッドで実行されるメソッドでは、実行する処理をできる限り少なくする必要があります。

ANRの対策方法

以下の方法が一般的なANRの対策になります。

  • 主なライフサイクルメソッド(onCreate() や onResume()など)で設定する内容はできる限り少なくする
  • ネットワークやデータベースの処理や、CPUへの負荷が高い計算(ビットマップのサイズ変更など)は、ワーカースレッドで行う

※データベース処理の場合はワーカースレッドもしくは非同期リクエストを使用するでもいいと思います。

UIスレッドで行う処理はなるべく短くして、時間のかかる処理は別スレッドで行う
これがタッチのUXを向上させ。ANRを防ぐ鉄則です。

Google Play consoleでのANRの調査方法

基本的な見方

Google Play console上に表示される、ANRのログの基本的な見方は以下の通りです。

  • 下から順番(降順)に追って見る
  • 黒字で書いてあるものは自分達が書いたコードで、グレーの文字は内部的な処理
  • 最新のエラー発生versionを見て、最初に該当のANRが発生したversionCodeを確認する(既存なのか新規発生なのかがわかる)

ANRを調査するときに意識すること

問題になっていそうな箇所がわかったら、次にその該当箇所のコードを見て、以下のような観点で調査をしていきます。

  • 「黒字」で書かれた自分たちのコードがどういうタイミングで走るのか?
  • UIスレッドで行われているか?
  • 通信周りやDB操作は「非同期」で処理していない所があるか?
  • ログにたくさん出ていないか?(ログにたくさん出ているところは、処理がネストしていることが原因になっていることが多い)

黒字でANRのログが表示されている例(内部的な処理じゃない場合)

ANRのログに黒字でログが出ている場合は、割と原因が追いやすいです。
例として、以下のログがGoogle Play Console上のANRのログ欄に 黒字で 表示されている場合を考えます。

at jp.co.hoge.view.ProgressAnimationLayout.<init> 
at jp.co.hoge.ui.base.BaseFragment.onCreateView (BaseFragment.java:126) 

まず、上記のログから、BaseFragmentonCreateView()でANRが発生していることがわかります。(onCreateView()にそんなに時間がかかっていなければログには表示されないので、onCreateView()の中の処理に絞ることができる)
onCreateView()が走っているということは、Fragmentが生成されたタイミングになるので、UIスレッドの処理の最中だったとわかります。

そして、ProgressAnimationLayout.<init>initとなっているのがポイントで、これは view の初期化タイミングを意味します。

ということは、

FragmentonCreateView()の中で ProgressAnimationLayoutの初期化の中で10秒以上たった = 「Fragment の View の初期化処理が重い」

といった感じで仮説を考えることができます。

全てグレー字でANRのログが表示されている例(内部的な処理の中でANRが発生している場合)

ANRを日々調査していると、
以下のように黒字が全くなく、内部的な処理の最中に発生したANRのログもよくあがってきます。

こういう場合も下から順に追って、怪しそうなやつを探します。

今回の場合だと、

at androidx.work.WorkManager.initialize<init> 

この辺りが怪しそうだなと思います。
WorkManegerの初期化に時間がかかってANRが発生してしまった可能性が考えられます。

例えば、

  1. アプリが死んでいる状態で、PlayService に登録していた WorkManager が立ち上がる
  2. Application 関係の初期化
  3. Worker の実行中に時間がかかってしまった

といったパターンが考えられます。

また、他のRunnableになっているスレッドをいくつか開いて調査も行います。

  • Runnableになっている Thread(Signal Catcher)をみるとAndroidのBootプロセス系が走っているところだった
  • Runnable になっている Thread(Queue)を見ると RemoteConfigManager.<init>が走っていて、held mutexes = mutator lock(shared held)と書いあった

以上から、

Remote Configの初期化に時間のかかった」かつ、 「mutax がロックされているのでどこかで共有プロセスがロックされて動けない状態が続いた」ことで 10秒経過してANRが発生した

といった感じで、内部的な処理の場合でも仮設を考えられます。

まとめ

以上が、私が日々行なっているGoogle Play ConsoleでのANRの調査でした。
自分なりの見解にはなりますが、ANRの原因調査などに参考になれば嬉しいです。

また、あくまで今回は、Google Play Console上のログから、原因調査をするところまでなので、
この後は、CPU Profilerなどを使用して、自分の仮説が正しいかさらに詳細な調査をしていく流れになっていくと思います。詳細はこちらの公式ページにわかりやすくまとまっています。
CPU Profiler を使用して CPU アクティビティを検査する

もし間違いなどあればご指摘いただけると嬉しいです。
最後まで読んでいいただき、ありがとうございました。

参考文献