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

1. 简介

在此 Codelab 中,您将学习如何使用 Crashlytics 的高级功能,这些功能将帮助您更好地了解崩溃以及可能导致这些崩溃的环境因素。

您将向示例游戏 MechaHamster: Level Up with Firebase Edition 添加新功能。此示例游戏是经典 Firebase 游戏《MechaHamster》的新版本,移除了其大部分内置 Firebase 功能,让您有机会实现 Firebase 的新用途来取代它们。

您将在游戏中添加调试菜单。此调试菜单会调用您将创建的方法,并使您可以练习 Crashlytics 的不同功能。这些方法将介绍如何使用自定义键、自定义日志、非严重错误等为自动崩溃报告添加注释。

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

学习内容

  • Crashlytics 自动捕获的错误类型。
  • 可刻意记录的其他错误。
  • 如何向这些错误添加更多信息,使其更易于理解。

所需条件

  • Unity(推荐的最低版本 2019 及以上版本),并且具备以下一项或两项条件:
    • iOS 构建支持
    • Android build 支持
  • (仅限 Android)Firebase CLI(用于为崩溃报告上传符号)

2. 设置您的开发环境

下面几部分将介绍如何下载 Level Up with Firebase 代码并在 Unity 中打开该代码。

请注意,其他多个 Firebase 和 Unity Codelab 都在使用以下“使用 Firebase 进行升级”示例游戏,因此您可能已经完成了本部分中的任务。如果是这样,您可以直接执行本页面上的最后一步:“添加适用于 Unity 的 Firebase SDK”。

下载代码

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

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

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

在 Unity 编辑器中打开 Level Up with 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

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

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

创建 Firebase 项目

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

在 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 Project 中,依次点击 Assets(资源)> Import Package(导入软件包)> Custom Package(自定义软件包)。
  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. 在项目的根目录下创建一个名为 Builds 的新文件夹(即,作为 Assets 目录的同级文件夹),然后创建一个名为 Android 的子文件夹。
    2. File > Build Settings > Player Settings > Configuration 中,将 Scripting Backend 设为 IL2CPP。
      • IL2CPP 通常会导致 build 更小且性能更好。
      • IL2CPP 也是 iOS 上唯一可用的选项,在此处选择它可使两个平台实现更好的对等,并简化两者之间的调试差异(如果您选择同时构建两者)。
  2. 构建您的应用。在 File > Build Settings 中,完成以下操作:
    1. 确保 Createsys.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 进行升级 (Level Up with Firebase) Unity 项目已有一个隐藏的调试菜单,您可以通过该菜单显示出来并为其编写功能。

启用调试菜单

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

  1. 在 Unity 编辑器中,打开名为 MainMenu 的预设件。4148538cbe9f36c5
  2. 在 Prefab 层次结构中,找到名为 DebugMenuButton 且已停用的子对象,然后选择它。816f8f9366280f6c.png
  3. 勾选包含 DebugMenuButton 的文本字段左上角的复选框,以启用 DebugMenuButton8a8089d2b4886da2
  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.ReportUncaughtExceptionsAsFatal 属性设置为 true(在 Unity 项目中初始化 Crashlytics),让 Crashlytics SDK 将这些异常报告为严重事件。这些事件会实时报告给 Crashlytics,而无需最终用户重启游戏。请注意,原生代码崩溃问题始终会报告为严重事件,并在最终用户重启游戏时一并发送。

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

iOS 模拟器

  • 当且仅当您将 Xcode 与模拟器分离时,系统才会报告 Crashlytics 信息。如果附加了 Xcode,它会向上游捕获错误,并阻止信息传递。

移动实体设备(Android 和 iOS)

  • Android 专用:ANR 仅在 Android 11 及更高版本上报告。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 信息中心。向下滚动到信息中心底部的 Issues(问题)表格,Crashlytics 会在此表格中将根本原因相同的事件划分为“问题”。
  3. 点击问题表格中列出的新问题。执行此操作后,系统会显示已发送到 Firebase 的每个事件的事件摘要

    您应该会看到类似以下屏幕截图的内容。请注意事件摘要如何以醒目的方式显示导致崩溃的调用的堆栈轨迹。40c96abe7f90c3aa

其他元数据

另一个有用的标签页是 Unity Metadata(Unity 元数据)标签页。此部分会告知您发生了事件的设备的属性,包括物理功能、CPU 型号/规格以及各种 GPU 指标。

在下面的示例中,此标签页中的信息可能对您有所帮助:
假设您的游戏大量使用了着色器来实现某种外观,但并非所有手机都具备能够呈现此功能的 GPU。借助 Unity Metadata(Unity 元数据)标签页中的信息,您可以更好地了解您的应用在决定自动提供或完全停用哪些功能时应测试哪些硬件。

虽然错误或崩溃可能永远不会在您的设备上发生,但由于 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. 点按记录非严重错误按钮,然后重启应用。
  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. 点按设置自定义键和崩溃按钮,然后重启您的应用。
  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. 点按设置日志和按键 → ANR 的按钮,然后重启应用。
  5. 返回 Crashlytics 信息中心,然后点击问题表格中的新问题,以查看事件摘要。如果调用顺利进行,您应该会看到如下内容:
    876c3cff7037bd07

    如您所见,Firebase 将线程上的忙碌等待确定为应用触发 ANR 的主要原因。
  6. 如果您在事件摘要日志标签页中查看日志,则会发现最后一项记录为“完成”的方法为 DoSevereWork
    5a4bec1cf06f6984

    相比之下,列出的最后一个启动方法为 DoExtremeWork,这表示在此方法执行期间发生了 ANR,并且游戏在可以记录 DoExtremeWork 前就已关闭。

    89d86d5f598ecf3a

为什么要这样做?

  • 重现 ANR 非常困难,因此,获得关于代码区域和指标的丰富信息对于以推理方式找出问题至关重要。
  • 通过自定义键中存储的信息,您现在可以知道哪个异步线程的运行时间最长,以及哪些异步线程有触发 ANR 的危险。通过这种相关的逻辑和数字数据,您可以了解自己在代码中的优化最必要之处。

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

以下方法也可从“调试”菜单调用,但它们本身不会产生问题,而是使用 Google Analytics(分析)作为另一种信息来源,以更好地了解您游戏的运行情况。

与您在此 Codelab 中编写的其他方法不同,您应该将这些方法与其他方法结合使用。在运行其他某个方法之前,可按您希望的任意顺序调用这些方法(通过按调试菜单中的相应按钮)。然后,当您检查特定 Crashlytics 问题中的信息时,便会看到有序的 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. 至少按下以下某个按钮一次或多次,以调用上述函数:
    • 日志字符串事件
    • 登录事件
  5. Crash Now 按钮。
  6. 重启游戏,使其将崩溃事件上传到 Firebase。
  7. 如果您记录了任意序列的 Google Analytics(分析)事件,然后让您的游戏生成一个事件,让 Crashlytics 根据该事件来生成报告(就像您刚才那样),这些事件会添加到 Crashlytics 事件摘要日志标签页中,如下所示:
    d3b16d78f76bfb04

13. 今后

因此,您应该有了更好的理论依据,作为对自动生成的崩溃报告的补充。借助这些新信息,您可以利用当前状态、过往事件记录和现有 Google Analytics(分析)事件,更好地细分导致相应结果的事件序列和逻辑。

如果您的应用以 Android 11(API 级别 30)或更高版本为目标平台,请考虑集成 GWP-ASan,这是一项原生内存分配器功能,有助于调试由原生内存错误(例如 use-after-freeheap-buffer-overflow bug)导致的崩溃。如需利用此调试功能,请明确启用 GWP-ASan

后续步骤

继续完成使用 Remote Config 对您的 Unity 游戏进行插桩处理 Codelab,您将了解如何在 Unity 中使用 Remote Config 和 A/B Testing。