Unity 游戏常见 ANR

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

WebView

WebView 是一个显示网页的Android类。第三方 SDK(例如广告)使用 WebView 在除 UnityPlayerActivity 之外的活动中显示动态网络内容。当第三方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,请参阅以下资源: