初识启动加速
应用的启动分为冷启动,热启动,温启动,而启动最慢,挑战最大的就是冷启动:系统和App本身都有更多的工作要从头开始,应用在冷启动之前,要执行三个任务:
1 加载启动App
2 App启动之后立即展示出一个空白的Window
3 创建App的进程
而这三个任务执行完毕后会马上执行以下任务
1 创建App对象
2 启动Main Thread
3 创建启动的Activity对象
4 加载View
5 布置屏幕
6 进行第一次绘制
而一旦App进程完成了第一次绘制,系统进程就会用Main Activity替换已经展示的Background Window,此时用户就可以使用App了
作为普通应用,App进程的创建等环节我们是无法住的控制的,可以优化的也就是Application,Activity创建以及回调等过程,同样,Google也给出了启动加速的方向。
1 利用提前展示出来的Window,快速展示出来一个画面,给用户快速反馈的体验
2 避免在启动时做密集沉重的初始化(Heavy app initialization)
3 定位问题:避免I/O操作,反序列化,网络操作,布局嵌套等
注:方向1 属于治标不治本
启动加速之主题切换
使用Activity的windowBackground主题属性来为启动的Activity提供一个简单的drawable
这样在启动的时候吗,就会先展示一个界面,这个界面就是Manifest中设置的Style,等Activity加载完毕后,再去加载Activity的界面,而在Activity的界面中,我们将主题重新设置为正常的主题,从而产生一种快的感觉
避免在启动时做密集沉重的初始化
在Application以及首屏Activity中我们主要做了:
1 MultiDex以及Tinker的初始化,最先执行,
2 Application中主要做了各种三方组件的初始化
优化方向
1 考虑异步初始化三方组件,不阻塞主线程
2 延迟部分三方组件的初始化,实际上我们把所有三方组件都放到异步任务中,可能会出现WorkThread中尚未初始化完毕但MainTnread中已经使用的错误,因此这种情况建议延迟到使用前再去初始化
修改:
1 将友盟,Bugly等组件放在WorkThread中初始化
2 延迟地图定位,ImageLoader等组件的初始化,地图鸡自由统计延迟4秒,此时应用已经打开,而ImageLoader 因为调用关系不能异步以及过久延迟,初始化从Application 延迟到SplashActivity,而EventBus因为在Activity中使用
注:闪屏页的2秒停留可以利用,把延迟操作延迟到这个时间间隔里
启动加速之Diagnosing The Problem
在开发阶段我们一般使用BlockCanary或者ANRWatchDog找耗时操作,简单明了,但是无法得到每一个方法的执行时间以及更详细的对比信息,我们可以通过Method Tracing或者DDMS来获得更全面详细的信息
启动应用,点击Start Method Tracing ,应用启动后再次点击,会自动打开刚才操作所记录下的.tracer文件,建议使用DDMS来查看
左侧为发生的具体线程,右侧为发生的时间轴,下面是发生的具体方法信息:Real Time/Calll(实际发生实际),Calls+RecurCalls/Total(发生次数)
1 可以直观看到MainThread的时间轴很长,说明大对数任务都是在MainTYhread中执行
2 通过Real Time降序排列可以看到程序中的部分代码确实非常耗时
即便是耗时操作,但是只要正确发生在WorkThread就没问题,因此我们需要确认这些方法执行的线程以及发生的时机,这些操作如果发生在主线程没可能不构成ANR的发生条件,但是卡顿是在所难免,结合上章节图App冷启动业务工作流程图中业务操作以及分析图,再次查看代码我们可以看到:部分耗时操作例如IO读取等确实发生在主线程。事实上在traceview里点击执行函数的名称不仅可以跟踪到父类及子类的方法耗时,也可以在方法执行时间轴中看到具体在哪个线程以及耗时的界面闪动。
分析到部分耗时操作发生在主线程,那么我们把耗时操作都改到子线程是不是就ok了?
1 卡顿不能都靠异步来解决,错误的时候工作线程不仅不能改善卡顿,反而可能加剧卡顿,是否需要开启工作线程需要根据具体的性能瓶颈根源具体分析
2 而如何开启线程同样也有学问:Thread,ThreadPoolExecutor,AsyncTask,HandlerThread,Intent Service等各有利弊,通常情况下,ThreadPoolExecutor 比Thread更加高效,优势明显,但是特定场景下单个时间点表现Thread会比ThreadPoolExecutor好,同样的创建对象,ThreadPoolExecutor的开销明显比Thread大
3 正确的开启线程也不能解决所有问题,例如执行网络请求会创建线程池,而在Application 中正确的创建线程池也会降低启动速度
通过对traceview的详细跟踪以及代码的详细比对,我发现卡顿发生在:
部分数据库及IO的操作发生在首屏Activity主线程;
Application中创建了线程池;
首屏Activity网络请求密集;
工作线程使用未设置优先级;
信息未缓存,重复获取同样信息;
流程问题:例如闪屏图每次下载,当次使用;
以及其它细节问题:
执行无用老代码;
执行开发阶段使用的代码;
执行重复逻辑;
调用三方SDK里或者Demo里的多余代码;
项目修改:
1. 数据库及IO操作都移到工作线程,并且设置线程优先级为THREAD_PRIORITY_BACKGROUND,这样工作线程最多能获取到10%的时间片,优先保证主线程执行。
2. 流程梳理,延后执行;
实际上,这一步对项目启动加速最有效果。通过流程梳理发现部分流程调用时机偏早、失误等,例如:
3.其它优化;
去掉无用但被执行的老代码;
去掉开发阶段使用但线上被执行的代码;
去掉重复逻辑执行代码;
去掉调用三方SDK里或者Demo里的多余代码;
信息缓存,常用信息只在第一次获取,之后从缓存中取;
项目是多进程架构,只在主进程执行Application的onCreate();
网友评论