调试 ANR

解决 Unity 游戏中的 ANR 问题是一个系统化的流程:

图 1. 解决 Unity 游戏中 ANR 问题的步骤。

集成报告服务

Android VitalsFirebase CrashlyticsBacktrace(经过认证的 Unity 合作伙伴)等报告服务可为您的游戏提供大规模的错误日志记录和分析。在开发周期的早期将报告服务 SDK 集成到您的游戏中。分析哪种报告服务最符合您的游戏需求和预算。

不同的报告服务捕获 ANR 的方式不同。添加第二个报告服务,以提高获取有效数据的几率,为您修复 ANR 问题提供依据。

集成报告 SDK 不会影响游戏性能或 APK 大小。

分析符号

分析报告服务中的报告,并检查堆栈轨迹是否采用人类可读的格式。如需了解详情,请参阅对 Unity 游戏的 Android 崩溃和 ANR 问题进行符号化解析

图 2. Crashlytics 显示 build ID 且缺少 libil2cpp.so 符号。

如何查看符号 build ID

如果报告系统显示 build ID 缺失,但 build 符号仍存在于 build 机存储空间中,则可以检查符号的 build ID,然后将其上传到报告服务。否则,您需要使用新的 build 上传符号文件。

在 Windows 或 macOS 上:

  1. 根据脚本后端导航到符号文件夹(请参阅解决方法):
    1. 使用以下命令(在 Windows 上,使用 Cygwin 运行 readelf 实用程序)
    2. 您可以根据需要使用 Grep 过滤文本输出
    3. 查找 build ID
readelf -n libil2cpp.so | grep 'Build ID'
Build ID: b42473fb7449e44e0182dd1f580c99bab0cd8a95

检查游戏代码

如果堆栈轨迹显示 libil2cpp.so 库中的函数,则表示错误发生在 C# 代码(已转换为 C++)中。libil2cpp.so 库不仅包含游戏代码,还包含插件和软件包。

C++ 文件名遵循 Unity 项目中定义的汇编名称。否则,文件名将采用默认的 Assembly-C# 名称。例如,图 3 显示了文件 Game.cpp(以蓝色突出显示)的错误,该文件是汇编定义文件中定义的名称。Logger 是 C# 脚本中的类名称(以红色突出显示),后跟函数名称(以绿色突出显示)。最后是 IL2CPP 转换器生成的完整名称(以橙色突出显示)。

图 3. 通过回溯测试项目调用堆栈。

执行以下操作,检查游戏代码:

  • 检查 C# 项目,看是否存在任何可疑代码。通常,C# 未处理的异常不会导致 ANR 或应用崩溃。即便如此,也要确保代码在不同情况下都能正常运行。检查代码是否使用了第三方引擎模块,并分析近期的版本是否引入了错误。此外,请检查您是否最近更新了 Unity,或者错误是否仅在特定设备上发生。
  • 将游戏导出为 Android Studio 项目。如果您拥有对游戏转换后的 C# 源代码的完全访问权限,就可以找到导致 ANR 的函数。C++ 代码与 C# 代码看起来截然不同,并且代码转换很少会出现问题。如果您发现了问题,请向 Unity 提交支持服务工单。
  • 查看游戏源代码,确保适当清理在 OnApplicationFocus()OnApplicationPause() 回调中运行的任何逻辑。
    • Unity 引擎具有用于暂停其执行的超时设置;如果这些回调的工作负载过多,可能会导致 ANR。
    • 向代码的各个部分添加日志或面包屑导航可增强数据分析。
  • 使用 Unity 性能分析器来调查游戏的性能。对应用进行性能分析也是帮助您找出可能导致 ANR 的瓶颈的绝佳方式。
  • 若要识别主线程上的长时间 I/O 操作,一个很好的方法是使用严格模式
  • 分析 Android Vitals 或其他报告服务历史记录,并检查出现错误最多的游戏的发布版本。在版本控制历史记录中查看源代码,并比较各个版本之间的代码更改。如果您发现可疑内容,请逐一尝试每次更改或可能的解决方法。
  • 检查收到 ANR 最多的设备和 Android 版本的 Google Play ANR 报告历史记录。如果设备或版本已过时,如果这样做不会影响游戏的盈利能力,您很可能可以放心地忽略它们。请仔细研究数据,因为某一组用户将无法再玩您的游戏。如需了解详情,请参阅分发信息中心
  • 查看游戏源代码,确保您没有调用任何可能导致问题的代码,例如,如果使用不当,finish 可能会具有破坏性。如需详细了解 Android 开发,请参阅 Android 开发者指南
  • 查看数据并将游戏 build 导出到 Android Studio 后,您将处理 C 和 C++ 代码,因此可以充分利用 Unity 标准解决方案之外的工具,例如 Android 内存性能分析器Android CPU 性能分析器perfetto

Unity 引擎代码

如需了解 Unity 引擎端是否发生了 ANR,请在堆栈轨迹中检查是否存在 libUnity.solibMain.so。如果您发现此类问题,请按以下步骤操作:

  • 首先,搜索社区渠道(Unity 论坛Unity 讨论Stackoverflow)。
  • 如果您没有找到任何内容,请提交 bug 以解决问题。提供经过符号化解析的堆栈轨迹,以便引擎的工程师可以更好地了解和解决错误。
  • 检查最新的 Unity LTS 是否针对您的问题进行了改进。如果是,请升级您的游戏以使用该版本。(此解决方案可能仅适用于某些开发者。)
  • 如果您的代码使用自定义 Activity 而非默认 Activity,请检查 Java 代码,确保 activity 不会导致任何问题。

第三方 SDK

  • 检查所有第三方库是否为最新版本,并且没有针对最新版 Android 的崩溃或 ANR 报告。
  • 请访问 Unity 论坛,了解是否有任何错误已在较高版本中得到解决,或者 Unity 或社区成员是否提供了解决方法。
  • 查看 Google Play ANR 报告,确保 Google 尚未发现该错误。Google 已经意识到了一些 ANR 问题,正在积极解决。

系统库

系统库通常不受开发者的控制,但它们所占 ANR 比例不高。除了与库开发者联系或添加日志以缩小问题范围之外,系统库 ANR 很难解决。

退出原因

ApplicationExitInfo 是一个 Android API,用于了解 ANR 原因。如果您的游戏使用的是 Unity 6 或更高版本,您可以直接调用 ApplicationExitInfo。对于较低版本的 Unity,您需要实现自己的插件,才能从 Unity 启用 ApplicationExitInfo 调用。

Crashlytics 也使用 ApplicationExitInfo;不过,您可以通过自己的实现实现更精细的控制,并添加更相关的信息