启动窗口优化
启动应用后会显示空白启动窗口,可给这个窗口这是一张背景图代替白屏(通过theme设置)。
异步任务
1,初始化逻辑抽象成一个个Task
2,根据任务的依赖关系,使用 BFS(广度优先) 构建出有向无环图,并得到拓扑排序,获取任务的执行顺序。
1.1,首先找出所有入度为 0 的队列,用 queue 变量存储
1.2,当队列不为空,进行循环判断。
-
从队列 pop 出,添加到结果队列
-
遍历当前任务的子任务,通知他们的入度减一(其实是遍历 taskChildMap),如果入度为 0,添加到队列 queue 里面
1.3,当结果队列和 list size 不相等试,证明有环
3,在多线程执行过程中,通过任务的依赖和 CounDownLatch 确保先后执行关系的
假设a是b的一个前置任务,当一个任务b执行时:
2.1,如果CounDownLatch>0,说明前置任务a没有执行完毕,任务b等待;
2.2,前置任务a执行完毕,通知任务b,b任务的CounDownLatch减一;
2.3,任务b的CounDownLatch如果为0,执行b任务。
任务的CounDownLatch为前置任务的数量。初始化任务时会设置当前任务的前置任务列表,所以可以通过当前任务获取到前置任务列表及其数量。
真正的任务是DispatchRunnable,上述任务指DispatchRunnable,它持有Task并在其run方法中调用了Task的run方法。
await方法实现原理,也是通过将使用CounDownLatch记录NeedWait的任务数,NeedWait任务执行完就减1,CounDownLatch如果为0,才执行后续任务(拓扑图任务后面的任务)
注意:异步任务必须使用线程池,避免线程过多,cpu过度切换
延迟任务
public class DelayInitDispatcher {
private Queue<Task> mDelayTasks = new LinkedList<>();
private MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
if(mDelayTasks.size()>0){
Task task = mDelayTasks.poll();
new DispatchRunnable(task).run();
}
return !mDelayTasks.isEmpty();
}
};
public DelayInitDispatcher addTask(Task task){
mDelayTasks.add(task);
return this;
}
public void start(){
Looper.myQueue().addIdleHandler(mIdleHandler);
}
}
复制代码
Handler().postDelayed()的痛点:
1,时机不容易控制:handler postDelayed指定的延迟时间不好估计。
2,导致界面UI卡顿:此时用户可能还在滑动列表。
使用IdleHandler,Handler 空闲的时候才会被调用,如果返回 true, 则会一直执行,如果返回 false,执行完一次后就会被移除消息队列。
当CPU空闲时,去执行延迟初始化的task,一个一个地拿出来并执行。这种分批执行的好处在于每一个task占用主线程的时间相对来说很短暂,并且此时CPU是空闲的,这样能更有效地避免UI卡顿。
需要注意的是,能异步的task(或者是必须在Application的onCreate方法完成前必须执行完的非异task务),优先使用异步启动器在Application的onCreate方法中加载,对于不能异步的task,我们可以利用延迟启动器进行加载。如果任务可以到用时再加载,可以使用懒加载的方式。
问题:用户使用某个库时,如果还没有初始化怎么处理?
类重排
通过 ReDex 的 Interdex 调整类在 Dex 中的排列顺序,把启动时需要加载的类按顺序放在主 dex 里。具体实现可以参考 Redex 初探与 Interdex:Andorid 冷启动优化。
抖音BoostMultiDex优化
针对5.0以下的系统:
具体的实现原理为:在第一次启动的时候,直接加载没有经过 OPT 优化的原始 DEX,先使得 APP 能够正常启动。然后在后台启动一个单独进程,慢慢地做完 DEX 的 OPT 工作,尽可能避免影响到前台 APP 的正常使用。绕过 ODEX 直接加载 DEX 的方案如下:
1)、从 APK 中解压获取原始 Secondary DEX 文件的字节码
2)、通过 dlsym 获取dvm_dalvik_system_DexFile数组
3)、在数组中查询得到Dalvik_dalvik_system_DexFile_openDexFile_bytearray函数
4)、调用该函数,逐个传入之前从 APK 获取的 DEX 字节码,完成 DEX 加载,得到合法的DexFile对象
5)、把DexFile对象都添加到 APP 的PathClassLoader的 pathList 里
主页面数据预加载
splash页面的时候使用异步任务提前将主页面的数据请求下来,主页面加载的时候可以直接从内存读取。
闪屏页与主页的绘制优化
1、分析布局,减少布局嵌套或者替换消耗性能少的布局(FrameLayout,constraintlayout)
2、使用include+merge减少布局层级
3、使用viewstub提供按需加载
4、复用系统自带的资源 winow background
内存优化避免gc占用cpu时间
启动过程中减少 GC 的次数
1,避免进行大量的字符串操作,特别是序列化和反序列化
2,频繁创建的对象需要考虑复用
3,转移到 Native 实现
减少冷启动的次数
MainActivity 返回用隐藏代替销毁
override fun onBackPressed() {
//super.onBackPressed()
moveTaskToBack(true)
}
复制代码
webview优化
如果主页面打开后有webview显示广告。
1,webview提前初始化及复用
2,使用离线包
3,使用数据预加载
参考
深入探索Android启动速度优化
Android 优化之 App 启动优化