Firebase Performance Monitoring for Android Tip #1: Automatic Traces for All Activities

If you haven’t tried Firebase Performance Monitoring yet, many Firebase developers have found it to be a helpful way to get a sense of some of the performance characteristics of their iOS or Android app, without writing many extra lines of code. To get more detailed information beyond what’s collected automatically, you’ll eventually have to write some custom traces and counters. Traces are a report of performance data within a distinct period of time in your app, and counters let you measure performance-related events during a trace. In today’s perf tip, I’ll propose a way to add potentially many more traces to your Android app without writing very much code at all.

Android apps are typically made up of a collection of activities that present some task or data to the user. For the purpose of hunting down potential performance problems, it can be handy to define a trace for every Activity in your app, so you can study the results later in the Firebase console. If your app has lots of activities, it might be kind of a pain to write the code for all of them. Instead, you can write a little bit of code that instruments all of them with their own trace.

Android gives you a way to listen in on the lifecycle of every single Activity in your app. The listeners are implementations of the interface ActivityLifecycleCallbacks, and you can register one with the Application.registerLifecycleCallbacks() method. For measuring performance, I suggest creating a trace that corresponds to the onStart() and onStop() lifecycle methods. When an activity is “started”, that means it’s visible on screen, and when it’s “stopped”, it’s no longer visible, so I think this is a good place to define a trace that tracks an activity while it’s actually doing things. Here’s the start of an implementation of ActivityLifecycleCallbacks that keeps track of traces for each of your activities. First we’ll make it a singleton so it can be easily accessed everywhere (or you might want to use some form of dependency injection):

public class PerfLifecycleCallbacks
        implements Application.ActivityLifecycleCallbacks {

    private static final PerfLifecycleCallbacks instance =
        new PerfLifecycleCallbacks();

    private PerfLifecycleCallbacks() {}
    public static PerfLifecycleCallbacks getInstance() {
        return instance;
    }
}

Then, inside that class, I’ll add some members that manage custom traces for each Activity:

    private final HashMap<Activity, Trace> traces = new HashMap<>();

    @Override
    public void onActivityStarted(Activity activity) {
        String name = activity.getClass().getSimpleName();
        Trace trace = FirebasePerformance.startTrace(name);
        traces.put(activity, trace);
    }

    @Override
    public void onActivityStopped(Activity activity) {
        Trace trace = traces.remove(activity);
        trace.stop();
    }


    // ...empty implementations of other lifecycle methods...

This will start a trace when any activity is started, and stop the same trace when the activity is stopped. For the name of the trace, I’m using the simple class name of the activity object, which is just the class name without the full java package. (Note: if you do this, make sure that your Activity class names are unique, if they’re spread across Java packages!)

I’ll add one more method to it that will return the trace of a given Activity object. That can be used in any activity to get a hold of the current trace so that counters can be added to it:

    @Nullable
    public Trace getTrace(Activity activity) {
        return traces.get(activity);
    }

This class should be registered before any Activity starts. A ContentProvider is a good place to do that. If you’re not familiar with how that works, you can read about how Firebase uses a ContentProvider to initialize.

public class PerfInitContentProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        context = getContext();
        if (context != null) {
            Application app = (Application) context.getApplicationContext();
            app.registerActivityLifecycleCallbacks(
                PerfLifecycleCallbacks.getInstance());
        }
    }
}

Don’t forget to add the ContentProvider to your app’s manifest! This will ensure that it gets created before any Activity in your app.

Once this ContentProvider is in place, your app will automatically create traces for all your activities. If you want to add counters to one of them, simply use the getTrace() method from the PerfLifecycleCallbacks singleton using the current Activity object. For example:

private Trace trace;

@Override
protected void onCreate(Bundle savedInstanceState) {
    trace = PerfLifecycleCallbacks.getInstance().getTrace(this);
    // use the trace to tally counters...
}

Be sure to think carefully about the counters you want to log! You’ll want to measure things that will give you information that helps inform a decision about how the user experience could be improved in your app. For example, you could record the ratio of cache hits to misses to help tune the amount of memory for the cache. And be sure to follow Firebase on Twitter to get more Firebase Performance Monitoring tips.