Unity 游戏常见 ANR

Unity ANR 出于多种原因发生。最常见的 ANR 是由 Android 和 Unity 组件的误用及其通信故障引起的。

WebView

WebView 是一个显示网页的 Android 类。第三方 SDK(例如广告)使用 WebViewUnityPlayerActivity 以外的活动中显示动态网页内容。当第三方 SDK 误用 WebView 时,会发生 ANR。

堆栈跟踪

堆栈跟踪是您理解 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 阻塞主线程的内容类型,请在加载、显示或关闭网页时向您的游戏中添加日志。
    • 您可以使用 BacktraceCrashlytics 报告服务。
    • 然后,在分析数据并找到问题后,尝试禁用有问题的广告提供商。
    • 包含内存日志以确保问题与内存无关。
  • 提醒用户从 Google Play 更新 WebView。从 Android 5.0(API 级别 21)及更高版本开始,WebView 已移至 APK。因此,它可以与 Android 平台分开更新。要查看设备上正在使用的 WebView 版本,请转到设置 > 应用 > Android 系统 WebView,然后查看页面底部的版本。
App info screen showing the WebView versions.
图 1. 检查 WebView 版本。

Unity 暂停

UnityPlayerActivity 接收 onPause() 调用时,以下操作链开始:

  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# asyncawait 关键字的简化 异步编程模型

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 安装推荐程序 是一个唯一字符串,每当用户点击广告时就会发送到 Play 商店。它是一个 Android 专用的广告跟踪标识符。安装后,应用会将安装推荐程序发送给归因合作伙伴,后者会将来源与安装匹配(确定转化归因)。

堆栈跟踪

图 5 显示了使用 Facebook SDK 检索安装归因的游戏的 ANR 堆栈跟踪。

图 5. 包含 Binder 调用的 Android Vitals 报告。

原因

ANR 是由缓慢的 Binder 调用引起的。但是,如果没有访问 SDK 源代码,则无法确定根本原因。

解决方案

解决此类问题需要与 SDK 开发人员沟通,或进行大量在线搜索以寻找可能的解决方案,检查较新版本的 SDK 是否为其他人解决了 ANR,甚至尝试小型推广策略。

Google 提供了一个SDK 索引页面,它将 Google Play 应用中的使用数据与通过代码检测收集的信息相结合,提供旨在帮助您决定是否采用、保留或从应用中删除 SDK 的属性和信号。

其他资源

要了解有关 ANR 的更多信息,请参阅以下资源: