Unity Games の一般的な ANR

Unity ANR はさまざまな理由で発生します。最も一般的な ANR は、Android コンポーネントと Unity コンポーネントの誤用と、それらの間の誤った通信が原因で発生します。

WebView

WebView は、ウェブページを表示する Android クラスです。サードパーティの SDK(広告など)は、WebView を使用して、UnityPlayerActivity 以外のアクティビティに動的なウェブ コンテンツを表示します。ANR は、サードパーティの SDK が WebView を誤用した場合に発生します。

スタック トレース

スタック トレースは、ANR の原因を理解するための最初の手段です。

/data/app/~~p-0ksfCD6bF6Sdq6kpVePg==/com.google.android.webview-5YQZOqKbbqp-uoLY6WYnTw==/base.apk!libmonochrome.so
  at J.N.Mhc_M_H$ (Native method)
  at org.chromium.components.viz.service.frame_sinks.ExternalBeginFrameSourceAndroid.doFrame (chromium-TrichromeWebViewGoogle.aab-stable-579013831:60)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1054)
  at android.view.Choreographer.doCallbacks (Choreographer.java:878)
  at android.view.Choreographer.doFrame (Choreographer.java:807)
  at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:1041)
  at android.os.Handler.handleCallback (Handler.java:938)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loop (Looper.java:223)
  at android.app.ActivityThread.main (ActivityThread.java:7721)
  at java.lang.reflect.Method.invoke (Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:592)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:952)

図 1. futex 待機が原因で発生した ANR のスタック トレース。

原因

現時点では、この問題の根本原因は不明です。考えられる原因は次のとおりです。

  • 広告の実装が不適切。
  • ユーザーがアプリの自動更新を無効にしている可能性があるため、WebView の古いバージョン。
  • システム リソース(CPU、GPU など)の使用率が高く、多くのプロファイリングが必要になる可能性があります。
  • シェーダー コンパイルがクラッシュする。これは、コンテンツに互換性のないシェーダーが含まれているか、古いバージョンの WebView がインストールされていることを示している可能性があります。

解決策

  • WebView がメインスレッドをブロックしている原因となっているコンテンツの種類を絞り込むには、ウェブページが読み込まれたとき、表示されたとき、閉じられたときに、ゲームにログを追加します。
    • Backtrace または Crashlytics レポート サービスを使用できます。
    • 次に、データを分析して問題を見つけたら、問題のある広告プロバイダを無効にしてみてください。
    • メモリログを含めて、問題がメモリに関連していないことを確認します。
  • Google Play から WebView を更新するようユーザーに通知します。Android 5.0(API レベル 21)以降では、WebView は APK に移動しました。そのため、Android プラットフォームとは別に更新できます。デバイスで使用されている WebView のバージョンを確認するには、[設定] > [アプリ] > [Android システム WebView] に移動し、ページの下部にあるバージョンを確認します。
WebView のバージョンが表示されたアプリ情報画面。
図 1. WebView のバージョンを確認��ます。

Unity の一時停止

UnityPlayerActivityonPause() 呼び出しを受信すると、次のオペレーション チェーンが開始されます。

  1. UnityPlayerActivity は、アクティビティが一時停止したことを Unity ランタイム エンジンに通知します。
  2. Unity は、OnApplicationPause イベントを実装するすべての MonoBehaviour を呼び出します。
  3. Unity は、サウンド再生、レンダリング、ゲームループ、アニメーションなどのコンポーネントとモジュールを停止します。
  4. Unity Android Player(UAP)とエンジンが同期されるように、UAP はエンジンが停止するまで 4 秒間待機します。
  5. そのオペレーションに 5 秒以上かかると、システムは ANR をトリガーします。

スタック トレース

"main" tid=1 Timed Waiting
jdk.internal.misc.Unsafe.park (Native method)
java.util.concurrent.locks.LockSupport.parkNanos (LockSupport.java:234)
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedNanos (AbstractQueuedSynchronizer.java:1079)
java.util.concurrent.locks.AbstractQueuedSynchronizer.tryAcquireSharedNanos (AbstractQueuedSynchronizer.java:1369)
java.util.concurrent.Semaphore.tryAcquire (Semaphore.java:415)
com.unity3d.player.UnityPlayer.pauseUnity (UnityPlayer.java:833)
com.unity3d.player.UnityPlayer.pause (UnityPlayer.java:796)
com.unity3d.player.UnityPlayerActivity.onPause (UnityPlayerActivity.java:117)
android.app.Activity.performPause (Activity.java:8517)
android.app.Instrumentation.callActivityOnPause (Instrumentation.java:1618)
android.app.ActivityThread.performPauseActivityIfNeeded (ActivityThread.java:5061)
android.app.ActivityThread.performPauseActivity (ActivityThread.java:5022)
android.app.ActivityThread.handlePauseActivity (ActivityThread.java:4974)
android.app.servertransaction.PauseActivityItem.execute (PauseActivityItem.java:48)
android.app.servertransaction.ActivityTransactionItem.execute (ActivityTransactionItem.java:45)
android.app.servertransaction.TransactionExecutor.executeLifecycleState (TransactionExecutor.java:179)
android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:97)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2303)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:201)
android.os.Looper.loop (Looper.java:288)
android.app.ActivityThread.main (ActivityThread.java:7884)
java.lang.reflect.Method.invoke (Native method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:936)

図 3. リリースされないセマフォが原因で ANR が発生しました。

解決策

一時停止イベントまたは再開イベント中に C# ゲームコードの実行が完了するまでに時間がかかりすぎないようにします。

  • ゲームをプロファイリングし、OnApplicationPause がコストの高いオペレーションかどうかを確認します。Stopwatch を使用できます。
  • I/O オペレーションや同期ネットワーク リクエストを避ける。
  • Task を使用して、オペレーションを別の Thread に移動します。Unity 2023.1 は、C# の async キーワードと await キーワードを使用して、簡略化された非同期プログラミング モデルをサポートしています。

UnitySendMessage がブロックされる

Java Unity プラグインと SDK は、JNI を使用して C# ゲームレイヤにデータを送信します。ただし、この通信は、ミューテックスなどのネイティブの同期ルーティンが原因でメインスレッドをブロックし、ロック競合が原因で ANR を発生させる可能性があります。

スタック トレース

図 4 の ANR は、Java プラグインによって呼び出された C# コードの長いオペレーションが原因で発生しました。Unity エンジンは、非優先度継承ミューテックスを使用して、正しい実行を保証します。

libc.so NonPI::MutexLockWithTimeout(pthread_mutex_internal_t*, bool, timespec const*) + 604
com.unity3d.player.UnityPlayer.nativeUnitySendMessage (Native method)
com.unity3d.player.UnityPlayer.UnitySendMessage (UnityPlayer.java:665)

図 4.ロックの競合が原因で発生した ANR。

原因

問題は、アプリケーションが再開されたときに複数のメッセージがディスパッチされることです。ゲームがバックグラウンドにある間はメッセージを送信できないため、メッセージはキューに登録されます。アプリが再開されると、すべてのメッセージが同時にディスパッチされます。

一時停止期間中、通常はゲームの情報をサーバーに保存します。たとえば、ゲーム内のプレーヤーの位置を記録して、ゲームが再開したときにプレーヤーが同じ場所に戻れるようにします。

このワークロードは、独自のワークロードを作成する他のサードパーティ コードと組み合わさると、デバイスのリソース、特にメインスレッドに過負荷をかける可能性があります。メインスレッドはアプリのユーザー インターフェースを実行し、ANR の主な発生場所となることがよくあります。そのため、メインスレッドに追加されたワークロードは、ANR の可能性を高めます。

解決策

アプリの一時停止中に、すべてのコード アクションが必要であることを確認するか、ユーザーの状態をローカル デバイスのメモリに保存してみてください。また、一時停止期間外でもこれらの操作を実行できるかどうかを確認します。

いくつかの方法:

  • メッセージを処理する C# オペレーションをメインスレッド以外のスレッドに移動します。
    • コードが Unity のメインスレッド コンテキストに依存しない場合は、メッセージの代わりに Task を通信に使用します。
  • ゲームが一時停止しているときに、プラグインから複数のメッセージを送信しないでください。
    • ゲームがバックグラウンドにある間、エンジンはメッセージを送信できません。
    • ゲームの機能に影響しない場合にのみ、最後のデータ状態をゲームに送信します。

インストール リファラー

Play Install Referrer は、ユーザーが広告をクリックするたびに Google Play ストアに送信される一意の文字列です。これは Android 固有の広告トラッキング ID です。インストールが完了すると、アプリはインストール リファラーをアトリビューション パートナーに送信します。アトリビューション パートナーは、ソースをインストールと照合し(コンバージョンを帰属させます)、

スタック トレース

図 5 は、Facebook SDK を使用してインストール アトリビューションを取得するゲームの ANR スタック トレースを示しています。

図 5. Binder 呼び出しを含む Android Vitals レポート。

原因

ANR はバインダー呼び出しが遅いことが原因で発生しました。ただし、SDK のソースコードにアクセスできないと、根本原因を特定できません。

解決策

この種の問題を解決するには、SDK デベロッパーとのコミュニケーション、解決策の可能性を探すためのオンライン検索、新しいバージョンの SDK で他のユーザーの ANR が解決されるかどうかの確認、小規模なロールアウト戦略のテストなどを行う必要があります。

Google は、Google Play アプリの使用状況データとコード検出によって収集された情報を組み合わせて、アプリで SDK を導入するか、維持するか、削除するかを判断する際に役立つような属性とシグナルを提供する SDK Index ページを提供しています。

参考情報

ANR について詳しくは、以下のリソースをご覧ください。