使用 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 + Unity Codelab 也会使用此通过 Firebase 升级示例游戏,因此您可能已完成本部分中的任务。如果是这样,您可以直接进入本页面的最后一步:“添加 Firebase SDK for Unity”。

下载代码

通过命令行克隆此 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 配置文件

点击 Register app(注册应用)后,系统会提示您下载两个配置文件(每个构建目标对应一个配置文件)。您的 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 >导入软件包 >自定义软件包
  4. Import package 对话框中,转到包含解压缩的 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. 在项目的根目录(即与 Assets 目录的同级)中创建一个名为 Builds 的新文件夹,然后创建一个名为 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 功能。提升技能,掌握 Firebase Unity 项目中已经有一个隐藏的调试菜单,您需要将其显示出来并编写相关功能。

启用调试菜单

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

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

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

在本 Codelab 的后面部分,您将为一些预配置的调试 Crashlytics 方法编写方法正文。但在 Level Up with Firebase Unity 项目中,这些方法是在 DebugMenu.cs 中定义和调用的。

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

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

生成 Crashlytics 问题并为其添加注释的方法

  • CrashNow
  • LogNonfatalError
  • LogStringsAndCrashNow
  • SetAndOverwriteCustomKeyThenCrash
  • SetLogsAndKeysBeforeANR

记录 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. 点按 Crash Now 按钮,然后继续执行此 Codelab 的下一步,了解如何查看和解读崩溃报告。

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

在查看崩溃报告时,您需要知道如何充分利用此类报告。您编写的每种方法都将演示如何向 Crashlytics 报告添加不同类型的信息。

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

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

其他元数据

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

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

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

41d8d7feaa87454d

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
  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. 点按 Set Custom Key and Crash 按钮,然后重启您的应用。
  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

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

    相比之下,列为起始方法的最后一个方法是 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. 至少按下列其中一个按钮一次或多次以调用上述函数:
    • 日志字符串事件
    • Log Int 事件
  5. 按下 Crash Now 按钮。
  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

后续步骤

继续学习使用 Remote Config 对 Unity 游戏进行插桩测试 Codelab 中,您将了解如何在 Unity 中使用 Remote Config 和 A/B Testing。