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
- 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.
- Select Tools > Firebase to open the Assistant pane.
- Choose Performance Monitoring to add to your app, then click Get started with Performance Monitoring.
- Click Connect to Firebase to connect your Android project with Firebase (this will open up the Firebase console in your browser).
- 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.
- You should next see a dialog to Connect your new Firebase App to your Android Studio project.
- Back in Android Studio, in the Assistant pane, you should see the confirmation that your app is connected to Firebase.
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.
Finally, you should see the success message in the Assistant pane in Android Studio that all dependencies are set up correctly.
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:
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.
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:
- 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");
// ...
}
- Override the
onCreate()
callback, and get the View added by thesetContentView()
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);
// ...
}
- We've included an implementation of
FistDrawListener
, which has two callbacks:onDrawingStart()
andonDrawingFinish()
(see the next section below for more details aboutFirstDrawListener
and what can affect its performance). Register theFirstDrawListener
at the end of Activity'sonCreate()
callback. You should stop yourviewLoadTrace
in theonDrawingFinish()
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();
}
});
- 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:
- Override the
onAttach()
callback and start recording yourfragmentLoadTrace
. We'll name this traceTest-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");
}
- Register the
FirstDrawListener
in theonViewCreated()
callback. Then, similar to the Activity example, stop the trace in theonDrawingFinish()
.
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();
}
});
- 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 tracesendScreenTrace()
: 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:
- 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);
// ...
}
- When you load your Fragment, register for
FragmentLifecycleCallbacks
and override theonFragmentAttached()
andonFragmentDetached()
callbacks. We have done this for you. You need to start recording screen traces in theonFragmentAttached()
callback and stop recording in theonFragmentDetached()
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);
}
};
- Re-run the app. Then, tap on the
LOAD FRAGMENT
button. Wait for a few seconds, then click theback 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
- In the logcat, click the Firebase console URL to visit the details page for a trace.
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
andTestFragment-LoadTime
. Click on the Duration for either one to view more details about the collected data.
- The detail page for the custom code trace shows you information about the duration of the trace (i.e., the measured load time).
- 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.
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
- You integrated Firebase Performance Monitoring into a sample app
- You now understand the life cycle of View loading
- You measured load time of both an Activity and a Fragment by adding custom code traces
- You recorded slow/frozen frames by adding custom screen traces with custom metrics
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.