Tracking Android App Launch in production

Search for a command to run...

Pierre-Yves Ricauyour wish is granted now, we have new API ApplicationStartInfo from API Level 35.
is there a way to improve app hot-launch time with the Papa lib or in general?
Our app is doing well in the cold and warm department but not able to figure what might be causing hot-launches to be bad.
any ideas?
You could add stack sampling in production with Debug.startMethodTracingSampling() (works in releases) with a low sampling rate, then upload the file from the device. Alternatively, you could track attributes (account, device type, entry point, etc) and then try to repro locally and once you repro a slow hot launch you can profile locally.
Exactly one year ago, after 12 years in San Francisco, I moved back to France. Life since has been a whirlwind: selling the condo, moving a container overseas, rediscovering French culture (I had to r

The SQLDelight documentation provides this example: val players: Flow<List<HockeyPlayer>> = playerQueries.selectAll() .asFlow() .mapToList(Dispatchers.IO) This looks reasonable, right? In the Square Point Of Sale application, we recently ...

A failed experiment with dominator trees

In this article I run the new LeakCanary toolkit against the Slack Android app. Read on to learn a bunch! A new LeakCanary toolkit In two months, I will give a talk at Droidcon SF: Cutting Edges: universal heap trimming with LeakCanary 3 At Square, ...

Today I was mob programming with Square's Mobile & Performance Reliability team and we toyed with an interesting idea. Our codebase has classes that represent screens a user can navigate to. These classes are defined in modules, and these modules hav...

👋 In this article, I summarize my findings on Android app launch after a few years of deep dives, with the hope that these guidelines help implementations track app launches correctly.
In User-Centric Mobile Performance, I argued that mobile app teams should primarily track user-centric performance metrics that represent the experience of using the app, that responsiveness thresholds should be context specific as user expectations vary based on what they are doing and what they're used to.
I used the example of app launches and a few days later Hanson Ho published a blog post arguing, amongst other things, that you should think about the percentage makeup of cold vs warm launches rather than just focusing on improving cold launches. Read it, it's a great perspective.
Hanson made a really good point: users don't really care about the specifics of whether an app is a cold or warm launch.
So what is it that users care about? As I highlighted above, user expectations vary based on what they are doing and what they're used to. We should put cold / hot / warm launch aside. Users can launch an app in different ways:
Tapping on a launcher icon
Tapping on a notification
Launch from another app (integration / chooser intent)
Bringing it back from Recents.
The first 3 cases can be determined by looking at the intent of the launched activity, and the last case by looking at whether the activity is provided with a saved instance state.
While it's useful to track whether a launch was cold / warm / hot, your primary dashboards and metrics should instead focus on the different scenarios under which users are launching the app, and target different thresholds for each.
For example, if 80% of app launches are from Recents (i.e. users constantly swap in & out of the app), then you should focus on optimizing that. And if 50% of the launches from Recents are cold launches, bringing that metric down, then you should look into why the app keeps getting killed (e.g. crash or high memory usage).
These words tend to be used interchangeably. However, I prefer Launch because it reminds me of the app launcher. Start can be confusing because an app launch can involve a process start or not. Also, in a Hot Launch the activity isn't actually started as it goes from paused to resumed.
An App Launch is when an app that had no visible activity now has visible activities.
In other words, an App Launch is when an app that had no activity in started or resumed state now has activities in started or resumed state.
I focus on activities because Android places each process into an importance hierarchy where a process can have a foreground or visible importance yet have no created activity (see Processes and app lifecycle).
I focus on visible rather than foreground because I don't want to count as app launch the case where the app had an activity visible in the background of an activity from another app and then came to the foreground.
The App startup time documentation does a great job of describing the difference between cold, warm, and hot launches.

However, there are different flavors of cold and warm launches, so it's worth digging into the details.
A Cold Launch is a launch where there was no existing app process and the system started the app process for the purpose of launching an activity. This is key: we need to check why the system decided to start the process. If the process was started for another reason and then sometimes during the init the system asked the app to launch an activity, then it's not a cold launch.
Some interesting attributes to track for cold launches:
A Hot Launch is a launch where there was an existing app process as well as an activity in Created state (i.e. stopped) that was then started.
A Warm Launch is any launch that isn't a cold launch or a hot launch:
Application.onCreate() wasn't finished running) then sometimes during the init the system asked the app to launch an activity. While this might look like a cold launch, users will actually not experience the full cold launch duration.The launch temperature is determined by the state of the app before the launch. In square/papa, I defined the following list of pre-launch states:
enum class PreLaunchState(val launchType: AppLaunchType) {
/**
* This is typically referred to as a "cold start".
* The process was started with a FOREGROUND importance and
* the launched activity was created, started and resumed before our first post
* ran.
*/
NO_PROCESS(COLD),
/**
* Same as [NO_PROCESS] but this was the first launch ever,
* which might trigger first launch additional work.
*/
NO_PROCESS_FIRST_LAUNCH_AFTER_INSTALL(COLD),
/**
* Same as [NO_PROCESS] but this was the first launch after the app was upgraded, which might
* trigger additional migration work. Note that if the upgrade if the first upgrade
* that introduces this library, the value will be [NO_PROCESS_FIRST_LAUNCH_AFTER_CLEAR_DATA]
* instead.
*/
NO_PROCESS_FIRST_LAUNCH_AFTER_UPGRADE(COLD),
/**
* Same as [NO_PROCESS] but this was either the first launch after a clear data, or
* this was the first launch after the upgrade that introduced this library.
*/
NO_PROCESS_FIRST_LAUNCH_AFTER_CLEAR_DATA(COLD),
/**
* This is the coldest type of "warm start". The process was not started with
* a FOREGROUND importance yet the launched activity was created, started and resumed
* before our first post ran. This means that while the process while starting, the
* system decided to launch the activity.
*/
PROCESS_WAS_LAUNCHING_IN_BACKGROUND(WARM),
/**
* This is a "warm start" where the activity brought to the foreground had to be created,
* started and resumed, and the task had no saved instance state bundle.
*/
NO_ACTIVITY_NO_SAVED_STATE(WARM),
/**
* This is a "warm start" where the activity brought to the foreground had to be created,
* started and resumed, and the task can benefit somewhat from the saved instance state bundle
* passed into onCreate().
*/
NO_ACTIVITY_BUT_SAVED_STATE(WARM),
/**
* This is a "hot start", the activity was already created and had been stopped when the app
* went in background. Bringing it to the foreground means the activity was started and then
* resumed. Note that there isn't a "ACTIVITY_WAS_PAUSED" entry here. We do not consider
* going from PAUSE to RESUME to be a launch because the activity was still visible so there
* is nothing to redraw on resume.
*/
ACTIVITY_WAS_STOPPED(HOT);
}
Here's the code to compute the pre-launch state which checks for cold starts by looking at process importance and whether the launched activity was created before the first post ran (as highlighted in Why did my process start? 🌄).
In When did my app start? ⏱ I concluded that you should use:
ContentProvider.onCreate().Since then API 33 added Process.getStartRequestedUptimeMillis() which is super promising, though I haven't tried using yet.
Process.getStartRequestedUptimeMillis(): when the user started waiting for the app to launchProcess.getStartUptimeMillis(): when the app started having an influence on launch time (when the APK starts loading)As far as I know, there's no API that provides the launch intent timestamp. There should be!
So our best option here is to leverage ActivityLifecycleCallbacks to capture timestamps:
The end of a launch is when the window of the launched activity has been rendered into a frame and submitted to the swap chain.
In First draw time 👩🎨 I showed how to access the decor view of the launched activity to add an OnDrawListener.
From the onDraw() callback, we need to figure out when that frame traversal is submitted to the swap chain with:
square/papaI'm hopeful that some day Google will release a new AndroidX library to track app launches (LaunchStats?). Until then, you can roll your own code following the guidelines I outlined, or leverage square/papa:
class ExampleApplication : Application() {
override fun onCreate() {
super.onCreate()
PapaEventListener.install { event ->
when (event) {
is AppLaunch -> {
Analytics.logAppLaunch(
preLaunchState = event.preLaunchState
durationUptimeMillis = event.durationUptimeMillis
)
}
}
}
}
}
I just realized that the AppLaunch event does not provide the intent and saved state details which are necessary to track User-Centric App Launch analytics as highlighted at the start of this article, so I filed square/papa#57 to fix that.
Header image generated by DALL-E.