Android启动优化
启动优化的方式
- 闪屏页优化
- MultiDex优化(本文重点)
- 第三方库懒加载
- WebView优化
- 线程优化
- 系统调用优化
闪屏页优化
给启动页activity或者application设置窗口背景主题
<style name="AppThemeWelcome" parent="Theme.AppCompat.NoActionBar">
...
<item name="android:windowBackground">@drawable/logo</item> <!-- 默认背景-->
</style>
MultiDex 优化
apk编译流程
- 打包资源文件,生成R.java文件(使用工具AAPT)
- 处理AIDL文件,生成java代码(没有AIDL则忽略)
- 编译 java 文件,生成对应.class文件(java compiler)
- .class 文件转换成dex文件(dex)
- 打包成没有签名的apk(使用工具apkbuilder)
- 使用签名工具给apk签名(使用工具Jarsigner)
- 对签名后的.apk文件进行对齐处理,不进行对齐处理不能发布到Google Market(使用工具zipalign)
classLoader加载类原理
ClassLoader.loadClass -> DexPathList.loadClass ->
遍历dexElements数组 ->DexFile.loadClassBinaryName
ClassLoader加载类的时候是通过遍历dex数组,从dex文件里面去加载一个类,加载成功就返回,加载失败则抛出Class Not Found 异常。
MultiDex原理
反射dexElements数组,将新增的dex添加到数组后面,这样就保证ClassLoader加载类的时候可以从新增的dex中加载到目标类
MultiDex 优化(两种方案)
install过程为什么耗时,因为涉及到解压apk取出dex、压缩dex、将dex文件通过反射转换成DexFile对象、反射替换数组
方案1:子线程install(不推荐)
这个方法大家很容易就能想到,在闪屏页开一个子线程去执行MultiDex.install,然后加载完才跳转到主页。需要注意的是闪屏页的Activity,包括闪屏页中引用到的其它类必须在主dex中,不然在MultiDex.install之前加载这些不在主dex中的类会报错Class Not Found。
这个可以通过gradle配置,如下:
defaultConfig {
//分包,指定某个类在main dex
multiDexEnabled true
multiDexKeepProguard file('multiDexKeep.pro') // 打包到main dex的这些类的混淆规制,没特殊需求就给个空文件
multiDexKeepFile file('maindexlist.txt') // 指定哪些类要放到main dex
}
maindexlist.txt 文件指定哪些类要打包到主dex中,内容格式如下
com/lanshifu/launchtest/SplashActivity.class
方案2:今日头条的启动优化
- 在Application 的attachBaseContext 方法里,启动另一个进程的LoadDexActivity去异步执行MultiDex逻辑,显示Loading。
- 然后主进程Application进入while循环,不断检测MultiDex操作是否完成
3.MultiDex执行完之后主进程Application继续走,ContentProvider初始化和Application onCreate方法,也就是执行主进程正常的逻辑
第三方库懒加载
很多第三方开源库都说在Application中进行初始化,十几个开源库都放在Application中,肯定对冷启动会有影响,所以可以考虑按需初始化,例如Glide,可以放在自己封装的图片加载类中,调用到再初始化,其它库也是同理,让Application变得更轻。
WebView启动优化。
WebView启动优化文章也比较多,这里只说一下大概优化思路。
- WebView第一次创建比较耗时,可以预先创建WebView,提前将其内核初始化。
- 使用WebView缓存池,用到WebView的地方都从缓存池取,缓存池中没有缓存再创建,注意内存泄漏问题。
- 本地预置html和css,WebView创建的时候先预加载本地html,之后通过js脚本填充内容部分。
数据预加载
这种方式一般是在主页空闲的时候,将其它页面的数据加载好,保存到内存或数据库,等到打开该页面的时候,判断已经预加载过,直接从内存或数据库读取数据并显示
线程优化
线程是程序运行的基本单位,线程的频繁创建是耗性能的,所以大家应该都会用线程池。单个cpu情况下,即使是开多个线程,同时也只有一个线程可以工作,所以线程池的大小要根据cpu个数来确定。