启动时间性能
用户期望应用程序响应快,加载快。 启动时间较慢的应用程序不满足此预期,可能会令用户失望。 这种糟糕的体验可能会导致用户在Play商店上评价您的应用,或者甚至完全放弃您的应用。
本文档提供了有助于您优化应用程序启动时间的信息。 它首先解释launch过程的内部。 接下来,它讨论如何配置启动性能。最后,它描述了一些常见的启动时间问题,并提供了一些提示如何解决。
启动内部
应用程序启动可能发生在三个状态之一个,每个状态都会影响应用程序对用户可见所需的时间:冷启动,热启动和Lukewarm start。 在冷启动,您的应用程序从头开始。 在其他状态下,系统需要将应用程序从后台运行到前台。我们建议您总是基于冷启动的假设进行优化。 这样做可以提高热启动和Lukewarm start的开始的性能。
为了优化您的应用程序以便快速启动,有必要了解系统和应用程序级别上发生的情况,以及在这些状态它们如何交互
1.冷启动
冷启动指的是应用程序从头开始:直到此时系统的进程还没有创建此应用程序的进程。在应用程序自启动以来第一次启动或系统终止应用程序等情况下会发生冷启动。这种类型的启动在最小化启动时间方面是最大的挑战,因为系统和应用程序比其他启动状态具有更多的工作。
在冷启动的开始,系统有三个任务。 这些任务是:
1.加载和启动应用程序。
2.在启动后立即显示应用程序的空白开始窗口。
3.创建应用程序进程。
4.一旦系统创建应用程序进程,应用程序进程将负责下一阶段。这些阶段是:
5.创建应用程序对象。
6.启动主线程。
7.创建主activity。
8.充气视图。
9.放置屏幕。
10.执行初始绘制。
一旦应用进程完成了第一次绘制,系统进程就会换出当前显示的背景窗口,将其替换为主要activity。 此时,用户可以开始使用应用程序。
图1显示了系统和应用程序进程如何在彼此之间移交工作。
图1.冷应用程序启动的重要部分的可视化表示。
在创建application和创建activity时可能会出现性能问题。
application creation
当应用程序启动时,空白的开始窗口保留在屏幕上,直到系统第一次完成绘制应用程序。此时,系统process将切换到应用程序的开始窗口,用户开始与应用程序交互。
如果您在自己的应用程序中重载了Application.oncreate(),应用程序将通过在您的应用程序对象上调用此方法来启动。之后,应用程序产生主线程,也称为UI线程,并创建您的主要activity的任务。
从这一点开始,系统级和应用级过程将根据应用程序生命周期阶段进行。
activity创建
应用程序进程创建您的activity后,该activity将执行以下操作:
1.初始化值
2.调用构造函数。
3.调用适用于activity的当前生命周期状态的回调方法,例如Activity.onCreate()。
通常,onCreate()方法对加载时间有最大的影响,因为它以最高的开销执行工作:加载和扩充视图,并初始化activity运行所需的对象。
2.热启动
与冷启动相比,热启动应用程序要简单得多,开销低。 在热启动,所有的系统都是把你的activity调到前台。 如果所有应用程序的activity仍驻留在内存中,那么应用程序可以避免重复对象初始化,加载布局和渲染。
但是,如果某些内存响应于内存调整事件(例如onTrimMemory())而被清除,那么将需要根据热启动事件重新创建这些对象。
热启动显示与冷启动场景相同的屏幕行为:系统进程显示空白屏幕,直到应用程序完成呈现activity。
3. Lukewarm start
冷启动包括在冷启动期间发生的操作的一些子集; 同时,它表示比热启动少的开销。 有许多潜在的状态可以被认为是Lukewarm start。 例如:
用户退出应用,但随后重新启动它。 该过程可能已继续运行,但应用程序必须通过调用onCreate()从头开始重新创建activity。
系统把应用程序从内存中删除,然后用户重新启动它。 进程和Activity需要重新启动,但任务可以从保存的实例状态bundle传递到onCreate()。
分析启动性能
为了正确诊断开始时间性能,您可以跟踪显示应用程序启动所需时间的指标。
初始显示时间
从Android 4.4(API级别19),logcat包括一个输出行,包含一个名为Displayed的值。 此值表示在启动过程和完成在屏幕上绘制相应activity之间经过的时间量。 经过的时间包括以下事件序列:
1.启动该过程。
2.初始化对象。
3.创建并初始化activity。
4.加载布局。
5.第一次绘制您的应用程序。
报告的日志行看起来类似于以下示例:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
如果您正在从命令行或终端中跟踪logcat输出,则查找已用时间很简单。 要在Android Studio中查找耗用时间,必须在logcat视图中禁用过滤器。 禁用过滤器是必要的,因为系统服务器(而不是应用程序本身)提供此日志。
完成相应设置后,您可以轻松搜索正确的字词以查看时间。 图2显示了如何禁用过滤器,并在底部的第二行输出中显示了显示时间的logcat输出示例。
logcat输出中显示的指标不一定捕获所有资源加载和显示之前的 时间量:它不包括布局文件中未引用的资源,或者应用程序作为对象初始化的一部分创建的资源。它排除了这些资源,因为加载它们是一个内联过程,不会阻止应用程序的初始显示。
完全显示的时间
您可以使用reportFullyDrawn()方法来测量应用程序启动和完成显示所有资源和查看层次结构之间的经过时间。这在应用程序执行延迟加载的情况下可能是有价值的。在延迟加载中,应用程序不会阻止窗口的初始绘制,而是异步加载资源并更新视图层次结构。
如果由于延迟加载,应用程序的初始显示不包括所有资源,那么您可以考虑将所有资源和视图的完成加载和显示作为单独的指标:例如,您的UI可能已完全加载,但尚未显示应用程序必须从网络获取的图片。
为了解决这个问题,你可以手动调用reportFullyDrawn()来让系统知道你的activity完成了它的延迟加载。当您使用此方法时,logcat显示的值是自应用程序对象创建以来所经过的时间,以及调用reportFullyDrawn()的时刻。
调用reportFullyDrawn()实例:
//
public class MainActivity extendsAppCompatActivity implements LoaderManager.LoaderCallbacks<Void> {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onLoadFinished(Loader<Void> loader, Void data) {
// 加载数据
// ……
// 上报reportFullyDrawn
reportFullyDrawn();
}
@Override
public Loader<Void> onCreateLoader(int id, Bundle args) {
return null;
}
@Override
public void onLoaderReset(Loader<Void> loader) {
}
}
输出实例:
$ adb logcat | grep “ActivityManager”
ActivityManager: Displayed com.example.launcher/. LauncherActivity: +999ms
ActivityManager: Fully drawn com.example.launcher/. LauncherActivity: +1s999ms
如果你发现你的显示时间比你想要的慢,你可以继续尝试找出启动过程中的瓶颈。
识别瓶颈
找到瓶颈的两个好方法是AndroidStudio的Method Tracer工具和内联跟踪。 要了解Method Tracer,请参阅该工具的文档。
如果您无法访问MethodTracer工具,或无法在正确的时间启动该工具以获取日志信息,则可以通过在应用程序和activity的onCreate()方法中内联跟踪获得类似的结果。要了解内联跟踪,请参阅跟踪函数的参考文档以及Systrace工具。
常见问题
本节讨论了经常影响应用程序启动性能的几个问题。 这些问题主要涉及初始化应用程序和activity对象,以及加载屏幕。
重应用程序初始化
当代码overrides Application对象时,在初始化该对象时执行繁重的工作或复杂的逻辑,启动性能可能会受到影响。如果执行些不需要的初始化,启动性能也会收到影响。
应用程序初始化期间的其他挑战包括影响大或数量众多的垃圾收集事件,或磁盘I / O与初始化同时发生,进一步阻止初始化过程。 垃圾收集是Dalvik运行时特别考虑的问题; Art运行时同时执行垃圾回收,最小化操作的影响。
诊断问题
您可以使用方法跟踪或内联跟踪来尝试诊断问题。
方法跟踪
运行方法跟踪器工具揭示callApplicationOnCreate()方法最终调用com.example.customApplication.onCreate方法。 如果工具显示这些方法需要很长时间才能完成执行,那么您应该进一步探索以了解正在发生的工作。
内联跟踪
使用内联跟踪来调查可能的罪魁祸首,包括:
您应用程式的初始onCreate()函数。
您的应用程序初始化的任何全局单例对象。
任何磁盘I / O,反序列化或在瓶颈期间可能发生的紧密循环。
解决问题
无论问题是不必要的初始化还是磁盘I / O,解决方案都需要延迟初始化对象:只初始化那些立即需要的对象。 例如,而不是创建全局静态对象,而是移动到单例模式,应用程序只在首次访问它们时才会初始化对象。
重activity初始化
activity创建通常需要很多高开销的工作。通常,有机会优化这项工作以实现性能提高。 这些常见问题包括:
初始化大的或复杂的布局。
阻止在磁盘或网络I / O上绘制屏幕。
加载和解码位图。
栅格化VectorDrawable对象。
初始化activity的其他子系统。
诊断问题
在这种情况下,同样,方法跟踪和内联跟踪都可以证明是有用的。
方法跟踪
当运行方法跟踪器工具时,特定区域专注于您的应用程序的Application子类构造函数和com.example.customApplication.onCreate()方法。
如果工具显示这些方法需要很长时间才能完成执行,那么您应该进一步探索以了解正在发生的工作。
内联跟踪
使用内联跟踪来调查可能的罪魁祸首,包括:
您应用程式的初始onCreate()函数。
它初始化的任何全局单例对象。
任何磁盘I / O,反序列化或在瓶颈期间可能发生的紧密循环。
解决问题
有许多潜在的瓶颈,但两个常见的问题和补救措施如下:
您的视图层次结构越大,应用程序inflating的时间就越多。 您可以采取两个步骤来解决这个问题:
通过减少冗余或嵌套布局来展开视图层次结构。
不要对UI的不需要在启动过程中可见的部分进行inflating。 相反,使用ViewStub对象作为子层次结构的占位符,应用程序可以在更适当的时间膨胀。
将所有的资源初始化在主线程上也可以减慢启动速度。 您可以解决此问题,如下所示:
移动所有资源初始化,以便应用程序可以在不同的线程上懒惰地执行它。
允许应用加载和显示您的视图,然后更新依赖于位图和其他资源的可视属性。
launch屏幕主题
您可能希望为应用程序的加载体验设计主题,以便应用程序的启动屏幕与应用程序的其他部分保持一致,而不是与系统主题一致。这样做可以隐藏缓慢的activity启动。
实现主题启动屏幕的常见方法是使用windowDisablePreview theme属性关闭系统进程在启动应用程序时绘制的初始空白屏幕。但是,这种方法可能会导致启动时间比不抑制预览窗口的应用程序更长。 此外,它强制用户在activity启动时不等待反馈,使他们想知道应用程序是否正常工作。
诊断问题
您可以通过观察用户启动应用时的慢响应来诊断此问题。 在这种情况下,屏幕可能看起来已冻结,或已停止响应输入。
解决问题
我们建议您不要禁用预览窗口,而是遵循常见的材料设计模式。 您可以使用activity的windowBackground主题属性为起始activity提供一个简单的自定义drawable。
例如,您可以创建一个新的可绘制文件,并从布局XML和应用程序清单文件中引用它,如下所示:
布局XML文件:
<layer-listxmlns:android="http://schemas.android.com/apk/res/android"android:opacity="opaque"> <!-- The background color, preferably the same as your normal theme --> <itemandroid: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 {
@Override
protected void onCreate(Bundle savedInstanceState) {
// Make sure this is before calling super.onCreate
setTheme(R.style.Theme_MyApp);
super.onCreate(savedInstanceState);
// ...
}
}