Skip to content

Instantly share code, notes, and snippets.

@maxirosson
Last active October 7, 2021 02:12
Show Gist options
  • Save maxirosson/952f5f19b7bf381be2281c5320a1d396 to your computer and use it in GitHub Desktop.
Save maxirosson/952f5f19b7bf381be2281c5320a1d396 to your computer and use it in GitHub Desktop.
package com.dipien.startup
import android.app.Activity
import android.app.Application
import android.content.Context
import android.os.Bundle
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import com.google.firebase.ktx.Firebase
import com.google.firebase.perf.ktx.performance
import com.google.firebase.perf.metrics.Trace
import java.util.concurrent.TimeUnit
/**
* A class to capture the Android AppStart Trace information.
* https://github.com/firebase/firebase-android-sdk/blob/master/firebase-perf/src/main/java/com/google/firebase/perf/provider/FirebasePerfProvider.java
*/
object StartupTrace : Application.ActivityLifecycleCallbacks, LifecycleObserver {
private val TAG = StartupTimeProvider::class.simpleName
private val MAX_LATENCY_BEFORE_UI_INIT = TimeUnit.MINUTES.toMillis(1)
var appStartTime: Long? = null
private var onCreateTime: Long? = null
var isStartedFromBackground = false
var atLeastOnTimeOnBackground = false
private var isRegisteredForLifecycleCallbacks = false
private var appContext: Context? = null
private var trace: Trace? = null
/**
* If the time difference between app starts and creation of any Activity is larger than
* MAX_LATENCY_BEFORE_UI_INIT, set isTooLateToInitUI to true and we don't send AppStart Trace.
*/
var isTooLateToInitUI = false
fun onColdStartInitiated(context: Context) {
appStartTime = System.currentTimeMillis()
trace = Firebase.performance.newTrace("cold_startup_time")
trace!!.start()
val appContext = context.applicationContext
if (appContext is Application) {
appContext.registerActivityLifecycleCallbacks(this)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
isRegisteredForLifecycleCallbacks = true
this.appContext = appContext
}
}
/** Unregister this callback after AppStart trace is logged. */
private fun unregisterActivityLifecycleCallbacks() {
if (!isRegisteredForLifecycleCallbacks) {
return
}
(appContext as Application).unregisterActivityLifecycleCallbacks(this)
isRegisteredForLifecycleCallbacks = false
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (isStartedFromBackground || onCreateTime != null) {
return
}
onCreateTime = System.currentTimeMillis()
if ((onCreateTime!! - appStartTime!!) > MAX_LATENCY_BEFORE_UI_INIT) {
isTooLateToInitUI = true
}
}
override fun onActivityStarted(activity: Activity) { }
override fun onActivityResumed(activity: Activity) {
if (isStartedFromBackground || isTooLateToInitUI || atLeastOnTimeOnBackground) {
unregisterActivityLifecycleCallbacks()
return
}
if (activity !is LoadingActivity) {
Log.d(TAG, "Cold start finished after ${System.currentTimeMillis() - appStartTime!!}ms")
trace?.stop()
trace = null
if (isRegisteredForLifecycleCallbacks) {
// After AppStart trace is logged, we can unregister this callback.
unregisterActivityLifecycleCallbacks()
}
}
}
override fun onActivityPaused(activity: Activity) { }
override fun onActivityStopped(activity: Activity) { }
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { }
override fun onActivityDestroyed(activity: Activity) { }
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onEnterBackground() {
atLeastOnTimeOnBackground = true
ProcessLifecycleOwner.get().lifecycle.removeObserver(this)
}
/**
* We use StartFromBackgroundRunnable to detect if app is started from background or foreground.
* If app is started from background, we do not generate AppStart trace. This runnable is posted
* to main UI thread from StartupTimeProvider. If app is started from background, this runnable
* will be executed before any activity's onCreate() method. If app is started from foreground,
* activity's onCreate() method is executed before this runnable.
*/
object StartFromBackgroundRunnable : Runnable {
override fun run() {
// if no activity has ever been created.
if (onCreateTime == null) {
isStartedFromBackground = true
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment