Android Vitals - Why did my process start? 🌄
Tags: android, vitals, performance, monitoring
Published: 2020-08-15 14:06:06

> Header image: *Windmill Sunrise* [by Romain Guy](https://www.flickr.com/photos/romainguy/3961050471).

This blog series is focused on stability and performance monitoring of Android apps in production. Last week, I wrote about how to [determine if an app start is a cold start](https://dev.to/pyricau/android-vitals-is-this-a-cold-start-3m44):

> If we post a message and no activity was created when that message runs, then we know this isn't a cold start, even if an activity is eventually launched 20 seconds later.

```kotlin
class MyApp : Application() {

  override fun onCreate() {
    super.onCreate()

    var firstActivityCreated = false

    registerActivityLifecycleCallbacks(object :
        ActivityLifecycleCallbacks {

      override fun onActivityCreated(
          activity: Activity,
          savedInstanceState: Bundle?
      ) {
        if (firstActivityCreated) {
          return
        }
        firstActivityCreated = true
      }
    })
    Handler().post {
      if (firstActivityCreated) {
        // TODO Report cold start
      }
    }
  }
}
```

With this approach, we must wait for an activity to be launched or that message to run before we know if an app start is a cold start. Sometimes it would be useful to know that from within `Application.onCreate()`. For example, we might want to preload resources asynchronously to optimize cold start:

```kotlin
class MyApp : Application() {

  override fun onCreate() {
    super.onCreate()

    if (isColdStart()) {
      preloadDataForUiAsync()
    }
  }
}
```

# Process importance

While there is no Android API to know why a process was started, there is one to know why a process is still running: [RunningAppProcessInfo.importance](https://developer.android.com/reference/android/app/ActivityManager.RunningAppProcessInfo#importance), which we can read from [ActivityManager.getMyMemoryState()](https://developer.android.com/reference/android/app/ActivityManager#getMyMemoryState(android.app.ActivityManager.RunningAppProcessInfo)). According to the [Processes and Application Lifecycle documentation](https://developer.android.com/guide/components/activities/process-lifecycle):

> To determine which processes should be killed when low on memory, Android places each process into an "importance hierarchy" based on the components running in them and the state of those components. [...] When deciding how to classify a process, the system will base its decision on the most important level found among all the components currently active in the process. 

Right when the process starts, we could check its importance.  If the importance is [IMPORTANCE_FOREGROUND](https://developer.android.com/reference/android/app/ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND), it must be a cold start:

```kotlin
class MyApp : Application() {

  override fun onCreate() {
    super.onCreate()

    if (isForegroundProcess()) {
      preloadDataForUiAsync()
    }
  }

  private fun isForegroundProcess(): Boolean {
    val processInfo = ActivityManager.RunningAppProcessInfo()
    ActivityManager.getMyMemoryState(processInfo)
    return processInfo.importance == IMPORTANCE_FOREGROUND
  }
}
```

# Confirming with data

I implemented cold start detection with both approaches in a sample app and got identical results either way. I then added the detection code to a production app with enough installs to provide meaningful results. Here are the (anonymized) results:

![importance at startup](https://dev-to-uploads.s3.amazonaws.com/i/w05pxgsdit8pmcywycbl.png)

This pie chart includes only app startups where an **activity was created before the first post**.
* **97.4%** had an importance of **100**, i.e. [IMPORTANCE_FOREGROUND](https://developer.android.com/reference/android/app/ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND).
* **2.4%** had an importance of **400**, i.e. [IMPORTANCE_CACHED](https://developer.android.com/reference/android/app/ActivityManager.RunningAppProcessInfo#IMPORTANCE_CACHED) (previously named [IMPORTANCE_BACKGROUND](https://developer.android.com/reference/android/app/ActivityManager.RunningAppProcessInfo#IMPORTANCE_BACKGROUND)).
* **0.2%** map to other importances.

Let's look at that data from another angle: given a specific importance, how often was an activity created before the first post?

![importance at startup 2](https://dev-to-uploads.s3.amazonaws.com/i/prhr5t415hox62977ei2.png)

* When startup importance is 100 there's always an activity created before first post. And when an activity is created before first post, 97.4% of the time the importance is 100.
* When startup importance is 400 there's **almost never** an activity created before first post. Almost never is not never, there are still enough cases that when an activity is created before first post, 2.4% of the time the importance is 400.

The most likely explanation for the 2.4% with importance 400 is that those were not cold starts, however the system received a query to start an activity almost immediately after the process started, right when running `Application.onCreate()` but before we had a chance to add our first post.

**Edit:** I logged the importance after first post and compared that to the importance on app start. My data showed that 74% of app starts where an activity was created before first post and the start process importance was not 100 had  that process importance changed to 100 after first post. This seem to confirm  the theory that the system decided to start the activity after the app had already began to start.

# Conclusion

We can combine our findings from the previous post to determine cold start accurately. A cold start:

* Has a process importance of [IMPORTANCE_FOREGROUND](https://developer.android.com/reference/android/app/ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND) on app startup.
* Has the first activity created before the first post is dequeued.
* Has the first activity created with a null bundle.

Here's the updated code:

```kotlin
class MyApp : Application() {

  override fun onCreate() {
    super.onCreate()
	
    if (isForegroundProcess()) {
      var firstPostEnqueued = true
      Handler().post {
        firstPostEnqueued = false
      }
      registerActivityLifecycleCallbacks(object :
          ActivityLifecycleCallbacks {
      
        override fun onActivityCreated(
            activity: Activity,
            savedInstanceState: Bundle?
        ) {
          unregisterActivityLifecycleCallbacks(this)
          if (firstPostEnqueued && savedInstanceState == null) {
            // TODO Report cold start
          }
        }
      })
    }
  }
  
  private fun isForegroundProcess(): Boolean {
    val processInfo = ActivityManager.RunningAppProcessInfo()
    ActivityManager.getMyMemoryState(processInfo)
    return processInfo.importance == IMPORTANCE_FOREGROUND
  }
}
```

I hope you're enjoying these investigations!

{% twitter 1291126907426099200 %}