原文地址:https://developer.android.com/topic/performance/vitals/launch-time
用户希望应用程序能够快速响应并快速加载。启动时间较慢的应用程序无法满足此预期,并且可能会令用户失望。这种糟糕的体验可能会导致用户在Play商店中对您的应用评分不佳,甚至完全放弃您的应用。
本文档提供的信息可帮助您优化应用的启动时间。它首先解释了启动过程的内部结构。接下来,它将讨论如何分析启动性能。最后,它描述了一些常见的启动时间问题,并提供了一些如何解决它们的提示。
了解app-start内部
应用程序启动可以在三种状态之一中进行,每种状态都会影响应用程序对用户可见所需的时间:冷启动(cold start),温启动(warn start)或热启动(hot start)。在冷启动时,您的应用程序从头开始。在其他状态下,系统需要将正在运行的应用程序从后台运行到前台。我们建议您始终根据冷启动的假设进行优化。这样做也可以改善温启动和热启动的性能。
为了优化您的应用程序以实现快速启动,了解系统和应用程序级别发生的情况以及它们在每种状态下的交互方式非常有用。
冷启动(Cold start)
冷启动是指应用程序从头开始:系统的进程在此开始之前没有创建应用程序的进程。冷启动发生在诸如自设备启动以来首次启动应用程序或自系统终止应用程序以来。这种类型的启动在最小启动时间方面提出了最大的挑战,因为系统和应用程序比其他启动状态有更多的工作要做。
在冷启动开始时,系统有三个任务。这些任务是:
- 加载并启动应用程序。
- 启动后立即显示应用程序的空白启动窗口。
- 创建应用程序进程。
一旦系统创建应用程序进程,应用程序进程就会负责下一阶段。这些阶段是:
- 创建应用程序对象。
- 启动主线程。
- 创建主要Activity。
- 绘制视图(View)。
- 布局屏幕。
- 执行初始化绘制。
应用程序进程完成第一次绘制后,系统进程会交换当前显示的背景窗口,将其替换为主活动。此时,用户可以开始使用该应用程序。
图1显示了系统和应用程序如何处理彼此之间的工作。
[站外图片上传中...(image-3e6e31-1535445599353)]
图1.冷启动应用程序重要部分的直观表示。
在创建应用程序和创建活动期间可能会出现性能问题。
Application creation
当您的应用程序启动时,空白的启动窗口将保留在屏幕上,直到系统首次完成绘制应用程序。此时,系统进程会交换应用程序的启动窗口,允许用户开始与应用程序进行交互。
如果您在自己的应用程序中重载了Application.onCreate()
,系统会在您的应用程序对象上调用onCreate()
方法。之后,应用程序会生成主线程(也称为UI线程),并通过创建MainActivity
来执行任务。
从这一点开始,系统和应用程序级别的流程将根据应用程序生命周期阶段进行。
Activity creation
应用程序进程创建Activity
后,Activity
将执行以下操作:
- 初始化值。
- 调用构造函数。
- 调用适合于
Activity
当前生命周期状态的回调方法,例如Activity.onCreate()
。
通常,onCreate()
方法对加载时间的影响最大,因为它以最高的开销执行工作:加载并绘制视图,以及初始化Activity
运行所需的对象。
热启动(Hot start)
应用程序的热启动比冷启动更简单,开销更低。在一次热启动中,所有系统都会将您的Activity
带到前台。如果应用程序的Activity
仍然驻留在内存中,那么应用程序可以避免重复对象初始化,布局绘制和渲染。
但是,如果为了响应内存t调整(例如onTrimMemory()
)而清除了某些内存,则需要重新创建这些对象以响应热启动事件。
热启动显示与冷启动方案相同的屏幕行为:系统进程显示空白屏幕,直到应用程序完成呈现活动。
温启动(Warm start)
温启动包括冷启动期间发生的一些操作子集;同时,它比热启动表示更少的开销。有很多潜在的状态可以被认为是温暖的开始。例如:
用户退出您的应用,但随后重新启动它。该过程可能已继续运行,但应用程序必须通过调用onCreate()
从头开始重新创建Activity。
统将您的应用程序从内存中逐出,然后用户重新启动它。需要重新启动进程和活动,但是任务可以从传递给onCreate()
的已保存实例状态包中获取。
检测并诊断问题
Android提供了多种方法让您知道您的应用存在问题,并帮助您进行诊断。 Android vitals可以提醒您问题正在发生,诊断工具可以帮助您诊断问题。
Android vitals
通过Play控制台,当您的应用启动时间过长时,Android vitals可以帮助您提高应用的性能。当应用程序出现以下情况的时候,Android vitals认为你的应用程序的启动时间过长:
- 冷启动需要5秒或更长时间。
- 温启动需要2秒或更长时间。
- 热启动需要1.5秒或更长时间。
每日会话是指您的应用使用的日期。
Android vitals不报告热启动的数据。有关Google Play如何收集Android重要数据的信息,请参阅Play控制台文档。
诊断较慢的启动时间
为了正确诊断开始时间性能,您可以跟踪指示应用程序启动所需时间的指标。
初始显示的时间
在Android 4.4(API级别19)及更高版本中,logcat包含一个包含名为Displayed的值的输出行。此值表示启动过程和完成在屏幕上绘制相应活动之间所经过的时间量。经过的时间包括以下事件序列:
- 启动过程。
- 初始化对象。
- 创建并初始化Activity。
- 填充布局。
- 首次绘制您的应用程序。
报告的日志行类似于以下示例:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
如果您正在从命令行或终端中跟踪logcat输出,则查找已用时间非常简单。要在Android Studio中查找已用时间,必须在logcat视图中禁用过滤器。禁用过滤器是必要的,因为系统服务器而不是应用程序本身服务于此日志。
完成适当的设置后,您可以轻松搜索正确的术语以查看时间。图2显示了如何禁用过滤器,以及在底部的第二行输出中显示了显示时间的logcat输出示例。
[站外图片上传中...(image-cc68eb-1535445599353)]
logcat输出中的Displayed
度量标准不一定捕获加载和显示所有资源之前的时间量:它会遗漏布局文件中未引用的资源或应用程序在对象初始化过程中创建的资源。它排除了这些资源,因为加载它们是一个内联过程,并不会阻止应用程序的初始显示。
有时,logcat输出中的Displayed
行包含总时间的附加字段。例如:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)
在这种情况下,第一次测量仅适用于首次绘制的活动。总时间测量从应用程序进程开始时开始,可能包括另一个首先启动但未向屏幕显示任何内容的活动。仅当单个活动与总启动时间之间存在差异时,才会显示总时间测量值
您还可以使用ADB Shell Activity Manager命令运行应用程序来测量初始显示的时间。例子:
adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN
显示的度量标准与以前一样出现在logcat输出中。您的终端窗口还应显示以下内容:
Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete
-c和-a参数是可选的,允许您为intent指定<category>和<action>。
其他方面
您可以使用reportFullyDrawn()
方法来度量应用程序启动和完全显示所有资源和视图层次结构之间所用的时间。在app执行延迟加载的情况下,这可能很有用。在延迟加载中,应用程序不会阻止窗口的初始绘制,而是异步加载资源并更新视图层次结构。
如果由于延迟加载,应用程序的初始显示不包含所有资源,您可能会将所有资源和视图的完成加载和显示视为单独的度量标准:例如,您的UI可能已完全加载,并绘制了一些文本,但尚未显示应用必须从网络中获取的图像。
要解决此问题,您可以手动调用reportFullyDrawn()
,让系统知道您的活动已完成其延迟加载。使用此方法时,logcat显示的值是从创建应用程序对象到调用reportFullyDrawn()
的时间。这是logcat输出的一个例子:
system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
logcat输出有时包括总时间,如初始显示时间中所述
如果您了解到显示时间比您想要的慢,您可以继续去找启动过程中的瓶颈。
找瓶颈
寻找瓶颈的两种好方法是Android Studio的Method Tracer工具和inline tracing。要了解Method Tracer,请参阅该工具的文档。
如果您无法访问Method Tracer工具,或者无法在正确的时间启动该工具以获取日志信息,则可以通过应用程序和Activity
的onCreate()
方法内联跟踪获得类似的洞察力。要了解内联跟踪,请参阅跟踪功能的参考文档以及Systrace工具。
注意常见问题
本节讨论通常会影响应用程序启动性能的几个问题。这些问题主要涉及初始化应用程序和Activity对象,以及加载屏幕。
重Application初始化
当您的代码覆盖Application对象时,并且在初始化该对象时执行繁重的工作或复杂的逻辑,启动性能将会受到影响。如果您的应用程序执行不需要执行的初始化,则您的应用程序可能会在启动期间浪费时间。某些初始化可能完全没有必要:例如,初始化Main Activity的状态信息,当应用程序实际启动以响应意图时。根据意图,应用程序仅使用先前初始化的状态数据的子集。
应用程序初始化期间的其他有影响的操作包括数量众多的垃圾收集事件,或者与初始化同时发生的磁盘I / O,进一步阻止了初始化过程。垃圾收集是Dalvik运行时的考虑因素; Art运行时同时执行垃圾收集,最大限度地减少操作的影响。
诊断问题
您可以使用方法跟踪或内联跟踪来尝试诊断问题。
方法跟踪(Method tracing)
运行Method Tracer工具会发现callApplicationOnCreate()方法最终会调用com.example.customApplication.onCreate方法。如果该工具显示这些方法需要很长时间才能完成执行,那么您应该进一步探索以查看正在进行的工作。
内联跟踪(Inline tracing)
使用内联跟踪来调查可能的罪魁祸首,包括:
- 您应用的初始
onCreate()
函数。 - 您的应用初始化的任何全局单例对象。
- 可能发生的任何磁盘I / O,反序列化或紧密循环。
解决问题的方法
无论问题在于不必要的初始化还是磁盘I / O,解决方案都会调用延迟初始化对象:仅初始化那些立即需要的对象。例如,不是创建全局静态对象,而是移动到单例模式,其中应用程序仅在第一次访问对象时初始化对象。此外,考虑使用像Dagger这样的依赖注入框架来创建对象,并在第一次注入它们时依赖它们。
重Activity初始化
创建Activity通常需要大量高额开销。通常,有机会优化这项工作以实现性能改进。这些常见问题包括:
- 填充大型或复杂的布局。
- 阻塞磁盘上的屏幕绘制或网络I / O。
- 加载和解码bitmap。
- 栅格化
VectorDrawable
对象。 - 初始化Activity的其他子系统。
诊断问题
在这种情况下,方法跟踪和内联跟踪都可以证明是有用的。
方法跟踪(Method tracing)
运行Method Tracer工具时,需要关注应用程序的Application子类构造函数和com.example.customApplication.onCreate()方法的特定区域。
如果该工具显示这些方法需要很长时间才能完成执行,那么您应该进一步探索以查看正在进行的工作。
内联跟踪(Inline tracing)
使用内联跟踪来调查可能的罪魁祸首,包括:
- 您应用的初始
onCreate()
函数。 - 您的应用初始化的任何全局单例对象。
- 可能发生的任何磁盘I / O,反序列化或紧密循环。
解决问题的方法
存在许多潜在的瓶颈,但两个常见问题和补救措施如下:
- 视图层次结构越大,应用程序对其进行填充的时间就越长。您可以采取以下两个步骤来解决此问题:
- 通过减少冗余或嵌套布局来展平视图层次结构。
- 不填充在启动期间不需要显示UI的部分内容。相反,使用
ViewStub
对象作为子层次结构的占位符,应用程序可以在更合适的时间实例。
- 在主线程上进行所有资源初始化也会降低启动速度。您可以按如下方式解决此问题:
- 迁移所有资源初始化,以便应用程序可以在另一个线程上懒惰地执行它。
- 允许应用加载并显示您的视图,然后更新依赖于位图和其他资源的可视属性。
启动屏幕主题
您可能希望主题化应用程序的加载体验,以便应用程序的启动屏幕与应用程序的其余部分主题一致,而不是系统主题。这样做可以隐藏缓慢的Activity启动。
实现主题启动屏幕的常用方法是使用windowDisablePreview
主题属性关闭系统进程在启动应用程序时绘制的初始空白屏幕。但是,与不抑制预览窗口的应用程序相比,此方法可以导致更长的启动时间。此外,它会强制用户在活动启动时等待没有反馈,让他们想知道应用程序是否正常运行。
诊断问题
您通常可以在用户启动应用时观察响应缓慢来诊断此问题。在这种情况下,屏幕似乎被冻结,或者已经停止响应输入。
解决问题
我们建议您不要禁用预览窗口,而是遵循常见的Material Design模式。您可以使用activity的windowBackground主题属性为启动活动提供简单的自定义drawable。
例如,您可以创建一个新的可绘制文件,并从布局XML和应用程序清单文件中引用它,如下所示
布局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);
// ...
}
}