Unity ANR 的发生有多种原因。最常见的 ANR 是由于 Android 和 Unity 组件的误用以及它们之间的通信错误造成的。
WebView
WebView
是一个显示网页的 Android 类。第三方 SDK(例如广告 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
阻塞主线程,请在加载、显示或关闭网页时向您的游戏添加日志。- 您可以使用 Backtrace 或 Crashlytics 报告服务。
- 然后,在分析数据并找到问题后,尝试禁用有问题的广告提供商。
- 包括内存日志以确保问题与内存无关。
- 提醒用户从 Google Play 更新
WebView
。从 Android 5.0(API 级别 21)及更高版本开始,WebView
已移至 APK。因此,它可以独立于 Android 平台进行更新。要查看设备上使用的WebView
版本,请转到 Settings > Apps > Android System WebView 并查看页面底部的版本。

WebView
版本。Unity 暂停
当 UnityPlayerActivity
收到 onPause()
调用时,以下操作链开始:
UnityPlayerActivity
通知 Unity 运行时引擎活动已暂停。- Unity 调用每个实现
OnApplicationPause
事件的MonoBehaviour
。 - Unity 停止其组件和模块,例如声音播放、渲染、游戏循环和动画。
- 为了确保
Unity Android Player
(UAP) 和引擎同步,UAP 会等待 4 秒,以便引擎停止。 - 如果该操作耗时超过 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
进行通信而不是消息。
- 如果您的代码不依赖于 Unity 的主线程上下文,请使用
- 当游戏暂停时,不要从您的插件发送多条消息。
- 游戏在后台时,引擎无法发送消息。
- 如果这不影响您的游戏功能,则只将最后的数据状态发送给您的游戏。
安装 referrer
Play Install Referrer 是用户点击广告时发送到 Play 商店的唯一字符串。它是一个 Android 特定的广告跟踪标识符。安装后,应用程序将安装 referrer 发送给归因合作伙伴,后者将来源与安装匹配(归因转化)。
堆栈跟踪
图 5 显示了使用 Facebook SDK 检索安装归因的游戏的 ANR 堆栈跟踪。

原因
ANR 是由缓慢的 binder 调用引起的。但是,在没有 SDK 源代码访问权限的情况下无法确定根本原因。
解决方案
解决此类问题需要与 SDK 开发者沟通,或者大量在线搜索潜在解决方案,检查是否有更新的 SDK 版本为其他人解决了 ANR,甚至尝试小范围发布策略。
Google 提供了一个 SDK 索引页面,它结合了 Google Play 应用的使用数据和通过代码检测收集的信息,提供属性和信号,旨在帮助您决定是否在应用中采用、保留或移除某个 SDK。
其他资源
要了解有关 ANR 的更多信息,请查阅以下资源: