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