Measure load time and screen rendering with Firebase Performance Monitoring

1. Introduction

Last Updated: 2021-03-11

Why do we need to measure the performance of Views?

Views are a key part of Android applications that directly affect the user experience. For example, your Activity or Fragment contains the UI which holds the View components that users interact with. Users are not able to see the entire content of the UI until it's completely drawn on the screen. Slow and frozen screens will directly impair user interaction with your app and create a bad user experience.

Doesn't Firebase Performance Monitoring provide these performance metrics out-of-the-box?

Firebase Performance Monitoring automatically captures some performance data out-of-the-box, such as your app start time (i.e., the load time for your first Activity only) and screen rendering performance (i.e., slow and frozen frames for Activities but not for Fragments). However, industry apps usually don't have lots of Activities but rather one Activity and multiple Fragments. Also, many apps usually implement their own Custom Views for more complex use cases. So it's often useful to understand how to measure the load time and screen rendering performance of both Activities and Fragments by instrumenting custom code traces in your app. You can easily extend this codelab to measure performance of Custom View components.

What you'll learn

  • How to add Firebase Performance Monitoring to an Android app
  • Understanding the loading of an Activity or a Fragment
  • How to instrument custom code traces to measure the load time of an Activity or Fragment
  • Understanding Screen Rendering and what is a Slow/Frozen frame
  • How to instrument custom code traces with metrics to record Slow/Frozen screens
  • How to view the collected metrics in the Firebase console

What you'll need

  • Android Studio 4.0 or higher
  • An Android device/emulator
  • Java version 8 or higher

2. Getting set up

Get the code

Run the following commands to clone the sample code for this codelab. This will create a folder called codelab-measure-android-view-performance on your machine:

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

If you don't have git on your machine, you can also download the code directly from GitHub.

Import the measure-view-performance-start project into Android Studio. You will probably see some compilation errors or maybe a warning about a missing google-services.json file. We'll correct this in the next section of this step.

In this codelab, we'll use the Firebase Assistant plugin to register our Android app with a Firebase project and add the necessary Firebase config files, plugins, and dependencies to our Android project — all from within Android Studio!

Connect your app to Firebase

  1. Go to Android Studio/Help > Check for updates to make sure that you're using the latest versions of Android Studio and the Firebase Assistant.
  2. Select Tools > Firebase to open the Assistant pane.

e791bed0999db1e0.png

  1. Choose Performance Monitoring to add to your app, then click Get started with Performance Monitoring.
  2. Click Connect to Firebase to connect your Android project with Firebase (this will open up the Firebase console in your browser).
  3. In the Firebase console, click Add project, then enter a Firebase project name (if you already have a Firebase project, you can select that existing project instead). Click Continue and accept terms to create the Firebase project and a new Firebase App.
  4. You should next see a dialog to Connect your new Firebase App to your Android Studio project.

42c498d28ead2b77.png

  1. Back in Android Studio, in the Assistant pane, you should see the confirmation that your app is connected to Firebase.

dda8bdd9488167a0.png

Add Performance Monitoring to your app

In the Assistant pane in Android Studio, click Add Performance Monitoring to your app.

You should see a dialog to Accept Changes after which Android Studio should sync your app to ensure that all necessary dependencies have been added.

9b58145acc4be030.png

Finally, you should see the success message in the Assistant pane in Android Studio that all dependencies are set up correctly.

aa0d46fc944e0c0b.png

As an additional step, enable debug logging by following the instructions in the step "(Optional) Enable debug logging". The same instructions are also available in the public documentation.

3. Run the app

If you have successfully integrated your app with the Performance Monitoring SDK, the project should now compile. In Android Studio, click Run > Run ‘app' to build and run the app on your connected Android device/emulator.

The app has two buttons that take you to a corresponding Activity and Fragment, like this:

410d8686b4f45c33.png

In the following steps of this codelab, you'll learn how to measure the load time and screen rendering performance of your Activity or Fragment.

4. Understanding the loading of an Activity or Fragment

In this step, we will learn what the system is doing during the loading of an Activity or Fragment.

Understanding the loading of an Activity

For an Activity, the load time is defined as the time starting from when the Activity object is created all the way until the First Frame is completely drawn on the screen (this is when your user will see the complete UI for the Activity for the first time). To measure if your app is fully drawn, you can use the reportFullyDrawn() method to measure the elapsed time between application launch and complete display of all resources and view hierarchies.

On a high level, when your app calls startActivity(Intent), the system automatically performs the following processes. Each process takes time to complete, which adds to the duration of time between the Activity creation and when the user sees the UI for the Activity on their screen.

c20d14b151549937.png

Understanding the loading of a Fragment

Similar to the Activity the load time for a Fragment is defined as the time starting from when the Fragment gets attached to its host Activity all the way until the First Frame for the Fragment View is completely drawn on the screen.

5. Measure the load time of an Activity

Delays in the first frame can lead to a bad user experience, so it's important to understand how much initial load delay your users are experiencing. You can instrument a custom code trace to measure this load time:

  1. Start the custom code trace (named TestActivity-LoadTime) in the Activity class as soon as the Activity object is created.

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. Override the onCreate()callback, and get the View added by the setContentView()method.
@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. We've included an implementation of FistDrawListener, which has two callbacks: onDrawingStart()and onDrawingFinish() (see the next section below for more details about FirstDrawListener and what can affect its performance). Register the FirstDrawListener at the end of Activity's onCreate()callback. You should stop your viewLoadTrace in the onDrawingFinish()callback.

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. Re-run the app. Then, filter the logcat with "Logging trace metric". Tap on the LOAD ACTIVITY button, and look for logs like below:
I/FirebasePerformance: Logging trace metric: TestActivity-LoadTime (duration: XXXms)

🎉 Congrats! You've successfully measured the loading time of an Activity and reported that data to Firebase Performance Monitoring. We'll view the recorded metric in the Firebase console later in this codelab.

Purpose of FirstDrawListener

In the section just above, we registered a FirstDrawListener. The purpose of FirstDrawListener is to measure when the first frame has begun and finished drawing.

It implements the ViewTreeObserver.OnDrawListener and overrides the onDraw() callback which is invoked when the View tree is about to be drawn. It then wraps the result to provide two utility callbacks onDrawingStart()and onDrawingFinish().

The complete code for FirstDrawListener can be found in this codelab's source code.

6. Measure the load time of a Fragment

Measuring the load time of a Fragment is similar to how we measure it for an Activity but with some minor differences. Again, we'll instrument a custom code trace:

  1. Override the onAttach() callback and start recording your fragmentLoadTrace. We'll name this trace Test-Fragment-LoadTime.

As explained in an earlier step, the Fragment object can be created anytime, but it becomes active only when it's attached to its host 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. Register the FirstDrawListener in the onViewCreated()callback. Then, similar to the Activity example, stop the trace in the 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. Re-run the app. Then, filter the logcat with "Logging trace metric". Tap on the LOAD FRAGMENT button, and look for logs like below:
I/FirebasePerformance: Logging trace metric: TestFragment-LoadTime (duration: XXXms)

🎉 Congrats! You've successfully measured the loading time of a Fragment and reported that data to Firebase Performance Monitoring. We'll view the recorded metric in the Firebase console later in this codelab.

7. Understanding Screen Rendering and what is a Slow/Frozen frame

UI Rendering is the act of generating a frame from your app and displaying it on the screen. To ensure that a user's interaction with your app is smooth, your app should render frames in under 16ms to achieve 60 frames per second ( why 60fps?). If your app suffers from slow UI rendering, then the system is forced to skip frames, and the user will perceive stuttering in your app. We call this jank.

Similarly, frozen frames are UI frames that take longer than 700ms to render. This delay is a problem because your app appears to be stuck and is unresponsive to user input for almost a full second while the frame is rendering.

8. Measure the Slow/Frozen frames of a Fragment

Firebase Performance Monitoring automatically captures slow/frozen frames for an Activity (but only if it is Hardware Accelerated). However, this feature is currently not available for Fragments. The slow/frozen frames of a Fragment is defined as the slow/frozen frames for the entire Activity between the onFragmentAttached()and onFragmentDetached()callbacks in the Fragment's lifecycle.

Taking motivation from the AppStateMonitor class (which is a part of the Performance Monitoring SDK responsible for recording screen traces for Activity), we implemented the ScreenTrace class (which is part of this codelab source code repo). ScreenTrace class can be hooked up to the Activity's FragmentManager's lifecycle callback to capture slow/frozen frames. This class provides two public APIs:

  • recordScreenTrace(): Starts recording a screen trace
  • sendScreenTrace(): Stops the recording of a screen trace and attaches custom metrics to log Total, Slow, and Frozen frame counts

By attaching these custom metrics, screen traces for Fragments can be handled the same way as screen traces for an Activity and can be displayed along with other screen rendering traces in the Performance dashboard of the Firebase console.

Here's how to log screen traces for your Fragment:

  1. Initialize the ScreenTrace class in your Activity that hosts the Fragment.

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. When you load your Fragment, register for FragmentLifecycleCallbacks and override the onFragmentAttached() and onFragmentDetached()callbacks. We have done this for you. You need to start recording screen traces in the onFragmentAttached() callback and stop recording in the onFragmentDetached() callback.

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. Re-run the app. Then, tap on the LOAD FRAGMENT button. Wait for a few seconds, then click the back button on the bottom navigation bar.

Filter the logcat with "Logging trace metric", then look for logs like below:

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

Filter the logcat with "FireperfViews", then look for logs like below:

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

🎉 Congrats! You've successfully measured the Slow/Frozen frames for a Fragment and reported that data to Firebase Performance Monitoring. We'll view the recorded metrics in the Firebase console later in this codelab.

9. Check metrics in the Firebase console

  1. In the logcat, click the Firebase console URL to visit the details page for a trace. ceb9d5ba51bb6e89.jpeg

Alternatively, in the Firebase console, select the project that has your app. In the left panel, locate the Release & Monitor section, then click Performance.

  • In the main Dashboard tab, scroll down to the traces table, then click the Custom traces tab. In this table, you'll see the custom code traces we added earlier plus some out-of-the-box traces, such as _app_start trace.
  • Find your two custom code traces, TestActivity-LoadTime and TestFragment-LoadTime. Click on the Duration for either one to view more details about the collected data.

a0d8455c5269a590.png

  1. The detail page for the custom code trace shows you information about the duration of the trace (i.e., the measured load time).

5e92a307b7410d8b.png

  1. You can also view the performance data for your custom screen trace.
  • Go back to the main Dashboard tab, scroll down to the traces table, then click the Screen rendering tab. In this table, you'll see the custom screen traces we added earlier plus any out-of-the-box screen traces, such as MainActivity trace.
  • Find your custom screen trace, MainActivity-TestFragment. Click the trace name to view the aggregated data of slow rendering and frozen frames.

ee7890c7e2c28740.png

10. Congratulations

Congratulations! You've successfully measured load time and screen rendering performance of an Activity and a Fragment by using Firebase Performance Monitoring!

What you have accomplished

What's next

Firebase Performance provides more ways of performance measurement of your app other than custom trace. It automatically measures app startup time, app-in-foreground, and app-in-background performance data. It's time for you to check these metrics in the Firebase Console.

Also, Firebase Performance offers automatic HTTP/S network request monitoring. With that you can easily instrument network requests without writing a single line of code. Can you try sending some network requests from your app and find the metrics in the Firebase console?

Bonus

Now that you know how to measure the load time and screen rendering performance of your Activity/Fragment by using custom code traces, can you explore our open sourced code base to see if you can capture those metrics out of the box for any Activity/Fragment that is a part of the app? Feel free to send the PR if you wish :-)

11. Bonus Learning

Understanding what's happening during the loading of an Activity will help you better understand the performance characteristics of your app. In an earlier step, we described at a high level what happens during the loading of an Activity, but the following diagram describes each phase in much higher detail.

cd61c1495fad7961.png