Android 启动优化常用的方案整理
首先附上启动流程:Launcher 点击图标开始
Launcher startActivityAMS查找应用进程是否创建- 未创建,
AMS通知从Zygote进程中fork创建出一个新的进程分配给该应用 - 进程创建完毕,
AMS通过Binder通知应用进程 - ActivityThread 调用
performLaunchActivity - Application 构造函数及
attachBaseContext(),onCreate() new Activity(), 并为此Activity创建一个new PhoneWindow(this)- Activity
onCreate() - Activity
setContentView() new DecorView()&addView(contentRoot, contentParent)onFinishInflate(): 此步骤只是inflate所有的DecorView上的布局views,并不可见- Activity
onStart() - Activity
onResume() window.addView(mDecorView)- View
onAttachedToWindow()、onMeasure()、onSizeChanged()、onLayout()、onDraw() - 至此步为止才把
DecorView加给Window, 应用首页才可见,onWindowFocusChanged(true) - Activity
onPause() - View
onWindowFocusChanged(false) - Activity
onStop() - Activity
onDestroy() - View
onDetackedFromWindow()
1、Application 初始化方面优化
在业务代码中,Application 启动时经常会初始化很多项目内的第三方库,这对 App 的启动会有很大影响,所以尽量把一些不太紧要的初始化异步执行,把必要的一些初始化才放到 onCreate 中执行。
至于异步初始化的方法则有:其他线程 、IntentService 、WorkManager 、MessageQueue.addIdleHandler() 等等进行处理。
IntentServiceAndroid 8.0+ 由于Service后台被限制已经弃用,推荐使用JobIntentService或Jetpack组件包的WorkManager进行代替使用。
2、优化首次安装或冷启动时,卡在白屏的视觉效果优化
在首次安装后或冷启动时,从 Launcher 中点击图标到闪屏页,在性能一般的机器上面,会有短时间的白屏或黑屏,这样对用户而言显然是不太好的。
我们可以在闪屏页配置一个背景,通常是一个和闪屏页相似的背景,然后就不会出现白屏或黑屏,而是设置的闪屏页背景,然后在显示闪屏页。
闪屏页即启动的第一个 Activity,App 启动速度和闪屏页的 onCreate 方法也有关系,所以尽量不要在闪屏页的 onCreate 做太多操作。
启动页主题
1 | <style name="SplashTheme" parent="@android:style/Theme.Light.NoTitleBar.Fullscreen"> |
splash_bg 可以是图片也可以是自定义的 layer 组合。
1 | <activity |
3、MultiDex OPT 优化
Android 低版本4.x及以下,SDK < 21的设备,采用的 Java 运行环境是 Dalvik 虚拟机。它相比于高版本,最大的问题就是在安装或者升级更新之后,首次冷启动的耗时漫长。这常常需要花费几十秒甚至几分钟,用户不得不面对一片黑屏,熬过这段时间才能正常使用 APP。这个问题的根本原因就在于,安装或者升级后首次 MultiDex 花费的时间过于漫长。
推荐使用 抖音的 BoostMultiDex 优化
- 利用系统隐藏函数,直接加载原始
DEX字节码,避免ODEX耗时 - 多级加载,在
DEX字节码、DEX文件、ODEX文件中选取最合适的产物启动 APP - 单独进程做
OPT,并实现合理的中断及恢复机制
4、利用 Redex 工具优化 Dex
Redex 是 Facebook 的一个开源工具,官方的介绍是 An Android Bytecode Optimizer ,Android 字节码优化器。可以减小 Android Apk 的大小和提高 App 启动速度。
根据官方文档的说明,主要的优化项有以下几点:
1、混淆和压缩
类似Android中的proguard,将字节码中的类名、方法名等替换成简短的字符串。2、内联函数
functionA->functionB->functionC,内联后变成functionA(包含functionB代码)->functionC,减少函数调用时间。3、删除代码
类似于标记回收算法,从某些入口函数可以遍历标记出可以访问到的方法,最后未标记到的方法可被移除。理论比较简单,但在Android中还有反射,或者资源文件中对代码的引用等特殊情况需要考虑到。4、基于反馈的class分布
也就是可以对Dex进行重组,根据使用方提供的App启动时加载的类序列配置文件调整Dex中类的顺序,把App冷启动时需要加载的类,放到 Dex前部。5、删除接口
删除只有一个实现类的接口,直接使用实现类。6、删除 MetaData
Dex文件中的一些元数据在运行中并不会使用到。所以可以用Dex中已有的字符串代替Java源文件引用,删除运行时使用不到的注解。
5、其它优化方案
提前加载
SharedPreferences在
Multidex之前CPU是空闲的,加载系统类是可行的,所以可充分利用这段时间加载SharedPreferences。启动阶段不启动子进程
初始化子进程会消耗CPU资源,在启动阶段会导致主进程CPU资源紧张,导致启动阶段资源紧张初始化过慢。
启动阶段不启动
Service、ContentProvider在
Application生命周期方法attachBaseContext方法和onCreate方法中间还会执行ContentProvider生命周期方法,如果在Application初始化过程中启动了Service或者Contentprovider会执行ContentProvider生命周期方法,非常耗时。提前异步类加载
对启动阶段用到的类进行提前异步加载,加载方法有两种:
Class.forName()只加载类本身及其静态变量的引用类。new 类实例可以额外加载类成员变量的引用类。
启动阶段抑制
GC支付宝就用了该方案
CPU锁频