TLDR;
So agenda for great cold startup should be
- fast Application.onCreate
- fast Cold Start Activity’s
- onCreate
- onStart
- onResume
What defines an AppStart at Higher Level
At a higher level we expect following things
Which is
- when we click on app launcher icon
- main entry for any APP
- other can be deepLinkActivity, if app supports one, or some media play activity if its a media App
- we expect app process to be created
- Creation of app object
- launching the main thread
- creating main Activity
- Inflating views
- Laying out screen
- Performing initial draw
- And then app functionality take over
What actually happens
This UML diagram is taken from Py’s dev.to Blog, link in reference below, highly recommended to read it
Now everything in sequence
- User taps launcher ICON
- An IPC call is being made to start that activity of App
- now as a result
system_server
shows the starting screen of App - and
system_server
parallely sends a fork command to create app process from Zygot process - And as soon as process is create from Zygot process
- Main thread is started
- And activityManagerService.attachApplication() is called and response is awaited
- After IPC return, Another call is being made to bind Application to activity Thead
- A message
BIND_APPLICATION
is emitted to Message Queue - Another message
EXECUTE_TRANSACTION
is also queued to Queue
- A message
- Now attachApplication is successful
- Main thread is resumed
- Looper.next is executed
- Gets
BIND_APPLICATION
- hence
ActivityThread.bindApplication()
is called
- Gets
- Looper.next is executed
- Gets
EXECUTE_TRANSACTION
- Then
ActivityThread.handleLaunchActivity()
is called, activity is created - Then
ActivityThread.handleStartActivity()
is called, activity is started - Then
ActivityThread.handleResumeActivity()
is called - Then Choreographer.scheduleFrameLocked() send
MSG_DO_FRAME
to Message Queue - After this Activity Resumes
- Gets
- And then Looper is enQueued again and return
MSG_DO_FRAME
- and then activity first draw happen on Main Thread
So from icon click to first draw all these things happens.
What comes under Cold start?
The user experience of launching an activity starts when the user touches the screen, however app developers have little influence on the time spent before
ActivityThread.handleBindApplication()
, so that’s where app cold start monitoring should start.
So we should monitor from handleBindApplication
till first draw.
To measure it#
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
}
}
}
}
Idea
- when its cold app start, before activity resumes, 1 events are already in Message Queue
EXECUTE_TRANSACTION
- and then this Handler.post{} is called onCreate of Application class
- and there we log COLD start
Lukewarm Start
There are many potential states that could be considered warm starts. For instance:
- The user backs out of your app, but then re-launches it. The process may have continued to run, but the app must recreate the activity from scratch via a call to onCreate().
- The system evicts your app from memory, and then the user re-launches it. The process and the activity need to be restarted, but the task can benefit somewhat from the saved instance state bundle passed into onCreate().
So if the activity is created with a saved instance state bundle, then that shouldn’t be considered a cold start. However, since the process needs to be restarted, there’s still a lot more work to do than just creating an activity. Let’s call this a lukewarm start.
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
var firstActivityCreated = false
var hasSavedState = false
registerActivityLifecycleCallbacks(object :
ActivityLifecycleCallbacks {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
if (firstActivityCreated) {
return
}
firstActivityCreated = true
hasSavedState = savedInstanceState != null
}
})
Handler().post {
if (firstActivityCreated) {
if (hasSavedState) {
// TODO Report lukewarm start
} else {
// TODO Report cold start
}
}
}
}
}
Check if its a cold start programatically?
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:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
if (isColdStart()) {
preloadDataForUiAsync()
}
}
}
We can also use activity importance to double check on coldStart hypothesis. Taken from Why Did my app start?
blog of Py
So final code looks like this
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
}
}
Neat,
and final end time is like following
class MyApp : Application() {
var firstFrameDoneMs: Long = 0
override fun onCreate() {
super.onCreate()
Choreographer.getInstance().postFrameCallback {
firstFrameDoneMs = SystemClock.uptimeMillis()
}
}
}
Trying all this in real App
To understand things better lets run this on an existing android application
Will be running benchmarks on this app called Meme Generator on my github.
We would be using benchmark library and method trace visualizer here.
Without Application class#
To start with the application does not have app class. And following are benchmark results
Starting 1 tests on moto g45 5G - 15
ExampleStartupBenchmark_startup
timeToInitialDisplayMs min 456.6, median 527.4, max 639.4
Traces: Iteration 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
Pictorial representation#
So, android studio profiler feature was used to get .trace file, and then used JAR from here to see this above image.
Above screenshot does not show app process creation brick, this is because application class was not created and attached to App in manifest file.
Always do sampling to create get small .trace file like in above Screenshot
With application class#
class MemeApp: Application() {
override fun onCreate() {
super.onCreate()
initFirebase()
initAnalytics()
}
private fun initAnalytics() {
Thread.sleep(500)
}
private fun initFirebase() {
Thread.sleep(2000)
}
}
Now to see benchmark results we have
Starting 1 tests on moto g45 5G - 15
ExampleStartupBenchmark_startup
timeToInitialDisplayMs min 2,929.6, median 3,030.7, max 3,047.4
Traces: Iteration 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
As expected median as bumped by roughly 2500 ms, i.e 2.5 seconds
Pictorial representation#
Findings
- You can now see that bindApplication is now visible
- and onCreate, took 2500 ms to get completed
Heavy work on activity’s onCreate#
Earlier it took 18 ms for MainActivity’s onCreate
Now, in MainActivity’s onCreate I am doing Thread.sleep(500)
, lets see this now
Starting 1 tests on moto g45 5G - 15
ExampleStartupBenchmark_startup
timeToInitialDisplayMs min 3,522.8, median 3,533.7, max 3,580.1
Traces: Iteration 0 1 2 3 4
as expected onCreate time impacted and hence first draw took time
Even if we put delay from onCreate to onResume() still metric would be same, because sequence to first draw is following
Application.onCreate()
Activity.onCreate()
Activity.onStart()
Activity.onResume()
Layout inflation, View.draw(), doFrame, etc.
So agenda for great cold startup should be
- fast Application.onCreate
- fast Cold Start Activity’s
- onCreate
- onStart
- onResume
Stay tuned for more startup insights!
Resource
Articles by Py here, its a 11 part series, must go through them for details