App启动时间(翻译)

2,734 阅读12分钟

本文从谷歌官网翻译: developer.android.google.cn/topic/perfo…

用户希望App启够足够快的开始启动,如果一个App启动时间过长,会令用户非常失望,并且可能会在play store中对App评价很低或者干脆卸载我们的App.

这篇文章主要是提供了一种能够优化我们App启动时间的方法。文章首先会描述我们App的启动过程,接下来会讨论一下我们应该怎样优化App的启动性能。最后,文章会阐述一下我们在启动优化过程中常见的问题,并且提出一些优化建议。

理解App的启动过程

App启动主要有三种状态,每种状态都会影响你的应用对用户的可见所需的时间:冷启动、温启动、热启动。热启动下,App会从头开始启动,温启动和冷启动应用会从后台切换到前台。我们建议你应该基于冷启动进行优化,这样做也可以改善热启动和热启动的性能。

为了更好的优化App启动性能,我们需要了解每一种状态下,系统和App级别内部是怎样工作的。

冷启动

冷启动是指应用从头开始启动:系统的进程还没有创建应用程序的进程。冷启动发生在比如设备启动后的首次启动应用程序,或者系统杀死了应用程序进程后。冷启动相比较于温启动和热启动,需要优化的工作更多挑战更大。

在冷启动开始开始,系统需要做三个主要的工作:

  1. 导入和启动App
  2. 启动之后立即显示一个空的Window窗体
  3. 创建App进程

一旦系统创建了App进程,App进程接下来会负责以下几个步骤的开始:

  1. 创建App对象
  2. 启动主进程
  3. 创建主Activity
  4. Inflating View
  5. 填充屏幕布局
  6. 执行初始化绘制

App进程完成初始化绘制后,会把当前main Activity替换为当前显示的背景窗口,这个时候用户就可以开始使用App了。

图1显示了系统和App进程两者之间是怎样进行交互的

img

图1 冷应用程序启动的重要部分的直观表示

图中所知,产生的性能问题主要集中在创建Applicaition和创建Activity中

Applicaition的创建

当你的Applicaition启动的时候,系统会创建一个空白的window窗口,直到应用第一次绘制完页面才会消失。绘制完之后,用户才可以开始使用App。

假如你对Application.onCreate()方法进行重载,系统会在应用程序对象上调用onCreate()方法。之后,应用程序会生成主线程(也称为UI线程),并通过创建main activcity来执行任务。

从这一点开始,系统和应用程序级别的流程将根据应用程序生命周期阶段进行。

Activity的创建

在Application 启动完成之后,App进程开始创建Activity,Activity的执行是按照以下几个步骤进行的:

  1. 初始化值
  2. 调用构造方法
  3. 调用生命周期回调方法,例如Activity.onCreate()等等

通常来说onCreate方法是开销最大的操作,因为在该方法中需要执行inflating views、初始化所需对象等等操作

热启动

热启动相比于冷启动来说,热启动更简单,花销更少。在热启动中,主要开销均来自于把Activity显示到前台来。如果应用程序的Activity仍然驻留在内存中,那么应用程序可以避免重复对象初始化,布局Inflating和渲染。

然后,在低内存情况下仍然可能会被回收掉,例如onTrimMemory()方法被回调,这个时候对象需要被重新创建。

热启动和冷启动的屏幕显示行为是一样:系统启动会也会先显示一个空白的屏幕,直到App完成渲染和Activity的显示。

温启动

温启动是冷启动的一个子集,同样的情况,它比热启动花销更少,温启动情况下,有许多潜在的状态可以被重用,例如:

  • 用户退出应用,但随后重新启动它。该进程可能还在继续运行,但应用程序必须通过调用onCreate()从头开始重新创建acitivty。
  • App的内存被系统回收情况下,用户重新启动App,App进程和Activity需要被重新创建。但是它们可以从onCreate方法保存的bundle中恢复。

检测并诊断问题

Android提供了几种方法可以知道你的App存在的一些问题。并且帮助你去诊断你的App。Android vitals能够提醒你出现的问题,并且提供一些诊断工具去帮助你。

Android vitals

Android vitals能够帮你提升App的性能并且能够通过Play Console提供警告,当你App满足一下几个点的时候,Android vitals会认为你的App时间过长:

  • 冷启动超过5s或者更长
  • 温启动2s或者更长
  • 热启动1.5s或者更长

有一个每日使用记录来记录App的启动时常。

Android vitals不会提供一个热启动的报告。关于Android vitals的更多信息,请访问Play Console 文档。

找出缓慢启动时间的原因

为了找出你App启动缓慢的原因,你需要跟踪applicaition启动指标。

显示的初始化时间

在Android4.4(API 19)或者更高,logcat包含一个包含名为Displayed的值的输出行,此值表示启动过程和完成在屏幕上绘制相应Activity之间所经过的时间。经过的时间包括以下事件序列:

  1. 启动App进程
  2. 初始化App对象
  3. 创建和初始化Activity
  4. Inflating 布局
  5. 第一次绘制应用

报告的格式大概如下:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms

如果您正在从命令行或终端中跟踪logcat输出,找出花费的时间是非常简单的。在Android Studio中查找已用时间,必须在logcat视图中禁用过滤器。禁用过滤器是必要的,因为该日志是系统级别的。

完成适当的设置后,您可以轻松搜索正确的关键字以查看时间。图2显示了如何禁用过滤器,以及在底部的第二行输出中显示了显示时间的logcat输出示例。

img

图二

logcat中显示的时间不是加载和显示所有资源的时间:它会不包括未在布局文件中引用的资源,它只会创建初始化对象所需要的一部分资源。排除这些无关资源是因为导入是一个联系的过程,只要不阻塞app初始化完成即可。(译者注:意思是这里显示的时间度量不是加载完所有资源,它只会按需加载)

有的时候Displayed标签会输出一个总时间,例如:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

这种情况下,第一个时间表示的是当前Activity绘制所花费的时间,total 时间代表的是从测量app启动开始时间,包括其它未显示在屏幕上的Activity时间。total time显示是为了显示单个Activity和总共启动时间的对比。

你也能够通过运行ADB Shell Activity Manager命令来显示app初始化时间,例如:

adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN

这里Displayed表现显示和前面一样,都是显示在logcat中。在logcat会显示如下:

Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete

其中-c-a参数可以对intent设置<category><action>

完整显示时间

使用reportFullyDrawn()方法来度量应用程序启动和完全显示所有资源和视图层次结构所用的时间。在app执行延迟加载的情况下,这可能很有用。在延迟加载中,应用程序不会阻止窗口的初始绘制,而是异步加载资源并更新视图层次结构。

如果由于延迟加载,应用程序的初始显示不包含所有资源,可以将所有资源和视图的完成加载和显示视为单独的度量标准:例如,您的UI可能已完全加载,并绘制了一些文本,但尚未显示从网络中拉取的图片资源。

为了解决这个问题,你需要手动调用reportFullyDrawn()这个方法让系统知道你的Activity是使用懒加载的方式。使用此方法时,logcat显示的值是从创建应用程序对象到调用reportFullyDrawn()的时间。这是logcat输出的一个例子:

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms

logcat中输出的是total time,前面显示的初始化时间已经讨论过了

如果App到显示时间比预想的要慢,可以继续尝试识别启动过程中的存在的瓶颈。

识别瓶颈

一个比较好的方式是使用Android studio提供的CPU性能分析工具,关于CPU性能分析工具更多的信息可以查看Inspect CPU Activity with CPU Profiler

您还可以通过跟踪Application和Activity的onCreate()方法来深入了解潜在的瓶颈。要了解内联跟踪,请参阅跟踪功能的文档以及Systrace 工具。

常见问题

本节讨论影响性能的几个常见问题,这些问题主要涉及初始化应用程序和活对象,以及屏幕的加载。

大型应用的初始化

当你对Application写入大量初始化或者其它一些工作的时候,会影响应用的启动性能。App需要浪费很多时间去执行子类复写的方法。有一些 初始化的工作不是必须的:例如为main Activity初始化一些并不需要的状态信息。

其它的一些问题包括在App初始化期间产生大量的需要GC的事件,或者磁盘I/O频繁读取。尤其是Dalvik运行时的垃圾收集; Art runtime同时执行垃圾收集,最大限度地减少操作的影响。

找出问题

可以使用tracing或者内联tracing的方式去找到问题所在。

方法 tracing

运行CPU Profiler工具查看能够调用你自定义的com.example.customApplication.onCreate  的 callApplicationOnCreate() 方法,如果该工具显示这些方法需要很长时间才能完成执行,那么您应该进一步探索以查看正在进行的工作。

Inline tracing

使用Inline tracing来找出可能出现的问题:

  • app中onCreate( 初始化方法
  • 使用app的全局单例对象
  • 可能发生的任何磁盘I / O,反序列化或循环的地方

解决问题

如果问题的产生的原因是因为密集的磁盘I/O造成的,解决方法就是仅仅加载必要的对象,其它对象通过懒加载的方式进行对象的初始化。例如,通过创建单例来替代创建静态对象,此外考虑使用像Dagger这样的依赖注入框架来创建对象,并在第一次注入它们时依赖它们。

大型Activity初始化

创建Activity通常需要大量高额开销,通常可以优化这创建过程实现性能的改进。这些优化点包括:

  • 优化复杂布局
  • 减少阻塞页面的磁盘和网络I/O
  • bitmaps加载优化
  • 栅格化VectorDrawable 对象
  • 初始化其它Activity的子系统

找出问题

可以通过tracing 或者 inline tracing来提供有用的帮助

方法tracing

通过CPU profiler工具来分析自定义Application的子类onCreate()方法。

如果该工具表明你的方法需要执行很长的时间,需要进一步找出时间开销的分布,从而解理解问题是怎么产生的。

Inline tracing

使用Inline tracing来找出可能出现的问题:

  • app中onCreate( 初始化方法
  • 使用app的全局单例对象
  • 可能发生的任何磁盘I / O,反序列化或循环的地方

解决问题

产生性能问题的原因有很多,但是通常来说有2个主要问题。

  • 复杂多层次的布局,需要App花更多的时间来加载它。2个步骤能够解决这个问题

    • 通过减少冗余或嵌套布局来减少视图层次结构。
    • 不需要开始就显示的布局,可以通过ViewStup对象来加载
  • 在主线程上进行所有资源初始化也会降低启动速度。可以按如下方式解决此问题:

    • 通过子线程进行资源的初始化工作
    • 可以使用类似于占位的方式来显示布局,等对应资源加载出来后再显示上去。

App启动主题

可以通过自定义主题的方式来启动App,这样可以隐藏Activity显示缓慢的问题。

比较常见的方式就是使用windowDisablePreview主题属性来替代App启动时候的空白页面。但是,与普通主题App相比,此方法可以导致更长的启动时间。此外它会强制用户在活动启动时等待,并且不能知道当前程序是否运行正常。

找出问题

您通常可以在用户启动应用时观察响应缓慢来找出问题。在这种情况下屏幕似乎被冻结,或者已经停止响应输入。

解决问题

我们建议您不要禁用预览窗口,而是遵循常见的Material Design模式。您可以使用Activity的windowBackground主题属性为启动活动提供简单的自定义drawable。

例如,您可以创建一个新的可绘制文件,并从布局XML和应用程序清单文件中引用它,如下所示:

layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
  <!-- The background color, preferably the same as your normal theme -->
  <item android:drawable="@android:color/white"/>
  <!-- Your product logo - 144dp color version of your app icon -->
  <item>
    <bitmap
      android:src="@drawable/product_logo_144dp"
      android:gravity="center"/>
  </item>
</layer-list>

Manifest file:

<Activity ...
android:theme="@style/AppTheme.Launcher" />

在Activity中通过调用``setTheme(R.style.AppTheme)方法来设置当前页面的主题,

class MyMainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Make sure this is before calling super.onCreate
        setTheme(R.style.Theme_MyApp)
        super.onCreate(savedInstanceState)
        // ...
    }
}