Android 应用启动时间统计方式

启动时间

Time To Initial Display (TTID)

TTID 指标用于测量应用生成第一帧所用的时间,包括进程初始化(如果是冷启动)、activity 创建(如果是冷启动/温启动)以及显示第一帧。

在 Android 4.4(API 级别 19)及更高版本中,Logcat 提供了一个 Displayed 值,用于测量从启动进程到完成在屏幕上绘制 activity 的第一帧之间经过的时间。

在 Android 4.4(API 级别 19)及更高版本中,Logcat 包含一个输出行,该行包含名为 Displayed 的值。此值代表从启动进程到在屏幕上完成对应 activity 的绘制所用的时间。经过的时间包括以下事件序列:

  • 启动进程。
  • 初始化对象。
  • 创建并初始化 activity。
  • 扩充布局。
  • 首次绘制应用。

日志如下:

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

Time To Full Display (TTFD)

TTFD 指标用于测量应用生成具有完整内容的第一帧所用的时间,包括在第一帧之后异步加载的内容。一般情况下,这是从网络加载的列表相关内容(由应用上报)。

在延迟加载的场景下,应用的 TTID 不包括所有资源的加载时间,可以将 TTFD 视为单独的指标:
例如,应用的界面可能已完全加载,并绘制了一些文本,但尚未显示应用必须从网络中加载的图片。
要测量 TTFD,所有内容显示后在 Activity 中手动调用 reportFullyDrawn() 方法。之后,可以在 Logcat 中看到:

举个例子:

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

本地测量启动时间方式

ADB

adb 启动一个应用程序:

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

Jetpack Macrobenchmark

也可以使用 Jetpack Macrobenchmark: Startup 测量应用程序启动时间。

使用 Jetpack Macrobenchmark 对用例进行基准测试

借助 Macrobenchmark,可以直接针对在搭载 Android M (API 23)更高版本系统的设备上运行的应用编写启动和运行时性能测试

建议将 Macrobenchmark 与最新版本的 Android Studio(2021.1.1 或更高版本)搭配使用,因为该版本的 IDE 中提供了可与 Macrobenchmark 集成的新功能。使用早期版本 Android Studio 的用户可以根据本主题后面部分的额外说明来处理跟踪文件。

基准测试通过 Macrobenchmark 库中的 MacrobenchmarkRule JUnit4 规则 API 提供:

    @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)
    }
  

自测:

gradle配置:

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'

生产环境测量启动时间

Android Vitals

在 Android Vitals(在 Google Play 控制台上)的 App 启动时间页面上,可以看到有关你的应用程序何时从 cold、warm 和 hot 系统状态启动的详细信息。

当您的应用启动时间过长时,Android Vitals 可以通过 Play 管理中心提醒您,从而帮助提升应用性能。Android Vitals 在您的应用出现以下情况时将其启动时间视为过长:

  • 启动用了 5 秒或更长时间。
  • 启动用了 2 秒或更长时间。
  • 启动用了 1.5 秒或更长时间。

  • 受影响的时段/日数量(个):用户遇到至少 1 次冷启动时间超过 5 秒的情况的活跃日数量。启动时间是指从用户启动您的应用到起初的几帧画面显示在屏幕上所需的时间。如果用户在一天内使用过您的应用,当天即计为一个活跃日。
  • 受影响的时段/日百分比:用户在各个系统状态下遇到启动时间过长问题的工作时段所占的百分比。冷启动时间过长:5 秒或更长。(用户遇到至少 1 次冷启动时间超过 5 秒的情况的活跃日百分比。启动时间是指从用户启动您的应用到起初的几帧画面显示在屏幕上所需的时间。如果用户在一天内使用过您的应用,当天即计为一个活跃日。)
  • 工作时段数:系统已记录的工作时段的大概数量。
  • 第 90/99 个百分位:10%/1% 的活跃日中,用户在您的应用中遇到启动时间过长的问题。
  • 第90个百分位(毫秒):在 10% 的活跃日中,用户遇到了启动时间超过以下值的情况。
  • 第99百分位(毫秒):在 1% 的活跃日中,用户遇到了启动时间超过以下值的情况。

注意:

第一个是如果应用程序在同一天从同一系统状态启动多次,则会记录当天的最长启动时间。所以只记录每天最差的启动时间,而不是任何发生的时间。

第二个(也是更重要的)是当应用程序的第一帧完全加载时。(TTID 指标)Android Vitals 使用初步显示所用时间指标。初步显示所用时间 (TTID) 指标用于测量应用生成第一帧所用的时间,包括进程初始化(如果是冷启动)、activity 创建(如果是冷启动/温启动)以及显示第一帧。当应用程序的第一帧完全加载时,将跟踪启动时间,即使它不是用户可以交互的页面。示例:如果应用程序以启动画面启动,则启动时间等于显示启动画面所需的时间。对于带有加载或启动画面的应用程序来说,因为 Android Vitals 只测量加载屏幕显示之前的时间,而不是用户可以与应用交互的时间,所以得到的数据比实际启动时间更短。



 

Firebase 性能监控

Firebase Performance Monitoring (google.com)

对于原生应用会自动衡量应用启动时间。

Performance Monitoring 使用跟踪记录来收集有关应用中受监控进程的数据。跟踪记录是一种报告,包含两个时间点之间在应用中捕获的数据。

Performance Monitoring 会自动收集与应用生命周期相关的多个跟踪记录。所有这些跟踪记录都类似于计时器,因为它们会衡量进程运行所需的时间(“时长”)。

应用启动跟踪记录 - 此跟踪记录衡量用户打开应用到应用响应之间的时间。在控制台中,此跟踪记录的名称为 _app_start。此跟踪记录收集的指标为“时长”。

  • 在应用的 FirebasePerfProvider ContentProvider 完成其 onCreate 方法时启动。
  • 在调用第一个活动的 onResume() 方法时停止。

请注意,如果应用程序不是由Activity冷启动(例如,由服务或广播接收器),则不会生成任何跟踪。

这是 Firebase 性能监控仪表板中应用启动时间指标的示例:

自定义跟踪

如果你的应用程序在启动时有启动动画,可以使用 Firebase Performance Monitoring 自定义代码跟踪手动跟踪启动时间。为特定应用代码添加自定义监控  |  Firebase Performance Monitoring (google.com)

实现方式:

1.创建 DVStartupTimeProvider

ContentProvider 初始化在 Application.onCreate 之前执行,可以使用其初始化 Firebase。通过调用 StartupTrace.onColdStartInitiated 方法注册 activity 生命周期回调,在用户可以交互的第一个页面 的 onResume 方法中停止跟踪。

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. AndroidManifest 中配置DVStartupTimeProvider

需要在 AndroidManifest.xml 上声明 ContentProvider,以便在应用程序启动时调用。该 initOrder 属性定义了可能的最大整数,因此该 ContentProvider 是第一个被调用的。
因为在 DVStartupTimeProvider 上初始化 Firebase ,因此禁用了 FirebaseInitProvider 。

<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.创建 StartupTrace

该 StartupTrace 类将启动自定义跟踪,然后侦听所有 activity.onResume方法。在第一个非加载动画页面的 onResume 方法中停止跟踪。

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.在仪表板上添加自定义指标

在仪表板上,添加自定义指标 cold_startup_time。

猜你喜欢

转载自blog.csdn.net/zhangying1994/article/details/129647766