Android application startup time statistics method

Start Time

Time To Initial Display (TTID)

The TTID metric measures the time it takes your app to generate its first frame, including process initialization (if cold start), activity creation (if cold/warm start), and displaying the first frame.

In Android 4.4 (API level 19) and higher, Logcat provides a Displayed value that measures the time elapsed between starting the process and finishing drawing the first frame of the activity on the screen.

In Android 4.4 (API level 19) and higher, Logcat includes an output line with a  Displayed value named . This value represents the time from the start of the process to the completion of drawing the corresponding activity on the screen. The elapsed time includes the following sequence of events:

  • Start the process.
  • Initialize the object.
  • Create and initialize the activity.
  • Expand the layout.
  • First paint application.

The log is as follows:

I/ActivityTaskManager: Displayed {包名}/.Activity: +414ms

Time To Full Display (TTFD)

The TTFD metric measures the time it takes for an app to generate the first frame with complete content, including content loaded asynchronously after the first frame. Typically, this is list-related content loaded from the network (reported by the app).

In the case of lazy loading, where the app's TTID does not include the load time of all resources, TTFD can be viewed as a separate metric:
for example, the app's UI might be fully loaded, with some text drawn, but not yet displayed Images loaded in .
To measure TTFD, manually call the reportFullyDrawn() method in the Activity after all content is displayed. After that, you can see in Logcat:

for example:

   override fun onResume() {
        super.onResume()
        reportFullyDrawn()
    }
2023-03-19 13:20:38.450 1449-1472/? I/ActivityTaskManager: Fully drawn {包名}/.MainActivity: +856ms

Local measurement startup time method

ADB

adb starts an application:

adb shell am start -W {包名}/.MainActivity

Jetpack Macrobenchmark

Application startup time can also be measured with Jetpack Macrobenchmark: Startup.

Benchmark your use case with Jetpack Macrobenchmark

With Macrobenchmark , you can write launch and runtime performance tests directly against your app running on devices running Android M (API 23) or higher .

It is recommended to use Macrobenchmark with the latest version of Android Studio (2021.1.1 or later) , as there are new features in the IDE that can be integrated with Macrobenchmark. Users of earlier versions of Android Studio can follow the additional instructions later in this topic to work with trace files.

Benchmarks are provided via the MacrobenchmarkRule JUnit4 rule API in the Macrobenchmark library:

    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = "mypackage.myapp",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) { // this = MacrobenchmarkScope
        pressHome()
        val intent = Intent()
        intent.setPackage("mypackage.myapp")
        intent.setAction("mypackage.myapp.myaction")
        startActivityAndWait(intent)
    }
  

self test:

gradle configuration:

defaultConfig {
        applicationId "com.example.myapplication"
        minSdk 23
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
implementation 'androidx.test.ext:junit-ktx:1.1.5'
implementation 'androidx.benchmark:benchmark-macro-junit4:1.1.0-beta05'
implementation 'androidx.test.uiautomator:uiautomator:2.2.0'

Production environment measurement startup time

Android Vitals

On the App Launch Times page in Android vitals (on the Google Play Console), you can see details about when your app launched from cold, warm, and hot system states.

Android vitals can help improve app performance by alerting you through the Play Console when your app is taking too long to launch  . Android vitals considers your app to take too long to start up if it:

  • A cold start took 5 seconds or more.
  • Warm boot took 2 seconds or more.
  • A warm boot took 1.5 seconds or more.

  • Affected period/number of days (number): The number of active days in which the user encountered at least one cold start time exceeding 5 seconds. Startup time is the time between when a user launches your app and when the first few frames appear on the screen. A day is counted as an active day if the user has used your app during the day.
  • Impacted hours/days: Percentage of work sessions where users experienced long startup times for each system state. Cold start time is too long: 5 seconds or more. (Percentage of active days where users experience at least 1 cold boot time longer than 5 seconds. Startup time is the time from when a user launches your app to when the first few frames appear on screen. If a user spends A day that has used your app is counted as an active day.)
  • Number of working sessions: the approximate number of working sessions recorded by the system.
  • 90th/99th percentile: 10%/1% of active days, users experience long startup times in your app.
  • 90th percentile (milliseconds): On 10% of active days, users experienced startup times greater than the following values.
  • 99th percentile (milliseconds): On 1% of active days, users experienced a startup time greater than the value below.

Notice:

The first is that if an application is launched from the same system state multiple times on the same day, the longest launch time for that day is recorded. So only record the worst startup times per day, not any occurrences.

The second (and more important) is when the first frame of the application is fully loaded. (TTID metric) Android vitals uses a preliminary display of the elapsed time metric. The Time To Initial Display (TTID) metric measures the time it takes your app to generate its first frame, including process initialization (if cold start), activity creation (if cold/warm start), and displaying the first frame. Startup time is tracked when the first frame of the application is fully loaded, even if it's not a page the user can interact with. Example: If the application starts with a splash screen, the startup time is equal to the time required to display the splash screen. For apps with a loading or splash screen, because Android vitals only measures the time until the loading screen is displayed, not the time the user can interact with the app, the resulting data is shorter than the actual launch time.



 

Firebase Performance Monitoring

Firebase Performance Monitoring (google.com)

For native apps the app launch time is automatically measured.

Performance Monitoring uses trace records to collect data about monitored processes in your application. A trace is a report that contains data captured in an application between two points in time.

Performance Monitoring automatically collects several traces related to the application lifecycle. All of these traces are like timers in that they measure how long ("duration") a process takes to run.

App launch trace - This trace measures the time between when a user opens the app and when the app responds. In the console, this trace is named  _app_start. The metric collected by this trace is Duration.

  • Fired when the app's FirebasePerfProvider ContentProvider completes its onCreate method.
  • Stops when the first activity's onResume() method is called.

Note that if the application is not cold-started by an Activity (for example, by a service or broadcast receiver), no trace will be generated.

Here's an example of app startup time metrics from the Firebase Performance Monitoring dashboard:

custom tracking

If your app has a splash animation on startup, you can manually track startup times using Firebase Performance Monitoring custom code tracking. Add custom monitoring for specific app code | Firebase Performance Monitoring (google.com)

Method to realize:

1. Create DVStartupTimeProvider

ContentProvider initialization is performed before Application.onCreate, which can be used to initialize Firebase. Register activity lifecycle callbacks by calling the StartupTrace.onColdStartInitiated method, and stop tracing in the onResume method of the first page the user can interact with.

package com.xxx.startup

import android.content.ContentProvider
import android.content.ContentValues
import android.database.Cursor
import android.net.Uri
import android.os.Handler
import android.os.Looper
import com.google.firebase.FirebaseApp

/**
 * @createTime 2023/3/26 16:08
 * @description
 */
class DVStartupTimeProvider : ContentProvider() {
    private val mainHandler = Handler(Looper.getMainLooper())
    override fun onCreate(): Boolean {
        FirebaseApp.initializeApp(context!!)
        DVStartupTrace.onColdStartInit(context!!)
        mainHandler.post(DVStartupTrace.StartFromBackgroundRunnable)
        return true
    }

    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
        return null
    }

    override fun getType(uri: Uri): String? {
        return null
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        return null
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        return -1
    }

    override fun update(
        uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?
    ): Int {
        return -1
    }
}

2. Configure DVStartupTimeProvider in AndroidManifest

A ContentProvider needs to be declared on the AndroidManifest.xml to be called when the application starts. The initOrder attribute defines the largest possible integer, so the ContentProvider is the first to be called.
FirebaseInitProvider is disabled because Firebase is initialized on DVStartupTimeProvider.

<provider  
	android:name="com.google.firebase.provider.FirebaseInitProvider"  
	android:authorities="${applicationId}.firebaseinitprovider"  
	tools:node="remove" />

<provider  
	android:authorities = "${applicationId}.startup-time-provider"  
	android:exported = "false"  
	android:initOrder = "2147483647"  
	android:name = "com.xxxx.startup.DVStartupTimeProvider" />  

 
3. Create a StartupTrace

The StartupTrace class will start the custom trace and then listen to all activity.onResume methods. Stop tracking in the onResume method of the first non-loading animated page.

package xxxx

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.perf.FirebasePerformance
import com.google.firebase.perf.metrics.Trace
import com.transsion.carlcare.HomeActivity
import java.util.concurrent.TimeUnit

/**
 * @createTime 2023/3/26 16:16
 * @description
 */
object DVStartupTrace : Application.ActivityLifecycleCallbacks, LifecycleObserver {
    private val START_SLOWEST_TIME = TimeUnit.MINUTES.toMillis(1)
    var appStartTime: Long = 0
    private var onCreateTime: Long = 0
    var isStartedFromBackground = false
    var atLeastOnTimeOnBackground = false
    private var isRegisteredForLifecycleCallbacks = false
    private lateinit var appContext: Context

    private var trace: Trace? = null

    var isTooSlowToInitUI = false

    fun onColdStartInit(context: Context) {
        appStartTime = System.currentTimeMillis()
        trace = FirebasePerformance.getInstance().newTrace("cold_startup_time")
        trace?.start()

        val applicationContext = context.applicationContext
        if (applicationContext is Application) {
            applicationContext.registerActivityLifecycleCallbacks(this)
            ProcessLifecycleOwner.get().lifecycle.addObserver(this)
            isRegisteredForLifecycleCallbacks = true
            appContext = applicationContext
        }
    }

    private fun unregisterActivityLifecycleCallbacks() {
        if (!isRegisteredForLifecycleCallbacks) {
            return
        }
        (appContext as? Application)?.unregisterActivityLifecycleCallbacks(this)
        isRegisteredForLifecycleCallbacks = false
    }

    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
        if (isStartedFromBackground || onCreateTime != 0L) {
            return
        }
        onCreateTime = System.currentTimeMillis()
        if (onCreateTime - appStartTime > START_SLOWEST_TIME) {
            isTooSlowToInitUI = true
        }
    }

    override fun onActivityStarted(activity: Activity) {
    }

    override fun onActivityResumed(activity: Activity) {
        if (isStartedFromBackground || isTooSlowToInitUI || atLeastOnTimeOnBackground) {
            unregisterActivityLifecycleCallbacks()
            return
        }
        if (activity is HomeActivity) {
            trace?.stop()
            trace = null

            if (isRegisteredForLifecycleCallbacks) {
                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)
    }

    object StartFromBackgroundRunnable : Runnable {
        override fun run() {
            if (onCreateTime == 0L) {
                isStartedFromBackground = true
            }
        }
    }

}

4. Add custom metrics to the dashboard

On the dashboard, add a custom metric cold_startup_time.

Guess you like

Origin blog.csdn.net/zhangying1994/article/details/129647766