用户希望应用程序能够快速响应并加载。 一个启动速度慢的应用程序不符合这个期望,可能会令用户失望。 这种糟糕的体验可能会导致用户在应用商店中对您的应用进行糟糕的评价,甚至完全放弃您的应用。
本文档提供的信息可帮助您优化应用的启动时间。 它首先解释启动过程的内部。 接下来,讨论如何配置启动性能。 最后,它描述了一些常见的启动时间问题,并提供了一些关于如何解决它们的提示
Launch Internals
应用程序启动可以发生在三种状态之一,每种状态都会影响您的应用程序对用户可见的时间长度:冷启动,热启动和温热启动。 冷启动,你的应用程序从头开始。 在其他的状态,系统需要将应用程序从后台引向前台。 我们建议您始终根据冷启动的情况进行优化。 这样做可以改善热启动和温热启动的性能。
为了优化您的应用程序以实现快速启动,常常需要了解系统和应用程序级别发生了什么以及它们如何交互
冷启动
冷启动是指应用程序从头开始:系统的进程在开始之前还没有创建应用程序的进程。 冷启动发生在您的应用程序自启动设备以来第一次启动的情况下,或由于系统杀死应用程序。 这种类型的启动在缩短启动时间方面提出了最大的挑战,因为系统和应用比其他启动状态有更多的工作要做。
在冷启动开始时,系统有三项任务。 这些任务是:
- 加载并启动应用。
- 启动后立即显示应用程序的空白开始窗口。
- 创建应用进程。
只要系统创建应用程序进程,应用程序进程就负责下一个阶段。 这些阶段是:
- 创建该应用对象
- 启动主线程
- 创建主activity
- 填充views
- 铺设屏幕
执行初始绘制
一旦应用程序完成第一次绘制,系统进程就会将当前显示的背景窗口替换为主要活动。 此时,用户可以开始使用该应用程序。
性能问题可能在创建应用程序和创建activity时出现
Application creation
当您的应用程序启动时,空白的起始窗口将保留在屏幕上,直到系统第一次完成绘制应用程序。 此时,系统进程为应用程序换掉了启动窗口,允许用户开始与应用程序进行交互
如果您在自己的应用程序中重载了Application.oncreate(),系统将调用您的应用程序对象上的onCreate()方法。 之后,应用程序会生成主线程,也称为UI线程,并执行创建主Activity的任务。
从这一点来看,系统和应用程序级别的进程按照应用程序生命周期阶段进行。
Activity的创建
应用程序进程创建您的活动后,活动执行以下操作:
- 初始化值
- 调用构造方法
- 调用回调方法,按照activity的生命周期
通常情况下,onCreate()方法对加载时间影响最大,因为它执行的开销最高:加载和扩充视图,并初始化活动运行所需的对象
热启动
应用程序的热启动比冷启动更简单,开销更低。 在一个温暖的开始,所有的系统都会把你的activity带到前台。 如果您的所有应用程序的活动仍驻留在内存中,则应用程序可以避免重复对象初始化,布局填充和渲染
但是,如果某些内存已被清除以响应内存调整事件(如onTrimMemory()),则将响应热启动事件重新创建这些对象。
热启动显示与冷启动场景相同的屏幕行为:系统进程显示空白屏幕,直到应用完成activity的渲染。
Lukewarm start
一个lukewarm start包含了在冷启动期间发生的一些操作子集; 与此同时,它代表的不是一个热启动的开始。 有许多潜在的状态可以被认为是温和的开始。 例如:
- 用户退出应用程序,但重新启动它。 该进程可能会继续运行,但应用程序必须通过调用onCreate()从头开始重新创建活动
- 系统从内存中清除您的应用程序,然后用户重新启动它。 进程和Activity需要重新启动,但是任务可以从saved instance state bundle 传递给onCreate()
分析启动性能
为了正确地诊断开始时间性能,您可以跟踪显示启动应用程序所需时间
要重现用户体验,请确保以非可调试模式对应用进行配置。 可调试模式启用调试功能,导致启动时间非典型的用户体验。
初始时间显示
从Android 4.4(API级别19),logcat包含一个输出行,其中包含一个名为Displayed的值。 该值表示启动过程和完成在屏幕上绘制相应活动之间所经过的时间量。 经过的时间包括以下一系列事件:
- 启动进程
- 初始对象
- 创建并初始化activity
- 填充布局
- 在第一时间绘制你的应用
报告的日志行与以下示例类似:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
如果您正在从命令行或终端跟踪logcat输出,则查找已用时间很简单。 要在Android Studio中查找已用时间,您必须在logcat视图中禁用筛选器。 禁用过滤器是必要的,因为系统服务器,而不是应用程序本身,服务于这个日志。
一旦你做了适当的设置,你可以轻松地搜索正确的术语来查看时间。 图2显示了如何禁用过滤器,并在底部输出的第二行显示了Displayed时间的logcat输出示例。
Logcat输出中的“显示”度量不一定会捕获所有资源加载和显示之前的时间量:它会遗漏布局文件中未引用的资源或应用程序作为对象初始化的一部分创建的资源。 它排除了这些资源,因为加载它们是一个内嵌的过程,并且不会阻止应用程序的初始显示
您也可以使用ADB Shell活动管理器命令运行您的应用程序来测量初始显示的时间。 这是一个例子:
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指定和
完全呈现的时间
您可以使用reportFullyDrawn()方法来测量应用程序启动和完成显示所有资源和视图层次之间的已用时间。 在应用执行延迟加载的情况下,这可能很有价值。 在延迟加载中,应用程序不会阻止窗口的初始绘制,而是异步加载资源并更新视图层次结构。
如果由于延迟加载,应用程序的初始显示不包含所有资源,则可以将所有资源和视图的加载和显示视为一个单独的度量标准:例如,您的UI可能已完全加载,绘制了一些文本, 但还没有显示应用程序必须从网络中获取的图像。
为了解决这个问题,您可以手动调用reportFullyDrawn()让系统知道您的活动已经完成了延迟加载。 使用此方法时,logcat显示的值是从创建应用程序对象到调用reportFullyDrawn()的时间。 这里是一个logcat输出的例子:
system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
logcat输出有时会包含一个总时间,正如在“初始显示时间”中所述
如果你知道你的显示时间比你想要的慢,你可以继续尝试确定启动过程中的瓶颈
查找瓶颈的两个好方法是Android Studio方法跟踪工具和内联跟踪。 要了解Method Tracer,请参阅工具文档。
如果您无法访问Method Tracer工具,或无法在正确的时间启动该工具以获取日志信息,则可以通过在应用程序和活动的onCreate()方法中进行内嵌跟踪来获得类似的洞察。 要了解内联跟踪,请参阅跟踪功能参考文档以及Systrace工具
常见问题
本节讨论经常影响应用程序启动性能的几个问题。 这些问题主要涉及初始化应用程序和活动对象,以及加载屏幕
沉重的应用初始化
当您的代码覆盖Application对象时,启动性能会受到影响,并在初始化该对象时执行繁重的工作或复杂的逻辑。 如果您的应用程序子类执行不需要完成的初始化,您的应用程序可能会浪费时间在启动过程中。 一些初始化可能是完全不必要的:例如,当应用程序实际启动以响应意图时,初始化主要活动的状态信息。 意图是,应用程序只使用以前初始化的状态数据的一个子集。
应用程序初始化过程中的其他问题包括垃圾收集事件的影响或数量众多,或磁盘I / O与初始化同时发生,进一步阻止初始化过程。 垃圾收集尤其是Dalvik运行时的一个考虑因素; Art运行时同时执行垃圾收集,最大限度地减少操作的影响
诊断问题
您可以使用方法跟踪或内联跟踪来尝试诊断问题。
方法追踪
运行Method Tracer工具显示callApplicationOnCreate()方法最终调用com.example.customApplication.onCreate方法。 如果该工具显示这些方法需要很长时间才能完成执行,那么您应该进一步研究以查看正在进行的工作。
内联追踪
使用内联追踪来调查可能的罪魁祸首,包括:
- 你的应用初始onCrate() 方法
- 全局的单例对象初始
任何磁盘I / O,反序列化,或瓶颈期间可能发生的紧密循环
解决问题
无论问题出在不必要的初始化还是磁盘I / O,解决方案都会调用延迟初始化对象:只初始化那些立即需要的对象。 例如,不是创建全局静态对象,而是移动到单例模式,应用程序只在第一次访问对象时创建对象。 另外,考虑使用像Dagger这样的依赖注入框架来创建对象,并且依赖关系是当它们被首次注入时繁重的activity 初始化
activity创建通常需要大量的高开销工作。 通常,有机会优化这项工作来实现性能改进。 这些常见问题包括:填充庞大或者复杂的布局
- 阻塞磁盘的屏幕绘图或网络I / O
- 加载并解码图片
- 栅格化VectorDrawable对象
- 在activity初始化其他子系统
解决问题重点内容
在这种情况下,方法跟踪和内联跟踪也是有用的
Method tracing
当运行Method Tracer工具时,特定的区域将关注于您的应用程序的Application子类的构造函数和com.example.custom的Application.onCreate()方法。
如果该工具显示这些方法需要很长时间才能完成执行,那么您应该进一步研究以查看正在进行的工作。
Inline tracing
使用内联追踪来调查可能的罪魁祸首,包括:
- 你的应用初始onCreate() 方法
- 全局的单例对象初始
任何磁盘I / O,反序列化,或瓶颈期间可能发生的紧密循环
解决问题
有很多潜在的瓶颈,但是两个常见的问题和解决方法如下视图层次越大,应用所需的时间就越多。 你可以采取两个步骤来解决这个问题
- 通过减少冗余或嵌套布局来平坦您的视图层次结构
- 不要在启动时填充不需要显示的部分UI。使用ViewStub对象作为应用程序可以在更合适的时间填充的子层次结构的占位符来代替。
在主线程上完成所有的资源初始化操作也会减慢启动速度。 你可以解决这个问题如下:
- 把所有可以通过懒加载的初始资源放到不同的线程去执行
- 允许应用加载展示你的视图,并且可以稍后跟新视觉属性通过bitmaps 和其他资源。
主题启动屏幕
您可能希望主题化您的应用的加载体验,以便应用的启动屏幕与应用的其余部分在主题上保持一致,而不是与系统主题一致。 这样做可以隐藏一个缓慢的activity 启动。
实现主题启动屏幕的常用方法是使用windowDisablePreview主题属性来关闭启动应用程序时系统进程绘制的初始空白屏幕。 但是,这种方法会导致比不抑制预览窗口的应用程序更长的启动时间。 此外,它强制用户在活动启动时等待而没有任何反馈,使他们不知道该应用程序是否运行正常。
诊断问题
您可以通过观察用户启动应用程序时的慢速响应来经常诊断此问题。 在这种情况下,屏幕可能会被冻结,或者停止响应输入
解决问题
我们建议您不要禁用预览窗口,而要遵循常见的Material Design模式。 您可以使用该活动的windowBackground主题属性为开始活动提供一个简单的自定义绘图。
例如,您可以创建一个新的可绘制文件,并从布局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):
public class MyMainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {// Make sure this is before calling super.onCreatesetTheme(R.style.Theme_MyApp);super.onCreate(savedInstanceState);// ...}
}