用户希望Android App可以很快地响应和快速被加载。一个启动很慢的App并不满足用户的期望并且会让使用者感到失望。这种比较差的体验会导致用户在应用市场给你的App比较低的评分,甚至完全弃用你的应用。

这篇文章将会帮助你去优化你应用的启动时间。一开始,它将讲解启动过程的内部构建;接下来,它将讨论如何描述启动流程;最后,它描述了一些常见的启动问题,并且提供了一些如何解决它们的提示。

1. 启动的内部构建

App启动发生在如下三个状态之一,每个状态都会影响你的应用对用户可见所经历的时间长短:冷启动,热启动和微温启动。在冷启动中,你的应用从头开始启动。在其他状态里,系统需要把应用从后台带到前台。我们建议你在冷启动的设想的基础上进行优化。如此做可以同样提升热启动和温启动的性能。

对于为了快速启动优化你的应用,理解在启动的过程中在各个状态系统和应用层级发生了什么,它们之间是如何交互的是非常重要的。

冷启动

冷启动指的是一个应用从头开始启动:系统进程直到冷启动才开始创建应用进程。冷启动在如下场景发生:你的应用在设备启动后第一次被启动,或者在应用被系统杀死后再启动应用。这种启动对减少启动时间而言,提出了最大的挑战,因为系统和应用在冷启动状态要做的工作比其他启动状态多。

在冷启动的开始,系统有三个任务。这些任务是:

  • 加载和启动应用。
  • 在启动后立刻为应用展示 一个空白启动窗口。
  • 创建应用进程。
    一旦系统创建了应用进程,应用进程将负责下一个阶段。这些阶段是:
  • 创建应用对象。
  • 启动主线程。
  • 创建主activity。
  • 填充视图(Inflating views)。
  • 屏幕布局。
  • 执行初始绘画。

一旦应用完成了第一次绘制,系统进程将会替换当前显示的背景窗口,而用主Activity去替换它。这个时候,用户可以开始使用应用。

图1展示了系统进程和应用进程如何互相切换工作。

instrument android 速度 android performance_初始化

应用创建

当你的应用启动的时候,空白的启动窗口将会停留在屏幕上直到系统在第一次结束绘制应用。这个时候,系统进程将会为你的应用替换启动窗口,允许用户去与应用进行交互。

如果你重载了Application.oncreate(),应用将通过调用这个方法去启动。在这之后,应用将会产生主线程,也就是UI线程,并且主线程将会创建你的主Activity。

从这个时候开始,系统层进程和应用层进程按照应用生命周期阶段执行。

Activity创建

在应用进程创建了你的Activity之后,应用将会执行如下操作:

  • 初始化变量。
  • 调用构造函数。
  • 调用回调函数,例如Activity.onCreate(),对应Activity的当前生命周期状态。

代表地,onCreate()方法对加载时间有最大的影响,因为它执行了最高开销的工作:加载和填充view,并且初始化了供Activity运行的对象。

热启动

热启动与冷启动相比更加简单和低开销。在热启动里,系统所做的就是把你的应用带到前台。如果你的应用的所有Activity在内存里都是存活的,应用可以避免重复的对象初始化,布局加载和填充。

但是,如果一些内存由于像 onTrimMemory()这样的内存回收事件而被回收,这些对象将由于热启动事件而被重建。

热启动展示了与冷启动相同的屏幕显示:系统进程展示了一个空白的屏幕,直到应用已经结束渲染当前Activity。

微温启动

微温启动包含了一些发生在冷启动的操作;同时,它比热启动的开销要小。有许多可能的状态可以被称为温启动状态,例如:

  • 用户退出你的应用,但是随后重新启动它。进程有可能会继续运行,但是应用将会通过onCreate方法重头重新创建Activity。
  • 系统把你的应用从内存中清除,然后用户重新启动它。进程和Activity将会被重新启动,但是启动速度将会从传递进onCreate方法的已保存实例的状态集中得到加快。

2.描述启动性能

为了准确地诊断启动时间性能,你可以以启动应用所需的时间长短为标准。

初始绘制所需的时间(Time to initial display)

从Android 4.4版本开始, logcat包括了一个输出,包含一个值为Displayed。这个值表示了启动进程和结束绘制相关Activity中间所需的时间。花费的时间包含如下部分:

  • 启动进程。
  • 初始化对象。
  • 创建和初始化Activity。
  • 填充布局。
  • 第一次绘制你的应用。

相关的log与下面的例子类似:

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

如果你在命令行或者在一个终端追踪logcat,找到上面运行的时间是直截了当的。为了在Android Studio中找到运行时间,你必须关掉你logcat 视图里的过滤条件。关掉过滤条件是必要的,因为是系统服务器而不是应用本身提供了这个log。

一旦你正确设置了log,你可以轻易地找到运行时间。

图2展示了如何关掉过滤功能,并且在logcat的倒数第二行,展示了我们所需的Displayed值也就是运行时间。

instrument android 速度 android performance_加载_02

在logcat输出的Displayed值不一定捕获所有资源被加载和展示所需的启动时间:它没有包含未在布局中引用的资源和作为对象初始化被应用创建的资源。它之所以排除这些资源是因为加载它们的是内联进程,这样做不会阻塞应用的初始绘制。

全部展示所需的时间(Time to full display)

你可以使用reportFullyDrawn() 方法去测量在应用启动和完全展示所有的资源和布局层级之间所需的运行时间。当应用在执行懒加载的时候这个时间值将会很有价值。在懒加载中,应用不阻塞窗口的初次绘制,而是去异步加载资源和视图层级。

如果,由于懒加载,一个应用的初始绘制并不包括所有的资源,你有可能把完全加载所有的资源和视图看作一种不同的衡量标准:例如,你的UI有可能完全加载了一些文本,但是还没有展示从网络上抓取的图片。

为了解决如上的问题,你可以手动调用reportFullyDrawn() 方法去让系统知道你的Activity已经结束了懒加载。当你使用了这个方法,这个值指的是应用程序对象的创建和你调用这个方法的地方之间所用的运行时间。

如果你发现你的展示所用的时间比你想的要慢,你可以继续尝试去找到启动过程中导致如此的瓶颈。

找出瓶颈

两种寻找瓶颈的好的方法是Android Studio的方法追踪工具和内联追踪。你可以在这里学习方法追踪器。

如果你不能使用方法追踪工具,或者不能通过log去抓取相应的信息,你可以通过你的应用和ActivityonCreate() 方法内部的内联追踪去获取相应的信息。点击如下链接去学习内联追踪:Trace方法和系统调用跟踪提供器工具。

3.常见的问题

这一部分讨论了几个常见的会影响启动性能的问题。这些问题主要涉及初始化应用和Activity对象,
已经屏幕内容的加载。

繁重的应用初始化

当你的代码重载了Application对象,并且在初始化对象时执行了繁重的工作和复杂的逻辑,启动性能会受到影响。如果你的应用子类执行了还不需要立刻做的初始化,你的应用可能会在启动的过程中浪费时间。有些初始化可能时完全不必要的:例如,当应用实际上已经开始在回应一个intent的时候,为主Activity进行状态信息的初始化就是不必要的。根据这个intent,应用可能只使用前一个初始化状态数据的子集。

其他在应用初始化过程中的问题包括大量的垃圾回收活动,或者与初始化操作同时发生的磁盘 I/O 操作,都进一步阻碍了初始化进程。 Dalvik 运行时对于垃圾回收是有特殊优化的;而 Art 运行时则同时执行垃圾回收,使这个操作的影响降到最低。

诊断问题

你可以使用 或者内联追踪去尝试诊断问题。

方法追踪

运行方法追踪工具揭示了callApplicationOnCreate() 方法最终调用了你的 com.example.customApplication.onCreate方法。如果工具展示了这些方法花费了很多时间去执行,你应该去研究在这个方法里做了哪些工作。

内联追踪

使用内联追踪去调查可能导致启动慢的原因:

  • 你的应用的初始onCreate()方法。
  • 任何全局单例对象。
  • 任何I/O操作,还原序列化或者在瓶颈期间密集的循环。
问题的解决方式

有许多潜在的瓶颈影响启动速度,但是两个常见的问题和相应的解决方式如下:

  • 你的视图层级越大,你的应用需要花费更多的时间去填充它。两个步骤可以解决这个问题:
  • 通过减少多余的和内嵌的布局,简化你的视图层级。
  • 在启动期间不需要展示给用户的布局暂时不要进行绘制填充。用户使用ViewStub对象替代父层级,这样可以在合适的时候再对这样的布局进行绘制。
  • 在主线程里初始化你所有的资源也会减慢启动的速度。你可以如下处理这种问题:
  • 把资源初始化放在非主线程上执行,以便进行懒加载。
  • 允许应用去加载和展示你的视图,随后更新依赖于 bitmap和其他资源的可视部分。

给启动屏幕添加主题(Themed launch screens)

你有可能希望给你的应用加载过程添加主题,以便应用的启动屏的主题风格与应用的其它部分相符,而不是简单的使用系统主题。如此做可以隐藏很慢的Activity启动现象。

一个常见的方式去实现自定义启动屏幕主题是:使用windowDisablePreview主题属性去关掉一开始的白屏,这个白屏就是系统线程在初始化App的时候绘制的。但是,但是这种方法会导致更长的启动时间,相较没有关掉预加载白屏的应用。同样地,当应用启动的时候,它强迫用户在没有任何反馈的情况下等待,这会让用户对应用是否正常运行干到疑惑。

诊断问题

你可以通过当用户启动你的应用时是否有很长的应答时间来诊断这个问题。在这种情况下,屏幕会看上去像是冻住了,或者停止了对输入的响应。

问题的解决方法

我们的建议是:你可以遵从常见的Material Design 样式,而不是去禁用预加载屏幕展示。你可以使用Activity的windowBackground属性去为启动Activity提供一个简单的自定义图片。

例如:你可以创建一个新的图片文件,并且用layout文件和manifest文件引用它,如下:

Layout 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 文件:

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

过渡回到正常的主题的最简单的方法是在super.onCreate() 和 setContentView()之前调用setTheme(R.style.AppTheme) 方法。

public class MyMainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // Make sure this is before calling super.onCreate
    setTheme(R.style.Theme_MyApp);
    super.onCreate(savedInstanceState);
    // ...
  }
}

英文原文:Launch-Time Performance

最近正好自己在研究如何优化App启动的性能,正好看到Android官方有这样一篇文章,出于兴趣故个人翻译了一下。因为水平可能有限,有些地方可能翻译的比较拗口,如果大家看到了请见谅,有什么问题请告知我,我这边会做相应的修改~