使用 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.png

  1. 返回 Android Studio,在 Assistant 窗格中,您应该会看到一条确认您的应用已与 Firebase 相关联的消息。

dda8bdd9488167a0.png

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

在 Android Studio 的 Assistant 窗格中,点击 Add Performance Monitoring to your app(将 Performance Monitoring 添加到您的应用)。

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

9b58145acc4be030.png

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

aa0d46fc944e0c0b.png

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

3. 运行应用

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

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

410d8686b4f45c33.png

在此 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 控制台的性能信息中心中的其他屏幕渲染轨迹一起显示。

如需为 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.png

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

5e92a307b7410d8b.png

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

ee7890c7e2c28740.png

10. 恭喜

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

您已完成的工作

后续步骤

除了自定义轨迹之外,Firebase Performance 还提供了更多方法来衡量应用的性能。它会自动衡量应用启动时间、应用前台和应用后台性能数据。现在,您可以前往 Firebase 控制台查看这些指标了。

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

奖励

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

11. 额外学习

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

cd61c1495fad7961.png