前言:
没有前言...直接上干货!
影响启动速度的原因
1.高耗时任务:
数据库初始化、某些第三方框架初始化、大文件读取、MultiDex加载等,导致CPU阻塞
2.复杂的View层级:
使用的嵌套Layout过多,层级加深,导致View在渲染过程中,递归加深,占用CPU资源,影响Measure、Layout等方法的速度
3.类过于复杂:
Java对象的创建也是需要一定时间的,如果一个类中结构特别复杂,new一个对象将消耗较高的资源,特别是一些单例的初始化,需要特别注意其中的结构
4.主题及Activity配置:
有一些App是带有Splash页的,有的则直接进入主界面,由于主题切换,可能会导致白屏,或者点了Icon,过一会儿才出现主界面
一些典型的例子及优化方案
1.MultiDex
由于Android 5.0以下使用的Dalvik虚拟机天生对MultiDex支持不好,导致在4.4(及以下)的系统上,如果使用了MultiDex做为分包方案,启动速度可能会慢的多,实际数值跟dex文件的大小、数量有关,估计会慢300~500ms
- 解决方案:
1.限制APP在5.0以上使用:目前大多数用户已经在使用Android 5.0以上的版本了,当然,还有很多4.4用户,很多APP也是只支持4.4以上(比如:百度APP),为了用户体验,可以考虑舍弃一部分用户
2.优化方法数:尽量避免方法超过65535个,同时可以开启Release配置的Minify选项,打包时删掉没有用的方法,不过如果框架引用的较多,基本没效果
3.少用一些不必要的框架:有些框架功能很强大,但不一定都能用得上,引进来会新增很多的方法,导致必须开启MultiDex,可以自己造轮子,或者找轻量级的框架
4.慎用Kotlin:由于Kotlin现在还没有内置在Android系统中,所以APP如果使用了Kotlin,可能会导致引入很多的Kotlin方法,导致必须分割Dex,这个有待Google在Android P中解决
5.Dex懒加载:在APP功能日益复杂的今天,MultiDex几乎是已经无法避免了,为了启动速度的优化,可以将启动时必需的方法,放在主Dex中(即classes.dex),方法是在Gradle脚本中配置multiDexKeepFile或者multiDexKeepProguard属性(代码如下),详见:官方文档,待App启动完成后,再使用MultiDex.install来加载其他的Dex文件。这种方法风险比较高,而且实现成本比较大,如果启动依赖的库比较多,还是无法实现
android {
buildTypes {
release {
multiDexKeepFile file('multidex-config.txt') // multiDexKeepFile规则
multiDexKeepProguard file('multidex-config.pro') // 类似ProGuard的规则
}
}
}
配置文件示例:
# 常规的multiDexKeepFile规则
com/example/MyClass.class
com/example/MyOtherClass.class
# 类似ProGuard规则
-keep class com.example.MyClass
-keep class com.example.MyClassToo
-keep class com.example.** { *; } // All classes in the com.example package
5.插件化或H5/React Native方案:即端只提供Native调用能力和容器,业务由插件来做,本地只需要加载基础的Native能力相关类即可,其他完全下发,或内置成资源文件调用
Glide及其他图片框架
Glide是一个很好用的图片加载框架,除了常用的图片加载、缓存功能以外,Glide支持对网络层进行定制,比如换成OkHttp来支持HTTP 2.0。不过,如果在追求启动速度的情况下,在Splash页或主界面加载某一张图片时,往往是第一次使用Glide,由于Glide没有初始化,会导致这次图片加载的时间比较长(不管本地还是网络),特别是在其他操作也在同时抢占CPU资源的时候,慢的特别明显!而后面再使用Glide加载图片时,还是比较快的
Glide初始化耗时分析:Glide的初始化会加载所有配置的Module,然后初始化RequestManager(包括网络层、工作线程等,比较耗时),最后还要应用一些解码的选项(Options)
- 解决方案:在Application的onCreate方法中,在工作线程调用一次GlideApp.get(this)
override fun onCreate() {
super.onCreate()
// 使用Anko提供的异步工作协程,或者自行创建一个并发线程池
doAsync {
GlideApp.get(this) // 获取一个Glide对象,Glide内部会进行初始化操作
}
}
greenDAO和其他数据库框架
greenDAO实现了一种ORM框架,数据库基于SQLite,使用起来很方便,不需要自己写SQL语句、控制并发和事务等等,其他常见的数据库框架如:Realm、DBFlow等等,使用起来也很方便,但他们的初始化,尤其是需要升级、迁移数据时,往往会带来不小的CPU和I/O开销,一旦数据量比较多(比如:很长时间的聊天记录、浏览器浏览历史记录等),往往都需要专门一个界面来告知用户:APP正在做数据处理工作。所以,如果为了提高APP启动速度,避免在APP启动时做数据库的耗时任务,很有必要!
- 解决方案:
1.必要数据避免使用数据库:如果首屏的展示内容需要根据配置来决定,那么干脆放弃数据库存储和读取,直接放在文件、SharedPreference里面,特别是多组键值对的读取,如果使用数据库,在除过初始化占用的时间以后,可能还需要30~50ms来完成(因为需要多次读取),而如果存在SharedPreference中,即使是转换成JSON并解析,可能也就在10ms之内
2.数据库预先异步初始化:使用greenDAO时,预先初始化很有必要,可以保证在第一次读取数据库时,不占用主线程资源,防止拖慢启动速度,具体做法如下:
// Application
override fun onCreate() {
super.onCreate()
// 使用Anko提供的异步工作协程,或者自行创建一个并发线程池
doAsync {
DbManager.daoSession // 获取一次greenDao的DaoSession实例化对象即可
}
}
// DBManager(数据库相关单例)
object DbManager {
// greenDAO的DaoMaster,用来初始化数据库并建立连接
private val daoMaster: DaoMaster by lazy {
val openHelper = DaoMaster.OpenHelper(ContextUtils.getApplicationContext(), "Test.db")
DaoMaster(openHelper.writableDatabase)
}
// 具体的数据库会话
val daoSession: DaoSession by lazy {
daoMaster.newSession()
}
}
View和主题
View层级
主要在于首屏/Splash页的Layout布局层次过深,导致View在渲染时,递归加深,消耗过多的CPU和内存资源,阻塞主线程,所以最根本的思路就是解决层级问题,检查一个App的View层级,可以使用Android Studio自带的Layout Inspector工具,如图:

在选择了需要检查的进程及Window(Dialog可能会创建新的Window,但显示的Activity是同一个)以后,就可以看到Android Studio自动进行的Capture的内容了

根据左边View层级显示的内容,分析不必要的嵌套布局,通过改造,即可对View层级进行优化
除了根据上面的方法分析层级以外,可以使用Google最新推出的ConstraintLayout,官网链接:ConstraintLayout
ConstraintLayout采用的约束布局概念,类似于iOS的AutoLayout,但使用起来,远比AutoLayout方便、强大,个人感觉吸取了RelativeLayout的方便、FrameLayout的灵活、LinearLayout的高效等特点,通过控件见相互的约束控制,可以构建出近乎平面的布局,这样就可以减少布局层级,只用ConstraintLayout一层Layout实现复杂的UI布局,非常值得学习和使用!

如果分别使用RelativeLayout、FrameLayout、LinearLayout和ConstraintLayout构建一个复杂的布局,或许,ConstraintLayout,就要比其他几种布局快50~100ms!
App主题
我们可以做个实验,使用以下几种主题,看看APP的启动速度(以ActivityManager的Log为准):
@android:style/Theme.NoTitleBar.Fullscreen
@android:style/Theme.Black
默认(根据操作系统自动选择)
其中,MainActivity的根布局是一个空的LinearLayout,将App杀死冷启动5,取平均时间
Black

平均启动时间:160ms
FullScreen

平均启动时间:126.8ms
默认:

平均启动时间:174.8ms
可以得出一个结论:使用一个没有ActionBar的主题,比较快,而如果连StatusBar也去掉了,速度最快!
原因是这样的,启动一个Activity的时候,系统会创建包含一个DecorView的Window,而StatusBar也好,ActionBar也好,都是这个View中的子元素,多了一个View,当然多了一层布局,肯定是耗时的
所以,如果想提高APP的启动速度,尤其是使用Splash的App,务必将第一个Activity的主题设为FullScreen的,这样能有效提高启动速度
总结
在APP功能日益增加和用户体验不断改良的今天,APP启动速度,已然成为影响用户体验的第一道门槛。所谓快,其实是在用户感官上的一种反应,如果能够使用以上的手段对APP的启动速度优化,虽然实际上启动时的总操作量可能并没有真正减少,但经过合理的先后顺序安排,可以使得某些不必要的任务,延后再执行,起到在APP启动时,更轻量、更灵敏的作用,这样能够比较快的响应用户从Launcher点击Icon的操作,提升用户体验,让用户感觉到『快』。
版权声明:本文为CSDN博主「袁国正_yy」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:Android启动速度优化移动开发可爱的程序猿-CSDN博客
如果对你有帮助,烦请各位点个关注!

网友评论