版权声明

凡未经作者授权,任何媒体、网站及个人不得转载、复制、重制、改动、展现或运用部分或全部的内容或服务。假如已转载,请自行删去。同时,咱们保存进一步追查相关行为主体的法律责任的权力。

2023 小酥肉不加辣,All rights reserved.

如何衡量和改进 Android 应用启动时间

让咱们探究衡量和改进运用发动时刻的不同办法,从这篇文章中,你能够发现还存在很多优化的机会,能够确定将你的优化作业集中在何处,并且能够看到优化后改进了多少。

发动时刻的界说

Time To Initial Display (TTID)

TTID 目标用于丈量运用生成榜首帧所用的时刻,包括进程初始化(假如是冷发动)、activity 创立(假如是冷发动/温发动)以及显现榜首帧。

Android 4.4(API 级别 19)及更高版别中,Logcat 供给了一个 Displayed值,用于丈量从发动进程到完结在屏幕上制作 activity 的榜首帧之间经过的时刻。

日志相似于以下示例:

ActivityManager: Displayed {package}/.StartupTiming: +1s434ms

Time To Full Display (TTFD)

TTFD 目标用于丈量运用生成具有完好内容的榜首帧所用的时刻,包括在榜首帧之后异步加载的内容。一般情况下,这是从网络加载的主要列表内容(由运用陈述)。

在推迟加载的场景下,运用的 TTID 不包括一切资源的加载时刻,能够将 TTFD 视为独自的目标:

例如,运用的界面或许已彻底加载,并制作了一些文本,但没有显现运用有必要从网络中加载的图片。

要丈量 TTFD,你能够在一切内容显现后在 Activity 中手动调用 reportFullyDrawn() 办法。之后,能够在 Logcat 中看到它:

ActivityManager: Fully drawn {package}/.MainActivity: +3s524ms

如安在本地环境丈量发动时刻

除了上述说到的能够在 Logcat 中检查日志的办法检查发动时刻,还有另种办法:

ADB

adb shell 供给了一个命令行界面来发动一个运用程序:

adb shell am start -n {package}/.MainActivity

运转的成果中能够检查运用发动时刻:

Starting: Intent { cmp={package}/.MainActivity }
Status: ok  
LaunchState: COLD  
Activity: {package}/.MainActivity  
TotalTime: 1380  
WaitTime: 1381  
Complete

Jetpack Macrobenchmark

运用 Jetpack Macrobenchmark: Startup 丈量运用程序发动时刻。 编写如下测试代码:

@RunWith(AndroidJUnit4::class)
class ExampleStartupBenchmark {
    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()
    @Test
    fun startup() = benchmarkRule.measureRepeated(
        packageName = "com.example.macrobenchmark_codelab",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD,
    ) {
        pressHome()
        startActivityAndWait()
        val contentList = device.findObject(By.res("snack_list"))
        val searchCondition = Until.hasObject(By.res("snack_collection"))
        // Wait until a snack collection item within the list is rendered
        contentList.wait(searchCondition, 5_000)
    }
}

运转测试之后检查成果

如何衡量和改进 Android 应用启动时间

更详细的内容可检查文章《运用 Macrobenchmark 测试 Android 运用功能》

如安在出产环境丈量发动时刻

丈量出产环境设备上的发动时刻也非常重要。它能供给更精确的丈量成果,由于你的运用程序是在真实场景中运用许多不同的设备履行的。

Android Vitals

在 Android Vitals(在 Google Play 操控台上)的 App 发动时刻页面上,能够看到有关你的运用程序何时从 cold、warm 和 hot 体系状况发动的详细信息。这些目标是主动核算的,无需任何开发作业。

如何衡量和改进 Android 应用启动时间

如何衡量和改进 Android 应用启动时间

在检查数据时,有两个方面需求留意,

榜首个是假如运用程序在同一天从同一体系状况发动屡次,则会记载当天的最长发动时刻

这意味着只记载每天最差的发动时刻,而不是任何产生的时刻。

第二个(也是更重要的)是当运用程序的榜首帧彻底加载时,将盯梢发动时刻,即便它不是用户能够交互的页面

示例:假如运用程序以发动画面发动,则发动时刻等于显现发动画面所需的时刻。

对于带有加载或发动画面的运用程序来说,由于 Android Vitals 只丈量加载屏幕显现之前的时刻,而不是用户能够与运用交互的时刻,所以得到的数据比实际发动时刻更短。

如何衡量和改进 Android 应用启动时间

Firebase 功能监控

Firebase Performance Monitoring 会主动搜集与运用程序生命周期相关的多个盯梢记载。

运用发动

此盯梢丈量用户打开运用程序和运用程序响应之间的时刻。在操控台中,盯梢的名称是_app_start 。此盯梢搜集的目标是 “持续时刻”。

  • FirebasePerfProvideronCreate 办法履行时发动。

  • onResume() – 在调用榜首个活动的办法时中止。

留意:假如运用程序不是从 activity 冷发动的,则不会生成任何盯梢记载。

这是 Firebase 功能监控仪表板中运用发动时刻目标的示例:

如何衡量和改进 Android 应用启动时间

Firebase Performance Monitoring 与 Android Vitals 相似,带有发动画面的运用程序的发动时刻并不精确。

自界说盯梢

假如你的运用程序在发动时有发动动画,能够运用 Firebase Performance Monitoring 自界说代码盯梢手动盯梢发动时刻。

需求依照以下 4 个过程完成:

  1. 创立 StartupTimeProvider

ContentProviderApplication.onCreate 之前履行,所以能够运用它初始化 Firebase。通过调用 StartupTrace.onColdStartInitiated 办法注册 activity 生命周期回调,在用户能够交互的榜首个页面 的 onResume 办法中中止盯梢。

class StartupTimeProvider: ContentProvider() {
    ... ...
    companion object {  
        private val TAG = StartupTimeProvider::class.simpleName  
    }  
    private val mainHandler = Handler(Looper.getMainLooper())  
    override fun onCreate(): Boolean {  
        try {  
            if (FirebaseApp.initializeApp(context!!) == null) {  
                Log.w(TAG, "FirebaseApp initialization unsuccessful");  
            } else {  
                Log.i(TAG, "FirebaseApp initialization successful");  
            }  
            StartupTrace.onColdStartInitiated(context!!)  
            mainHandler.post(StartupTrace.StartFromBackgroundRunnable)  
        } catch (e: Exception) {  
            Log.e(TAG, "Failed to initialize StartupTimeProvider", e)  
        }  
        return true  
    }  
    ... ...
}
  1. 在 AndroidManifest 文件上装备 StartupTimeProvider

需求在 AndroidManifest.xml 上声明 ContentProvider,以便在运用程序发动时调用。该 initOrder 特点界说了或许的最大整数,因此该 ContentProvider 是榜首个被调用的。

由于咱们已经在 StartupTimeProvider 上初始化 Firebase,因此禁用了 FirebaseInitProvider

<?xml version= "1.0" encoding= "utf-8" ?>
<manifest xmlns:android = "http://schemas.android.com/apk/res/android"  
xmlns:tools = "http://schemas .android.com/tools"  
package = "com.example">  
    <application>
        <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.example.startup.StartupTimeProvider" />  
    </application>
</manifest>
  1. 创立 StartupTrace

StartupTrace 类将发动自界说盯梢,然后侦听一切 activity.onResume办法。在榜首个非加载动画页面的 onResume 办法中中止盯梢。

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
    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 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) {
                unregisterActivityLifecycleCallbacks()
            }
        }
    }
    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onEnterBackground() {
        atLeastOnTimeOnBackground = true
        ProcessLifecycleOwner.get().lifecycle.removeObserver(this)
    }
    ... ...
    object StartFromBackgroundRunnable : Runnable {
        override fun run() {
            if (onCreateTime == null) {
                isStartedFromBackground = true
            }
        }
    }
}
  1. 在仪表板上增加自界说目标

在仪表板上,增加自界说目标 cold_startup_time

如何衡量和改进 Android 应用启动时间

现在能够在仪表板上看到新目标:

如何衡量和改进 Android 应用启动时间

运用此自界说盯梢,您还能够丈量加载 activity 所耗费的时刻。

如何衡量和改进 Android 应用启动时间

改进运用发动时刻

一旦界说了良好的发动目标,在运用程序中检测它们能够让你了解并优先考虑改进发动功能以供给更好的用户体会。

为了完成发动时刻功能改进,需求检查三个重要的当地:

  • 任何 ContentProvider.oncreate()
  • 你的 Application.oncreate()
  • 榜首个 Activity.oncreate()

修复发动溃散

发动期间的溃散是让用户放弃一个运用程序的最快的办法,因此在开端削减发动时刻之前,先修复发动溃散。

后台作业

除非有必要,不然不要阻塞主线程。将 I/O 和非要害途径移出主线程。在运用程序发动之前,删去、推迟或将与发动体会不直接相关的任何作业移至后台。一些有用的提示:

  • JetpackWorkManager 能够协助你将作业转移到后台运转。
  • 尽量防止在主线程上运用 runBlocking,由于它会运转一个新的 Kotlin 协程并阻塞当时线程直到它完结。
  • 在主线程上运用 SharedPreference 的 apply() 而不是 commit()。 commit() 办法是同步的,会暂停 UI 烘托。

删去或共同网络请求

运用程序在发动时会履行很多网络请求。尽量削减烘托首屏所需的网络请求次数,甚至共同屡次调用,以节约时刻。

推迟加载

测验尽或许地进行推迟加载组件、模块和库,以便在榜首次运用时尽或许晚地履行初始化或装备,而不是都在 Application#onCreateActivity#onCreate 中初始化。

缓存内容

在某些情况下,缓存呈现榜首个屏幕所需的内容能够节约发动时刻。这需求你评价是尽快显现新内容更好,还是显现当即可用的内容更好。

跳过出产调试代码

应该在出产环境中跳过一切与调试相关的代码。比如:

  • leakcanary
  • Logcat / timber
  • StrictMode

削减剖析 / 盯梢库集成

在运用程序中集成的库越多,发动时刻就越长。大多数用于剖析或盯梢的第三方库都在 ContentProvider 中初始化,然后导致运用程序发动时刻增加。这些第三方库包括 Firebase Analytics、Firebase Performance Monitoring、Firebase Crashlytics、Sentry、Instabug、Adjust、New Relic 等。

  • 测验将这些库削减到最低极限,并防止集成多个履行相同丈量盯梢的库。
  • 将剖析和盯梢视为一种抽样,因此并非 100% 的用户都需求集成剖析 / 盯梢库。

Jetpack Startup

假如你运用 ContentProvider 来初始化运用程序的某些部分,那么应该将它们搬迁到 Jetpack Startup 。它供给了一种在运用程序发动时初始化组件的高效办法。库开发人员和运用程序开发人员都能够运用此库来简化发动次序并明确设置初始化次序。

关于它的一些运用链接:

  • 文档
  • 版别说明
  • 源码

禁用 FirebaseInitProvider

假如运用运用 Firebase SDK ,在运用 Gradle 进行构建时,Android 构建东西通常会主动将FirebaseInitProvider 合并到你的运用构建中。每个 ContentProvider 都会增加运用的发动时刻,所以删去此 ContentProvider 能够协助提高发动功能。

在运用的 Manifest 文件中,增加一条 FirebaseInitProvider的声明,并运用节点标记将其特点设置 tools:node 为值 "remove"。这会告诉 Android 构建东西不要在运用中包括此组件:

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

由于删去了 FirebaseInitProvider,所以需求在你的运用中的某处履行相同的初始化(在你自己的 Jetpack App StartupInitializer 中,以确保 Analytics 能够正常作业):

if (FirebaseApp.initializeApp(context!!) == null) {
    Log.w(TAG, "FirebaseApp initialization unsuccessful")  
} else {  
    Log.i(TAG, "FirebaseApp initialization successful")  
}

自界说发动页

从 Android 12 开端,需求搬迁发动页到 SplashScreen API。这个 API 能够加速发动时刻,并完成向后兼容,为一切 Android 体系版别的发动页创造共同的外观和感觉。

概况点击 Splash Screen 搬迁指南检查。

基线装备文件

基线装备文件是 APK 中包括的类和办法的列表,Android 运转时 (ART) 在装置期间运用它来预编译机器码的要害途径。这是一种装备文件引导优化 (PGO) 方式,可让运用优化发动、削减卡顿并提高功能。

有关详细信息,请阅览我的另一篇文章运用 Baseline Profiles 改进 Android 运用功能。

参阅

developer.android.com/topic/perfo…