经典面试题25 - 如何优化移动Android应用启动速度

问题
如何优化移动Android应用启动速度?
解答
在讨论如何优化App启动速度之前,我们要清楚启动的分类:
- 冷启动
- 热启动
- 温启动
冷启动指应用从零开始启动,一切资源都要从头开始获取和初始化。而其他两个状态都是系统把在后台运行的应用切换到前台。我们在讨论优化启动状态的时候一般是指冷启动,因为这样的优化也会覆盖其他两个状态的启动。
我们来看看应用在不同的状态启动时,系统和应用层面都发生了什么。
冷启动
在应用冷启动之前,系统进程还没有创建应用进程,在应用启动时,系统进程会做以下三件事:
- 加载启动应用。
- 启动后展现一个空白窗口。
- 创建应用进程。
应用进程被创建后,就进入了应用进程的主导阶段,应用进程主要做以下六件事:
- 创建应用对象。
- 启动应用主线程。
- 创建应用主Activity。
- 填充视图。
- 屏幕视图布局。
- 渲染视图。
在应用进程完成了第一次渲染后,系统进程将会用主Activity替换当前显示的默认窗口,然后用户就可以使用应用了。
如果开发者重载了Application.oncreate(),应用将通过调用重载方法启动。此后应用将会产生UI主线程,并且主线程将会创建主Activity,从而应用进程按照应用生命周期阶段执行。

在应用进程创建了Activity之后,应用将会执行如下操作:
- 初始化变量。
- 调用构造函数。
- 调用回调函数,例如Activity.onCreate(),对应Activity的生命周期状态。
一般来说,onCreate()方法对加载时间有最大的影响,因为它需要加载和填充View,并且初始化供Activity运行的对象。
热启动
在热启动时,系统所做的是把应用从后台切到前台。如果应用的所有Activity仍在内存里存在,可以避免重复初始化对象,布局创建和填充。
但是如果一些内存由于类onTrimMemory()的原因被回收,这些对象需要被重新创建。
而系统进程在热启动和冷启动时做工作的一样:系统进程展示一个空白的屏幕,直到应用完成渲染当前Activity。
温启动
温启动复杂度介于冷启动和热启动之间,比冷启动简单,但却比热启动开销大。
有不少状态可以被称为温启动状态,如:
- 用户退出应用,但是随后重新启动它,进程有可能还在继续运行,但是应用将会通过onCreate方法重新创建Activity。
- 系统把应用从内存中清除,然后用户重新启动它。进程和Activity将会被重新创建,但是启动速度因为onCreate方法的已保存实例得到加速。
接下来我们来讨论测量应用启动的性能,注意不要使用Debug版本的应用做调试。
Logcat Displayed
从Android 4.4版本开始, logcat提供了一个包含Displayed的输出:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)
这个值表示了启动应用进程和结束绘制相关Activity中间所需的时间。花费的时间包含如下部分:
- 启动应用进程。
- 初始化对象。
- 创建和初始化Activity。
- 填充布局。
-
绘制应用。
Displayed
在logcat输出的Displayed值不一定捕获所有资源被加载和展示所需的启动时间,它没有包含未在布局中引用的资源和作为对象初始化被应用创建的资源。
上面中的total时间指应用进程启动以后,包括一些Activity可能最先启动却没有展示在屏幕上的时间,total只有在总时间和单个Activity和总时间不一致的情况下显示。
工具方法
推荐两种寻找瓶颈的好的方法是Android Studio的方法追踪工具和内联追踪。可以在这里学习方法追踪器。
如果不能使用方法追踪工具,或者不能通过log去抓取相应的信息,可以通过应用和ActivityonCreate() 方法内部的内联追踪去获取相应的信息。点击如下链接去学习内联追踪:Trace方法和系统调用跟踪提供器工具。
常见问题
- 当Application对象被重载,并且在初始化对象时执行了繁重的操作和复杂的逻辑,启动性能可能会受到影响。有些初始化可能是完全不必要的,对于一些不必要多初始化和磁盘资源读取,可以使用延迟加载对象,如使用单例模式来替换静态变量,这样只有在第一次被访问才被初始化,依赖性注入框架如Dagger也可做相应优化。
-
视图层级越多,应用需要花费更多的时间去填充它。两个步骤可以解决这个问题:
- 通过减少多余的和内嵌的布局,简化视图层级。
- 在启动期间不需要展示给用户的布局暂时不要进行绘制填充。使用ViewStub对象替代父层级,这样可以在合适的时候再对这样的布局进行绘制。
-
在主线程里初始化所有的资源也会减慢启动的速度。可以尝试如下处理这种问题:
- 把资源初始化放在非主线程上执行,以便进行懒加载。
- 允许应用去加载和展示视图,随后更新依赖于 bitmap和其他资源的可视部分。
启动屏幕
从产品体验的角度,我们可以给应用加载过程添加主题,以便应用的启动屏的主题风格与应用的其它部分相符,这样可以掩盖Activity缓慢启动现象。
一个常见的方式去实现自定义启动屏幕主题是:使用windowDisablePreview主题属性去关掉默认的白屏,这个白屏就是系统线程在初始化App的时候绘制的。但是这种方法会导致更长的启动时间,同样地,当应用启动的时候,它强迫用户在没有任何反馈的情况下等待,这会让用户对应用是否正常运行感到疑惑。
这里可以遵从常见的Material Design样式,而不是去禁用预加载屏幕展示。然后使用Activity的windowBackground属性去为启动Activity提供一个简单的自定义图片。
例如:创建一个新的图片文件,并且用layout文件和manifest文件引用它,如下:
<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" />
更多
获取更多内容请关注微信公众号豆志昂扬:
- 直接添加公众号豆志昂扬;
- 微信扫描下图二维码;
