使用 Crashlytics 高级功能了解 Unity 游戏发生崩溃的情况

1. 简介

在此 Codelab 中,您将学习如何使用 Crashlytics 的高级功能,以更好地了解发生崩溃的情况和可能造成崩溃的原委。

您将向示例游戏 MechaHamster: Level Up with Firebase Edition 添加新功能。此示例游戏是经典 Firebase 游戏 MechaHamster 的新版本,移除了其中的大部分内置 Firebase 功能,以便您在其中实现 Firebase 的新用途。

您将向游戏添加一个调试菜单。此调试菜单会调用您要创建的方法,并允许您使用 Crashlytics 的不同功能。这些方法将向您展示如何使用自定义键、自定义日志、非严重错误等为自动崩溃报告添加注释。

构建游戏后,您将使用调试菜单并检查结果,以了解它们提供的独特视图,了解游戏在实际环境中的运行方式。

学习内容

  • Crashlytics 自动捕获的错误类型。
  • 可有意记录的其他错误。
  • 如何向这些错误添加更多信息,以便用户更轻松地理解。

您需要满足的条件

  • Unity(建议的最低版本为 2019 及更高版本),并满足以下一项或两项要求:
    • iOS build 支持
    • Android build 支持
  • (仅限 Android)Firebase CLI(用于上传崩溃报告的符号)

2. 设置您的开发环境

以下部分介绍了如何下载 Level Up with Firebase 代码并在 Unity 中打开它。

请注意,这款利用 Firebase 提升游戏水平示例游戏也被其他一些 Firebase + Unity Codelab 使用,因此您可能已经完成了本部分中的任务。如果是,您可以直接跳转到本页面的最后一步:“添加适用于 Unity 的 Firebase SDK”。

下载代码

从命令行克隆此 Codelab 的 GitHub 代码库

git clone https://github.com/firebase/level-up-with-firebase.git

或者,如果您尚未安装 git,可以以 ZIP 文件的形式下载代码库

在 Unity 编辑器中打开借助 Firebase 进行升级

  1. 启动 Unity Hub,然后在 Projects(项目)标签页中,点击 Open(打开)旁边的下拉箭头
  2. 点击从磁盘添加项目
  3. 前往包含代码的目录,然后点击 OK
  4. 如果系统提示,请选择要使用的 Unity 编辑器版本和目标平台(Android 或 iOS)。
  5. 点击项目名称 level-up-with-firebase,该项目将在 Unity 编辑器中打开。
  6. 如果编辑器未自动打开该项目,请在 Unity 编辑器的 Project(项目)标签页中依次选择 Assets(资源)> Hamster(仓鼠),打开 MainGameScene
    ff4ea3f3c0d29379.png

如需详细了解如何安装和使用 Unity,请参阅在 Unity 中工作

3. 将 Firebase 添加到您的 Unity 项目

创建 Firebase 项目

  1. Firebase 控制台中,点击添加项目
  2. 如需创建新项目,请输入要使用的项目名称。
    这还会将项目 ID(显示在项目名称下方)设置为基于项目名称的某个值。您可以选择点击项目 ID 上的修改图标,进一步对其进行自定义。
  3. 如果看到相关提示,请查看并接受 Firebase 条款
  4. 点击继续
  5. 选择为此项目启用 Google Analytics 选项,然后点击继续
  6. 选择要使用的现有 Google Analytics 账号,或选择创建新账号以创建新账号。
  7. 点击 Create project
  8. 项目创建完毕后,点击继续

在 Firebase 中注册您的应用

  1. 仍在 Firebase 控制台中,点击项目概览页面中心的 Unity 图标以启动设置工作流;如果您已向 Firebase 项目添加了应用,请点击添加应用以显示平台选项。
  2. 选择同时注册 Apple (iOS) 和 Android 构建目标。
  3. 输入 Unity 项目针对具体平台的 ID。对于此 Codelab,请输入以下内容:
  4. (可选)输入 Unity 项目针对具体平台的别名。
  5. 点击注册应用,然后前往下载配置文件部分。

添加 Firebase 配置文件

点击注册应用后,系统会提示您下载两个配置文件(每个 build 目标一个配置文件)。您的 Unity 项目需要这些文件中的 Firebase 元数据才能与 Firebase 连接。

  1. 下载两个可用的配置文件:
    • 对于 Apple (iOS):下载 GoogleService-Info.plist
    • 对于 Android:下载 google-services.json
  2. 打开 Unity 项目的 Project 窗口,然后将两个配置文件移到 Assets 文件夹中。
  3. 返回 Firebase 控制台,在设置工作流中,点击下一步,然后继续执行“添加 Firebase SDK for Unity”。

添加适用于 Unity 的 Firebase SDK

  1. 在 Firebase 控制台中,点击下载 Firebase Unity SDK
  2. 将 SDK 解压缩到合适的位置。
  3. 在您打开的 Unity 项目中,依次点击 Assets(资源)> Import Package(导入资源包)> Custom Package(自定义资源包)。
  4. 导入软件包对话框中,前往包含解压缩 SDK 的目录,选择 FirebaseAnalytics.unitypackage,然后点击 Open
  5. 在随即显示的 Import Unity Package 对话框中,点击 Import
  6. 重复上述步骤以导入 FirebaseCrashlytics.unitypackage
  7. 返回 Firebase 控制台,在设置工作流中,点击下一步

如需详细了解如何将 Firebase SDK 添加到 Unity 项目,请参阅其他 Unity 安装选项

4. 在 Unity 项目中设置 Crashlytics

如需在 Unity 项目中使用 Crashlytics,您需要执行一些额外的设置步骤。当然,您需要初始化 SDK。此外,您还需要上传符号,以便在 Firebase 控制台中查看符号化堆栈轨迹,并且您需要强制制造一次测试崩溃,以确保 Firebase 能够获取崩溃事件。

初始化 Crashlytics SDK

  1. Assets/Hamster/Scripts/MainGame.cs 中,添加以下 using 语句:
    using Firebase.Crashlytics;
    using Firebase.Extensions;
    
    第一个模块可让您使用 Crashlytics SDK 中的方法,第二个模块包含一些 C# Tasks API 扩展。如果没有这两个 using 语句,以下代码将无法运行。
  2. 仍在 MainGame.cs 中,通过调用 InitializeFirebaseAndStartGame() 将 Firebase 初始化添加到现有 Start() 方法:
    void Start()
    {
      Screen.SetResolution(Screen.width / 2, Screen.height / 2, true);
      InitializeFirebaseAndStartGame();
    }
    
  3. 同样,在 MainGame.cs 中,找到 InitializeFirebaseAndStartGame(),声明一个应用变量,然后覆盖该方法的实现,如下所示:
    public Firebase.FirebaseApp app = null;
    
    // Begins the firebase initialization process and afterwards, opens the main menu.
    private void InitializeFirebaseAndStartGame()
    {
      Firebase.FirebaseApp.CheckAndFixDependenciesAsync()
      .ContinueWithOnMainThread(
        previousTask => 
        {
          var dependencyStatus = previousTask.Result;
          if (dependencyStatus == Firebase.DependencyStatus.Available) {
            // Create and hold a reference to your FirebaseApp,
            app = Firebase.FirebaseApp.DefaultInstance;
            // Set the recommended Crashlytics uncaught exception behavior.
            Crashlytics.ReportUncaughtExceptionsAsFatal = true;
            InitializeCommonDataAndStartGame();
          } else {
            UnityEngine.Debug.LogError(
              $"Could not resolve all Firebase dependencies: {dependencyStatus}\n" +
              "Firebase Unity SDK is not safe to use here");
          }
        });
    }
    

将初始化逻辑放置在此处可防止在 Firebase 依赖项初始化之前进行玩家互动。

Crashlytics 常见问题解答中介绍了将未处理的异常报告为严重异常的好处和影响。

构建项目并上传符号

对于 iOS 和 Android 应用,构建和上传符号的步骤有所不同。

iOS+(Apple 平台)

  1. 在“Build Settings”(构建设置)对话框中,将您的项目导出到 Xcode 工作区。
  2. 构建应用。
    对于 Apple 平台,Firebase Unity Editor 插件会自动配置 Xcode 项目,以便为每个 build 生成与 Crashlytics 兼容的符号文件并将其上传到 Firebase 服务器。您必须提供此符号信息,才能在 Crashlytics 信息中心内查看经过符号化解析的堆栈轨迹。

Android

  1. (仅在初始设置期间,不适用于每次 build)设置 build:
    1. 在项目目录的根目录中创建一个名为 Builds 的新文件夹(即作为 Assets 目录的同级文件夹),然后创建一个名为 Android 的子文件夹。
    2. 依次选择 File > Build Settings > Player Settings > Configuration,然后将 Scripting Backend 设置为 IL2CPP。
      • IL2CPP 通常会使 build 更小且性能更好。
      • IL2CPP 也是 iOS 上唯一可用的选项,在此处选择它可使这两个平台更加一致,并简化这两个平台之间差异的调试(如果您选择同时构建这两个平台)。
  2. 构建应用。在 File(文件)> Build Settings(构建设置)中,完成以下操作:
    1. 确保选中 Create symbols.zip(或如果系统显示下拉菜单,请选择 Debugging)。
    2. 直接通过 Unity 编辑器将 APK 构建到您刚刚创建的 Builds/Android 子文件夹中。
  3. 构建完成后,您需要生成与 Crashlytics 兼容的符号文件,并将其上传到 Firebase 服务器。如需在 Crashlytics 信息中心内查看原生库崩溃的经过符号化解析的堆栈轨迹,您必须提供此符号信息。

    运行以下 Firebase CLI 命令,生成并上传此符号文件:
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
    • FIREBASE_APP_ID:您的 Firebase Android 应用 ID(而不是您的软件包名称)。在您之前下载的 google-services.json 文件中找到此值。它是 mobilesdk_app_id 值。
      Firebase Android 应用 ID 示例:1:567383003300:android:17104a2ced0c9b9b
    • PATH/TO/SYMBOLS:构建完成时在 Builds/Android 目录中生成的压缩符号文件的路径(例如:Builds/Android/myapp-1.0-v100.symbols.zip)。

强制造成一次测试崩溃以完成设置

如需完成 Crashlytics 设置并在 Firebase 控制台的 Crashlytics 信息中心内查看初始数据,您需要强制造成一次测试崩溃。

  1. MainGameScene 中,在编辑器的 Hierarchy 中找到 EmptyObjectGameObject,向其添加以下脚本,然后保存场景。此脚本将在您运行应用几秒钟后造成一次测试崩溃。
    using System;
    using UnityEngine;
    
    public class CrashlyticsTester : MonoBehaviour {
        // Update is called once per frame
        void Update()
        {
            // Tests your Crashlytics implementation by
            // throwing an exception every 60 frames.
            // You should see reports in the Firebase console
            // a few minutes after running your app with this method.
            if(Time.frameCount >0 && (Time.frameCount%60) == 0)
            {
                throw new System.Exception("Test exception; please ignore");
            }
        }
    }
    
  2. 构建您的应用,并在构建完成后上传符号信息。
    • iOS:Firebase Unity Editor 插件会自动配置您的 Xcode 项目,以便上传您的符号文件。
    • Android:运行 Firebase CLI crashlytics:symbols:upload 命令来上传符号文件。
  3. 运行应用。应用运行后,查看设备日志并等待 CrashlyticsTester 触发异常。
    • iOS:在 Xcode 的底部窗格中查看日志。
    • Android:在终端中运行以下命令来查看日志:adb logcat
  4. 请访问 Crashlytics 信息中心查看异常!您可以在信息中心底部的问题表格中看到该信息。在本 Codelab 的后面部分,您将详细了解如何浏览这些报告。
  5. 确认事件已上传到 Crashlytics 后,选择您将其附加到的 EmptyObject GameObject,仅移除 CrashlyticsTester 组件,然后保存场景以将其恢复为原始状态。

5. 启用和了解调试菜单

到目前为止,您已将 Crashlytics 添加到 Unity 项目,完成了设置,并确认 Crashlytics SDK 正在将事件上传到 Firebase。现在,您将在 Unity 项目中创建一个菜单,以演示如何在游戏中使用更高级的 Crashlytics 功能。提升 Unity 开发技能项目中已经有一个隐藏的调试菜单,您需要将其显示出来并编写相关功能。

启用调试菜单

您的 Unity 项目中存在用于访问调试菜单的按钮,但目前未启用。您必须启用该按钮,才能从 MainMenu 预制对象访问它:

  1. 在 Unity 编辑器中,打开名为 MainMenu 的预制对象。4148538cbe9f36c5.png
  2. 在预制体层次结构中,找到名为 DebugMenuButton 的停用子对象,然后选择它。816f8f9366280f6c.png
  3. 选中包含 DebugMenuButton 的文本字段左侧左上角的复选框,即可启用 DebugMenuButton8a8089d2b4886da2.png
  4. 保存预制模块。
  5. 在编辑器中或在设备上运行游戏。现在应该可以访问该菜单了。

预览和了解调试菜单的方法体

在本 Codelab 的后面部分,您将为一些预配置的调试 Crashlytics 方法编写方法正文。不过,在提升 Unity 开发技能项目中,这些方法是在 DebugMenu.cs 中定义并从中调用的。

虽然其中一些方法会同时调用 Crashlytics 方法和抛出错误,但 Crashlytics 捕获这些错误的能力并不取决于是否先调用这些方法。而是通过这些方法添加的信息来增强自动捕获错误生成的崩溃报告。

打开 DebugMenu.cs,然后找到以下方法:

生成和注释 Crashlytics 问题的方法

  • CrashNow
  • LogNonfatalError
  • LogStringsAndCrashNow
  • SetAndOverwriteCustomKeyThenCrash
  • SetLogsAndKeysBeforeANR

用于记录 Google Analytics 事件以协助调试的方法

  • LogProgressEventWithStringLiterals
  • LogIntScoreWithBuiltInEventAndParams

在此 Codelab 的后续步骤中,您将实现这些方法,并了解它们如何帮助解决游戏开发过程中可能出现的特定情况。

6. 确保在开发期间提交崩溃报告

在开始实现这些调试方法并了解它们对崩溃报告的影响之前,请务必了解事件是如何报告给 Crashlytics 的。

对于 Unity 项目,游戏中的崩溃和异常事件会立即写入磁盘。对于不会让您的游戏崩溃的未捕获异常(例如,游戏逻辑中未捕获的 C# 异常),您可以让 Crashlytics SDK 将其报告为严重事件,具体方法是,在在 Unity 项目中初始化 Crashlytics 的地方将 Crashlytics.ReportUncaughtExceptionsAsFatal 属性设置为 true。这些事件会实时报告给 Crashlytics,而无需最终用户重启游戏。请注意,原生代码崩溃始终会报告为严重事件,并在最终用户重启游戏时一并发送。

此外,请注意不同运行时环境向 Firebase 发送 Crashlytics 信息的方式存在以下细微但重要的差异:

iOS 模拟器

  • 只有当您将 Xcode 与模拟器分离时,才会报告 Crashlytics 信息。如果 Xcode 已附加,则会捕获上游错误,从而阻止信息传送。

移动实体设备(Android 和 iOS)

  • 特定于 Android:仅在 Android 11 及更高版本中报告 ANR。ANR 和非严重事件会在下次运行时报告。

Unity 编辑器

CrashNow() 中测试一触按钮游戏崩溃

在游戏中设置 Crashlytics 后,Crashlytics SDK 会自动记录崩溃和未捕获的异常,并将其上传到 Firebase 以供分析。报告会显示在 Firebase 控制台中的 Crashlytics 信息中心中。

  1. 为了证明这确实是自动完成的,请打开 DebugMenu.cs,然后按如下方式覆盖方法 CrashNow()
    void CrashNow()
    {
        TestCrash();
    }
    
  2. 构建您的应用。
  3. (仅限 Android)运行以下 Firebase CLI 命令上传符号:
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
  4. 点按立即崩溃按钮,然后继续执行本 Codelab 的下一步,了解如何查看和解读崩溃报告。

7. 了解 Firebase 控制台中的问题报告

在查看崩溃报告时,您需要了解一些关于如何充分利用崩溃报告的知识。您编写的每种方法都将展示如何向 Crashlytics 报告添加不同类型的信息。

  1. 点按 Crash Now(立即崩溃)按钮,然后重启应用。
  2. 前往 Crashlytics 信息中心。向下滚动到信息中心底部的问题表格,Crashlytics 会将所有具有相同根本原因的事件划分为“问题”。
  3. 点击问题表格中列出的新问题。这样一来,系统就会显示有关发送到 Firebase 的每个事件的事件摘要

    您应该会看到如下所示的屏幕截图。请注意,事件摘要如何醒目地显示导致崩溃的调用的堆栈轨迹。40c96abe7f90c3aa.png

其他元数据

另一个有用的标签页是 Unity 元数据标签页。本部分介绍了发生事件的设备的属性,包括物理特征、CPU 型号/规格以及各种 GPU 指标。

下面是一个示例,说明此标签页中的信息可能很有用:
假设您的游戏大量使用着色器来实现特定外观,但并非所有手机的 GPU 都能够渲染此功能。在决定自动提供或完全停用哪些功能时,您可以通过 Unity 元数据标签页中的信息更好地了解应用应针对哪些硬件进行测试。

虽然 bug 或崩溃可能从未在您的设备上发生,但由于实际使用的 Android 设备极其多样,因此有助于您更好地了解受众群体设备的特定“热点”。

41d8d7feaa87454d.png

8. 抛出、捕获和记录异常

作为开发者,即使您的代码正确捕获并处理了运行时异常,也最好记录异常的发生情况以及发生在什么情况下。Crashlytics.LogException 正可用于此目的,即向 Firebase 发送异常事件,以便您在 Firebase 控制台中进一步调试问题。

  1. Assets/Hamster/Scripts/States/DebugMenu.cs 中,将以下代码附加到 using 语句:
    // Import Firebase
    using Firebase.Crashlytics;
    
  2. 仍在 DebugMenu.cs 中,按如下方式覆盖 LogNonfatalError()
    void LogNonfatalError()
    {
        try
        {
            throw new System.Exception($"Test exception thrown in {nameof(LogNonfatalError)}");
        }
        catch(System.Exception exception)
        {
            Crashlytics.LogException(exception);
        }
    }
    
  3. 构建您的应用。
  4. (仅限 Android)运行以下 Firebase CLI 命令上传符号:
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
  5. 点按 Log Nonfatal Error(记录非严重错误)按钮,然后重启应用。
  6. 前往 Crashlytics 信息中心,您应该会看到与此 Codelab 最后一步中看到的内容类似的内容。
  7. 不过,这次将事件类型过滤条件限制为非严重,以便仅查看非严重错误,例如您刚刚记录的错误。
    a39ea8d9944cbbd9.png

9. 将字符串记录到 Crashlytics,以便更好地了解程序执行流程

您是否曾尝试过找出为什么从多个路径调用(每次会话数百次甚至数千次)的代码行会突然生成异常或崩溃?虽然在 IDE 中逐步调试代码并仔细查看值会很有帮助,但如果只有极少数用户遇到此问题,该怎么办?更糟糕的是,如果无论您做什么都无法重现此崩溃问题,您会怎么做?

在这种情况下,提供一些背景信息会很有帮助。借助 Crashlytics.Log,您可以写出所需的上下文。不妨将这些消息视为对未来自己的提示,提醒自己可能要做些什么。

虽然日志可以用在多种场景中,但在记录调用顺序和/或调用是否存在等重要信息时,日志最有用。

  1. Assets/Hamster/Scripts/States/DebugMenu.cs 中,按如下方式覆盖 LogStringsAndCrashNow()
    void LogStringsAndCrashNow()
    {
        Crashlytics.Log($"This is the first of two descriptive strings in {nameof(LogStringsAndCrashNow)}");
        const bool RUN_OPTIONAL_PATH = false;
        if(RUN_OPTIONAL_PATH)
        {
            Crashlytics.Log(" As it stands, this log should not appear in your records because it will never be called.");
        }
        else
        {
            Crashlytics.Log(" A log that will simply inform you which path of logic was taken. Akin to print debugging.");
        }
        Crashlytics.Log($"This is the second of two descriptive strings in {nameof(LogStringsAndCrashNow)}");
        TestCrash();
    }
    
  2. 构建您的应用。
  3. (仅限 Android)运行以下 Firebase CLI 命令上传符号:
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
  4. 点按 Log Strings and Crash Now(记录字符串并立即崩溃)按钮,然后重启应用。
  5. 返回 Crashlytics 信息中心,然后点击问题表格中列出的最新问题。同样,您应该会看到与之前的问题类似的内容。
    7aabe103b8589cc7.png
  6. 不过,如果您点击事件摘要中的日志标签页,则会看到如下视图:
    4e27aa407b7571cf.png

10. 写入和覆盖自定义键

假设您想更好地了解与设置为少量值或配置的变量对应的崩溃问题。在任何给定时间,能够根据您查看的变量和可能值的组合进行过滤,会很有用。

除了记录任意字符串之外,Crashlytics 还提供了另一种调试方式:自定义键,可用于了解程序在崩溃时的确切状态。

这些是您可以为会话设置的键值对。与累积且纯粹是累加的日志不同,键可以被覆盖,以便仅反映变量或条件的最新状态。

除了作为程序上次记录状态的账本之外,这些键还可以用作 Crashlytics 问题的有力过滤条件。

  1. Assets/Hamster/Scripts/States/DebugMenu.cs 中,按如下方式覆盖 SetAndOverwriteCustomKeyThenCrash()
    void SetAndOverwriteCustomKeyThenCrash()
    {
        const string CURRENT_TIME_KEY = "Current Time";
        System.TimeSpan currentTime = System.DateTime.Now.TimeOfDay;
        Crashlytics.SetCustomKey(
            CURRENT_TIME_KEY,
            DayDivision.GetPartOfDay(currentTime).ToString() // Values must be strings
            );
    
        // Time Passes
        currentTime += DayDivision.DURATION_THAT_ENSURES_PHASE_CHANGE;
    
        Crashlytics.SetCustomKey(
            CURRENT_TIME_KEY,
            DayDivision.GetPartOfDay(currentTime).ToString()
            );
        TestCrash();
    }
    
  2. 构建您的应用。
  3. (仅限 Android)运行以下 Firebase CLI 命令上传符号:
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
  4. 点按设置自定义按键和崩溃按钮,然后重启应用。
  5. 返回 Crashlytics 信息中心,然后点击问题表格中列出的最新问题。同样,您应该会看到与之前的问题类似的内容。
  6. 不过,这次请点击事件摘要中的标签页,以查看键的值,包括 Current Time
    7dbe1eb00566af98.png

为什么要使用自定义键,而不是自定义日志?

  • 日志非常适合存储顺序数据,但如果您只需要最新值,则自定义键更适合。
  • 在 Firebase 控制台中,您可以在问题表格搜索框中按键值轻松过滤问题。

不过,与日志类似,自定义键也有限制。Crashlytics 最多可支持 64 个键值对。达到此阈值后,系统就不会再保存更多的值。每个键值对的大小上限为 1 KB。

11. (仅限 Android)使用自定义键和日志来了解和诊断 ANR

对于 Android 开发者来说,最难调试的问题类别之一就是应用无响应 (ANR) 错误。当应用在 5 秒内未响应输入时,就会发生 ANR。如果发生这种情况,则表示应用卡住或运行速度非常慢。系统会向用户显示一个对话框,用户可以选择“等待”或“关闭应用”。

ANR 会给用户带来糟糕的体验,并且(如上文中的 ANR 链接中所述)可能会影响您的应用在 Google Play 商店中的曝光度。由于 ANR 的复杂性,以及它们通常是由在不同手机型号上具有截然不同行为的多线程代码引起的,因此在调试时重现 ANR 通常非常困难,甚至几乎不可能。因此,通常最好采用分析和演绎的方法来解决这些问题。

在此方法中,我们将结合使用 Crashlytics.LogExceptionCrashlytics.LogCrashlytics.SetCustomKey 来补充自动问题日志记录,并提供更多信息。

  1. Assets/Hamster/Scripts/States/DebugMenu.cs 中,按如下方式覆盖 SetLogsAndKeysBeforeANR()
    void SetLogsAndKeysBeforeANR()
    {
        System.Action<string,long> WaitAndRecord =
        (string methodName, long targetCallLength)=>
        {
            System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
            const string CURRENT_FUNCTION = "Current Async Function";
    
            // Initialize key and start timing
            Crashlytics.SetCustomKey(CURRENT_FUNCTION, methodName);
            stopWatch.Start();
    
            // The actual (simulated) work being timed.
            BusyWaitSimulator.WaitOnSimulatedBlockingWork(targetCallLength);
    
            // Stop timing
            stopWatch.Stop();
    
            if(stopWatch.ElapsedMilliseconds>=BusyWaitSimulator.EXTREME_DURATION_MILLIS)
            {
              Crashlytics.Log($"'{methodName}' is long enough to cause an ANR.");
            }
            else if(stopWatch.ElapsedMilliseconds>=BusyWaitSimulator.SEVERE_DURATION_MILLIS)
            {
              Crashlytics.Log($"'{methodName}' is long enough it may cause an ANR");
            }
        };
    
        WaitAndRecord("DoSafeWork",1000L);
        WaitAndRecord("DoSevereWork",BusyWaitSimulator.SEVERE_DURATION_MILLIS);
        WaitAndRecord("DoExtremeWork",2*BusyWaitSimulator.EXTREME_DURATION_MILLIS);
    }
    
  2. 构建您的应用。
  3. 运行以下 Firebase CLI 命令上传符号:
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
  4. 点按标签为 Set Logs And Keys → ANR 的按钮,然后重启应用。
  5. 返回 Crashlytics 信息中心,然后点击问题表格中的新问题,查看事件摘要。如果调用正常完成,您应该会看到如下所示的内容:
    876c3cff7037bd07.png

    如您所见,Firebase 指出线程上的繁忙等待是应用触发 ANR 的主要原因。
  6. 如果您查看事件摘要日志标签页中的日志,会发现记录为已完成的最后一个方法是 DoSevereWork
    5a4bec1cf06f6984.png

    相比之下,列为正在启动的最后一个方法是 DoExtremeWork,这表示 ANR 发生在此方法期间,并且游戏在能够记录 DoExtremeWork 之前就关闭了。

    89d86d5f598ecf3a.png

为什么要这样做?

  • 重现 ANR 非常困难,因此,能够获取有关代码区域和指标的丰富信息对于通过演绎法找出问题非常重要。
  • 通过存储在自定义键中的信息,您现在可以了解哪个异步线程的运行时间最长,以及哪些线程有触发 ANR 的风险。这类相关的逻辑和数字数据会显示代码中哪些部分最需要优化。

12. 穿插 Google Analytics 事件以进一步丰富报告

您还可以通过“调试”菜单调用以下方法,但这些方法不会自行生成问题,而是将 Google Analytics 用作另一种信息来源,以便更好地了解游戏的运作方式。

与您在此 Codelab 中编写的其他方法不同,您应将这些方法与其他方法结合使用。在运行其他方法之前,请按您想要的任意顺序调用这些方法(通过在“调试”菜单中按相应的按钮)。然后,当您检查特定 Crashlytics 问题中的信息时,您会看到 Google Analytics 事件的有序日志。这些数据可在游戏中使用,以便更好地了解程序流或用户输入的组合,具体取决于您对应用的插桩方式。

  1. Assets/Hamster/Scripts/States/DebugMenu.cs 中,覆盖以下方法的现有实现:
    public void LogProgressEventWithStringLiterals()
    {
          Firebase.Analytics.FirebaseAnalytics.LogEvent("progress", "percent", 0.4f);
    }
    
    public void LogIntScoreWithBuiltInEventAndParams()
    {
          Firebase.Analytics.FirebaseAnalytics
            .LogEvent(
              Firebase.Analytics.FirebaseAnalytics.EventPostScore,
              Firebase.Analytics.FirebaseAnalytics.ParameterScore,
              42
            );
    }
    
  2. 构建并部署游戏,然后进入调试菜单
  3. (仅限 Android)运行以下 Firebase CLI 命令上传符号:
    firebase crashlytics:symbols:upload --app=<FIREBASE_APP_ID> <PATH/TO/SYMBOLS>
    
  4. 按以下至少一个按钮一次或多次即可调用上述函数:
    • 记录字符串事件
    • 记录 Int 事件
  5. 立即崩溃按钮。
  6. 重启游戏,让其将崩溃事件上传到 Firebase。
  7. 当您记录各种任意序列的 Google Analytics 事件,然后让游戏生成 Crashlytics 用于创建报告的事件(就像您刚才所做的那样)时,这些事件会添加到 Crashlytics 事件摘要日志标签页中,如下所示:
    d3b16d78f76bfb04.png

13. 后续行动

这样一来,您应该就有了更好的理论依据来补充自动生成的崩溃报告。有了这些新信息,您就可以使用当前状态、过去事件的记录和现有的 Google Analytics 事件,更好地拆解导致相应结果的事件序列和逻辑。

如果您的应用以 Android 11(API 级别 30)或更高版本为目标平台,不妨考虑添加 GWP-ASan。GWP-ASan 是一种原生内存分配器功能,可用于调试由原生内存错误(例如 use-after-freeheap-buffer-overflow bug)引起的崩溃。如需充分利用此调试功能,请明确启用 GWP-ASan

后续步骤

继续学习 为您的 Unity 游戏集成 Remote Config Codelab,了解如何在 Unity 中使用 Remote Config 和 A/B 测试。