使用 Firebase Performance Monitoring 衡量加载时间和屏幕渲染情况

1. 简介

上次更新日期:2021 年 3 月 11 日

为什么需要衡量观看的效果?

视图是 Android 应用中直接影响用户体验的关键部分。例如,您的 activity 或 fragment 包含界面,其中包含用户与之互动的 View 组件。除非界面完全在屏幕上绘制,否则用户无法看到界面的全部内容。屏幕运行缓慢和卡顿会直接影响用户与应用的互动,并造成糟糕的用户体验。

Firebase Performance Monitoring 不是提供开箱即用的性能指标吗?

Firebase Performance Monitoring 可自动捕获一些性能数据,例如您的应用启动时间(即仅限第一个 activity 的加载时间)和屏幕渲染性能(即 activity 的缓慢帧和冻结帧,但 fragment 不适用的)。不过,行业应用通常没有很多 activity,而是包含一个 activity 和多个 fragment。此外,许多应用通常会针对更复杂的用例实现自己的自定义视图。因此,了解如何通过在应用中检测自定义代码跟踪记录来衡量 activity 和 fragment 的加载时间和屏幕渲染性能,通常很有帮助。您可以轻松扩展此 Codelab,以衡量自定义视图组件的性能。

学习内容

  • 如何向 Android 应用添加 Firebase Performance Monitoring
  • 了解 activity 或 fragment 的加载
  • 如何对自定义代码跟踪记录进行插桩处理以衡量 activity 或 fragment 的加载时间
  • 了解屏幕渲染以及什么是慢帧/冻结的帧
  • 如何使用指标对自定义代码跟踪记录进行插桩,以记录慢速屏幕/冻结屏幕
  • 如何在 Firebase 控制台中查看收集的指标

所需条件

  • Android Studio 4.0 或更高版本
  • Android 设备/模拟器
  • Java 8 或更高版本

2. 准备工作

获取代码

运行以下命令以克隆本 Codelab 的示例代码。这将在您的机器上创建一个名为 codelab-measure-android-view-performance 的文件夹:

$ git clone https://github.com/FirebaseExtended/codelab-measure-android-view-performance.git
$ cd codelab-measure-android-view-performance

如果您的机器上没有 git,也可以直接从 GitHub 下载代码。

measure-view-performance-start 项目导入 Android Studio。您可能会看到一些编译错误,或者看到有关缺失 google-services.json 文件的警告。我们将在此步骤的下一部分中更正此问题。

在此 Codelab 中,我们将使用 Firebase Assistant 插件在 Firebase 项目中注册我们的 Android 应用,并将必要的 Firebase 配置文件、插件和依赖项添加到我们的 Android 项目中 - 所有这些都在 Android Studio 中完成

将您的应用关联至 Firebase

  1. 依次前往 Android Studio/Help > Check for updates,确保您使用的是最新版 Android Studio 和 Firebase Assistant。
  2. 依次选择 Tools > Firebase 以打开 Assistant 窗格。

e791bed0999db1e0.png

  1. 选择要添加到应用的 Performance Monitoring,然后点击 Performance Monitoring 使用入门
  2. 点击连接 Firebase,将您的 Android 项目与 Firebase 连接 (这会在浏览器中打开 Firebase 控制台)
  3. 在 Firebase 控制台中,点击添加项目,然后输入 Firebase 项目名称(如果您已经有 Firebase 项目,可以改为选择现有的项目)。点击继续并接受条款,以创建 Firebase 项目和新的 Firebase 应用。
  4. 接下来,您应该会看到一个对话框,用于将新的 Firebase 应用关联到您的 Android Studio 项目。

42c498d28ead2b77

  1. 返回 Android Studio,在 Assistant 窗格中,您应该会看到您的应用已连接到 Firebase 的确认消息。

dda8bdd9488167a0.png

将性能监控添加到您的应用

在 Android Studio 的 Assistant 窗格中,点击 Add Performance Monitoring to your app

您应该会看到一个 Accept Changes 对话框,之后 Android Studio 应同步您的应用,以确保已添加所有必要的依赖项。

9b58145acc4be030.png

最后,您应该会在 Android Studio 的 Assistant 窗格中看到一条成功消息,表示所有依赖项均已正确设置。

aa0d46fc944e0c0b

此外,还需要按照“(可选)启用调试日志记录”步骤中的说明启用调试日志记录公开文档中也提供了相同的说明。

3. 运行应用

如果您已成功将应用与 Performance Monitoring SDK 集成,项目现在应该可以编译了。在 Android Studio 中,依次点击 Run > Run 'app',以在已连接的 Android 设备/模拟器上构建和运行应用。

该应用有两个按钮,可将您定向到相应的 activity 和 fragment,如下所示:

410d8686b4f45c33

在此 Codelab 的后续步骤中,您将学习如何衡量 activity 或 fragment 的加载时间和屏幕渲染性能。

4. 了解 activity 或 fragment 的加载情况

在此步骤中,我们将了解系统在加载 activity 或 fragment 期间会执行的操作。

了解 Activity 的加载

对于 activity,加载时间是指从创建 activity 对象一直到第一帧完全在屏幕上绘制完成的时间(即用户首次看到 activity 的完整界面的时间)。如需衡量应用是否已完全绘制,您可以使用 reportFullyDrawn() 方法测量从应用启动到完全显示所有资源和视图层次结构所用的时间。

概括来讲,当应用调用 startActivity(Intent) 时,系统会自动执行以下过程。每个进程都需要时间才能完成,这就增加了从创建 activity 到用户在屏幕上看到 activity 界面之间的时长。

c20d14b151549937.png

了解 Fragment 的加载

与 activity 类似,fragment 的加载时间定义为从 fragment 附加到其宿主 activity 开始,到 fragment 视图的第一帧完全绘制在屏幕上为止所用的时间。

5. 衡量 activity 的加载时间

第一帧的延迟可能会导致糟糕的用户体验,因此请务必了解用户的初始加载延迟是多少。您可以对自定义代码跟踪记录进行插桩,从而衡量此加载时间:

  1. 创建 Activity 对象后,立即在 Activity 类中启动自定义代码跟踪记录(名为 TestActivity-LoadTime)。

TestActivity.java

public class TestActivity extends AppCompatActivity {   
    // TODO (1): Start trace recording as soon as the Activity object is created.
    private final Trace viewLoadTrace = FirebasePerformance.startTrace("TestActivity-LoadTime");

    // ...

}
  1. 替换 onCreate() 回调,并获取 setContentView() 方法添加的 View。
@Override     
public void onCreate(Bundle savedInstanceState) {    
    super.onCreate(savedInstanceState);          

    // Current Activity's main View (as defined in the layout xml file) is inflated after this            
    setContentView(R.layout.activity_test);          

    // ...

    // TODO (2): Get the View added by Activity's setContentView() method.         
    View mainView = findViewById(android.R.id.content);     

    // ...
}
  1. 我们添加了 FistDrawListener 的实现,它有两个回调:onDrawingStart()onDrawingFinish()(请参阅下一部分,详细了解 FirstDrawListener 以及影响其性能的因素)。在 activity 的 onCreate() 回调末尾注册 FirstDrawListener。您应在 onDrawingFinish() 回调中停止 viewLoadTrace

TestActivity.java

    // TODO (3): Register the callback to listen for first frame rendering (see
    //  "OnFirstDrawCallback" in FirstDrawListener) and stop the trace when View drawing is
    //  finished.
    FirstDrawListener.registerFirstDrawListener(mainView, new FirstDrawListener.OnFirstDrawCallback() {              
        @Override             
        public void onDrawingStart() {       
          // In practice you can also record this event separately
        }

        @Override             
        public void onDrawingFinish() {
            // This is when the Activity UI is completely drawn on the screen
            viewLoadTrace.stop();             
        }         
    });
  1. 重新运行应用。然后,使用“Logging trace metric”过滤 logcat。点按 LOAD ACTIVITY 按钮,然后查找如下日志:
I/FirebasePerformance: Logging trace metric: TestActivity-LoadTime (duration: XXXms)

🎉 恭喜!您已成功衡量 activity 的加载时间,并将这些数据报告给 Firebase Performance Monitoring。在此 Codelab 的后面部分,我们将查看 Firebase 控制台中记录的指标。

FirstDrawListener 的用途

在上面的部分中,我们注册了 FirstDrawListenerFirstDrawListener 的用途是测量第一帧何时开始和完成绘制。

它会实现 ViewTreeObserver.OnDrawListener,并替换在视图树即将绘制时调用的 onDraw() 回调。然后,它会封装结果,以提供两个实用程序回调 onDrawingStart()onDrawingFinish()

您可以在此 Codelab 的源代码中找到 FirstDrawListener 的完整代码。

6. 测量 fragment 的加载时间

Fragment 加载时间的测量方式与 Activity 的测量方式类似,但存在一些细微差别。同样,我们将对自定义代码轨迹进行插桩:

  1. 替换 onAttach() 回调并开始录制您的 fragmentLoadTrace。我们将此轨迹命名为 Test-Fragment-LoadTime

如前所述,Fragment 对象可以随时创建,但只有在附加到其宿主 Activity 后,它才会变为活动状态。

TestFragment.java

public class TestFragment extends Fragment {

   // TODO (1): Declare the Trace variable.
   private Trace fragmentLoadTrace;

   @Override
   public void onAttach(@NonNull Context context) {
       super.onAttach(context);

       // TODO (2): Start trace recording as soon as the Fragment is attached to its host Activity.
       fragmentLoadTrace = FirebasePerformance.startTrace("TestFragment-LoadTime");
   }
  1. onViewCreated() 回调中注册 FirstDrawListener。然后,与 activity 示例类似,在 onDrawingFinish() 中停止轨迹。

TestFragment.java

@Override
public void onViewCreated(@NonNull View mainView, Bundle savedInstanceState) {
   super.onViewCreated(mainView, savedInstanceState);

   // ...

   // TODO (3): Register the callback to listen for first frame rendering (see
   //  "OnFirstDrawCallback" in FirstDrawListener) and stop the trace when view drawing is
   //  finished.
   FirstDrawListener.registerFirstDrawListener(mainView, new FirstDrawListener.OnFirstDrawCallback() {

       @Override
       public void onDrawingStart() {
           // In practice you can also record this event separately
       }

       @Override
       public void onDrawingFinish() {
           // This is when the Fragment UI is completely drawn on the screen
           fragmentLoadTrace.stop();
       }
   });
  1. 重新运行应用。然后,使用“Logging trace metric”过滤 logcat。点按 LOAD FRAGMENT 按钮,然后查找如下日志:
I/FirebasePerformance: Logging trace metric: TestFragment-LoadTime (duration: XXXms)

🎉 恭喜!您已成功衡量 fragment 的加载时间,并将这些数据报告给 Firebase Performance Monitoring。我们将在本 Codelab 的后面部分在 Firebase 控制台中查看记录的指标。

7. 了解屏幕渲染以及什么是呈现速度缓慢/冻结的帧

界面呈现是指从应用生成帧并将其显示在屏幕上的动作。为了确保用户能够流畅地与应用互动,应用渲染帧的时间应少于 16 毫秒,以达到每秒 60 帧(为什么是 60fps?)。如果您的应用存在界面渲染缓慢的问题,系统会不得不跳过一些帧,这会导致用户感觉您的应用卡顿。我们将这种情况称为卡顿

同样,冻结的帧是指呈现时间超过 700 毫秒的界面帧。这种延迟是个问题,因为在呈现帧时,您的应用在近一秒内似乎卡住了,对用户输入无响应。

8. 衡量 fragment 的慢帧/冻结帧

Firebase Performance Monitoring 会自动捕获 activity 的慢帧/冻结帧(但前提是该 activity 已启用硬件加速)。不过,此功能目前不适用于 fragment。fragment 的慢帧/冻结帧定义为 fragment 生命周期中的 onFragmentAttached()onFragmentDetached() 回调之间的整个 activity 的慢帧/冻结帧。

我们从 AppStateMonitor 类(它是 Performance Monitoring SDK 中负责记录 Activity 屏幕跟踪记录的一部分)的出发点,我们实现了 ScreenTrace 类(它是此 Codelab 源代码库的一部分)。ScreenTrace 类可连接到 activity 的 FragmentManager 生命周期回调,以捕获慢速/冻结帧。此类提供了两个公共 API:

  • recordScreenTrace():开始录制屏幕跟踪记录
  • sendScreenTrace():停止录制屏幕轨迹,并附加自定义指标以记录总帧数、慢帧数和冻结帧数

通过附加这些自定义指标,fragment 的屏幕跟踪记录的处理方式与 activity 的屏幕跟踪记录相同,并且可与其他屏幕呈现跟踪记录一起显示在 Firebase 控制台的“Performance”信息中心内。

以下是为 Fragment 记录屏幕跟踪记录的方法:

  1. 在托管 fragment 的 activity 中初始化 ScreenTrace 类。

MainActivity.java

// Declare the Fragment tag
private static final String FRAGMENT_TAG = TestFragment.class.getSimpleName();

// TODO (1): Declare the ScreenTrace variable.
private ScreenTrace screenTrace;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // TODO (2): Initialize the ScreenTrace variable.
    screenTrace = new ScreenTrace(this, FRAGMENT_TAG);

    // ...
}
  1. 加载 fragment 时,请注册 FragmentLifecycleCallbacks 并替换 onFragmentAttached()onFragmentDetached() 回调。我们已为您完成此操作。您需要在 onFragmentAttached() 回调中开始录制屏幕轨迹,并在 onFragmentDetached() 回调中停止录制。

MainActivity.java

private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
       new FragmentManager.FragmentLifecycleCallbacks() {

           @Override
           public void onFragmentAttached(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull Context context) {
               super.onFragmentAttached(fm, f, context);

               // TODO (3): Start recording the screen traces as soon as the Fragment is
               //  attached to its host Activity.
               if (FRAGMENT_TAG.equals(f.getTag()) && screenTrace.isScreenTraceSupported()) {
                   screenTrace.recordScreenTrace();
               }
           }

           @Override
           public void onFragmentDetached(@NonNull FragmentManager fm, @NonNull Fragment f) {
               super.onFragmentDetached(fm, f);

               // TODO (4): Stop recording the screen traces as soon as the Fragment is
               //  detached from its host Activity.
               if (FRAGMENT_TAG.equals(f.getTag()) && screenTrace.isScreenTraceSupported()) {
                   screenTrace.sendScreenTrace();
               }

               // Unregister Fragment lifecycle callbacks after the Fragment is detached
               fm.unregisterFragmentLifecycleCallbacks(fragmentLifecycleCallbacks);
           }
       };
  1. 重新运行应用。然后点按“LOAD FRAGMENT”按钮。等待几秒钟,然后点击底部导航栏上的 back button

使用“Logging trace metric”(日志跟踪指标)过滤 Logcat,然后查找如下日志:

I/FirebasePerformance: Logging trace metric: _st_MainActivity-TestFragment (duration: XXXms)

使用“FireperfViews”过滤 Logcat,然后查找如下日志:

D/FireperfViews: sendScreenTrace MainActivity-TestFragment, name: _st_MainActivity-TestFragment, total_frames: XX, slow_frames: XX, frozen_frames: XX

🎉 恭喜!您已成功衡量 fragment 的慢帧/冻结帧数,并将这些数据报告给 Firebase Performance Monitoring。我们稍后会在此 Codelab 中在 Firebase 控制台中查看记录的指标。

9. 在 Firebase 控制台中查看指标

  1. 在 Logcat 中,点击 Firebase 控制台网址以访问轨迹的详情页面。ceb9d5ba51bb6e89.jpeg

或者,在 Firebase 控制台中选择包含您的应用的项目。在左侧面板中,找到发行和监控部分,然后点击性能

  • 在主信息中心标签页中,向下滚动到“轨迹”表,然后点击自定义轨迹标签页。在此表格中,您会看到我们之前添加的自定义代码轨迹,以及一些开箱即用轨迹,例如 _app_start 轨迹。
  • 找到您的两个自定义代码轨迹 TestActivity-LoadTimeTestFragment-LoadTime。点击任一项的时长,即可查看收集的数据的更多详细信息。

a0d8455c5269a590

  1. 自定义代码跟踪记录的详情页面会显示跟踪记录时长(即测得的加载时间)的相关信息。

5e92a307b7410d8b.png

  1. 您还可以查看自定义屏幕轨迹的性能数据。
  • 返回主信息中心标签页,向下滚动到轨迹表,然后点击屏幕渲染标签页。在此表中,您将看到我们之前添加的自定义屏幕跟踪记录以及任何开箱即用的屏幕跟踪记录,例如 MainActivity 跟踪记录。
  • 找到您的自定义屏幕跟踪记录 MainActivity-TestFragment。点击轨迹名称可查看呈现速度缓慢的帧和冻结的帧的汇总数据。

ee7890c7e2c28740

10. 恭喜

恭喜!您已成功使用 Firebase Performance Monitoring 测量了 activity 和 fragment 的加载时间和屏幕渲染性能!

您的成果

后续步骤

除了自定义跟踪记录之外,Firebase 性能还提供了更多应用性能衡量方法。它会自动衡量应用启动时间、应用前台活动和应用后台性能数据。是时候在 Firebase 控制台中查看这些指标了。

此外,Firebase Performance 还提供自动 HTTP/HTTPS 网络请求监控功能。这样,您无需编写任何代码,即可轻松插桩网络请求。您可以尝试从您的应用发送一些网络请求,并在 Firebase 控制台中查找指标吗?

奖励

现在,您已经知道如何使用自定义代码轨迹衡量 activity/fragment 的加载时间和屏幕渲染性能,能否探索我们的开源代码库,看看能否直接为应用中的任何 activity/fragment 捕获这些指标?如果您需要,请随时发送 PR :-)

11. 额外学习

了解 activity 加载期间发生的情况有助于您更好地了解应用的性能特性。在前面的步骤中,我们大致介绍了 activity 加载期间发生的情况,但下图更详细地介绍了每个阶段。

cd61c1495fad7961