思路分析
前言
冷启动涉及的因素很多,从流程上说,分为以下几个阶段
- 点击Launcher的图标,AMS处理intent,与Zygote socket交互fork进程,新进程运行ActivityThread代码,AMS和ApplicationThread互相绑定,AMS发binder信息反射启动Application。以上是第一阶段,这个阶段到Application的attchbasecontext方法之前,我们基本上都无法参与,但ContentProvider的初始化是在Application之前的,它的onCreate方法运行在主线程,这个也需要关注一下耗时问题。
- 从Application的attchbasecontext开始,是第二阶段,这个阶段经历以下几个流程。Application的init方法,attchbasecontext方法,onCreate方法,SplashActivity的生命周期,最后跳转到主Activity的过程。这个过程中会初始化大量的三方组件和业务组件,其中就包含了耗时方法,如果项目比较旧,还会有很多冗余的逻辑和代码,依赖关系等错综复杂。这时候优化就必须进行了。
优化逻辑
一、分析耗时
首先要找到耗时的方法,卡顿的原因才能继续,不然优化半天我们为了啥?
网上有很多方法,简单列几个
-
Logcat打日志,这个就不多说了,**过滤关键字“Displayed”**就可以看到
-
adb shell
// 其中的AppstartActivity全路径可以省略前面的packageName adb shell am start -W [packageName]/[AppstartActivity全路径]
通过这个方法可以看懂ASM启动Activity的耗时,非准确
-
Logcat代码打点,在启动的关键节点打耗时方法,计算差值
-
AOP:此方法灵活应用around,before,after等关键词对方法进行耗时打印,了解更多可以搜索引擎解决
-
TraceView和Systrace,有需要可以搜索引擎解决
TraceView:Android studio自带的ProFiler就可以查看,耗时非常不准确。或者可以在开始记录的点写上代码Debug.startMethodTracing(“tracePath”),在终止记录的点写上代码Debug.stopMethodTracing()。通过adb pull /mnt/sdcard/tracePath.trace .将trace导出指定的文件夹中,通过Android studio打开trace文件,界面同CPU Profiler差不多。
Systrace:在系统的一些关键链路(如SystemServcie、虚拟机、Binder驱动)插入一些信息(Label)。然后,通过Label的开始和结束来确定某个核心过程的执行时间,并把这些Label信息收集起来得到系统关键路径的运行时间信息,最后得到整个系统的运行性能信息。
-
ASM插装,使用GradlePlugin编写插件,利用ASM在Transform过程中对工程方法耗时进行打印。
我选择的是最后一种方法,这种方法的优势是,打成正式包之后对log的日志进行分析,对函数本身的耗时影响较小准确性高。但是缺点就是无法和分析工具一样,获得更多的硬件信息。
二、优化工具
-
主题切换:在Activity的windowBackground主题属性预先设置一个启动图片(layer-list实现),在启动后,在Activity的onCreate()方法中的super.onCreate()前再setTheme(R.style.AppTheme)。
-
懒加载:注意和延时加载不同,是在需要使用的时候才加载。
-
异步加载:这里需要了解线程池、线程池调优、和任务异步相关的知识。通过CPU核心数量等相关信息,开辟CPU密集线程池和IO密集线程池,把不必须同步加载的任务异步加载。这里可以自己写一个启动bootmanager来统一管理同步和异步任务。
-
延时加载:延时加载解决的问题是必须同步加载,但是又不急需的任务。延时加载可以将其执行顺序往后调。来达到降低耗时的目的。这里可以开发一个lazybootmanager来统一管理延时任务
-
Multidex优化:这个只针对Android5.0以下的机型,直接使用字节BoostMultiDex插件即可,有需要搜索引擎解决。
-
内存抖动:减少临时变量的使用,和不必要的GC,这个得结合项目及代码进行优化。
-
耗时方法:将耗时方法异步执行,再保存静态结果,以供同步方法使用。这里需要做容错,如果异步方法没执行出结果,那么再同步调用一遍,以保执行结果的正确性。
-
类加载优化:new 实例中有很大一部分时间是load相应的类到方法区,非常耗时,需要优化。
-
WebView启动:使用WebView缓存池,用到WebView的时候都从缓存池中拿,注意内存泄漏问题。
-
IO优化:业务数据预加载、数据库提前读取数据。|| xml结构优化,比如启动页就简单的FrameLayout加图片等形式
-
子进程优化:启动阶段减少子进程的启动等。
实践思路
1、耗时
使用Plugin插件+ASM字节码插装的方式进行方法耗时打印。
ASM插装代码可以参考ASMPlugin插件(Android Studio)进行分析,降低难度。
2、同步&异步加载框架
这里可以去github上找到很多开源的bootmanger框架,没必要自己写,但需要实现以下几种功能。
1、任务依赖:必须解决任务之间的依赖关系,这个可以参考单向无环拓补图算法进行实现。
2、任务优先级:同步方法直接的优先级、异步方法之间的优先级。各自灵活设置,保证优化的有效性。
3、线程池:必须通过CPU等硬件信息设置CPU密集的线程池。
4、任务解耦:这个基本上没啥问题,但也要注意。
5、任务区分:主进程任务、主线程任务、运行进程等一系列问题。
3、懒加载框架
这个同样去github找,注意以下几点就行。
1、触发时机:一般是mainActivity的idelHandler时机触发,注意多次idel触发多个任务,不要一次全部执行,影响用户使用,可以使用优先队列实现。
2、任务性质:延时加载解决的问题是必须同步加载,但是又不急需的任务。如果可以异步,直接给异步框架即可。
4、IO&内存抖动
1、数据库,SP文件异步读取到内存中,减少查询耗时
2、xml资源文件简化,SplashActivity耗时同步方法异步,注意容错。**不推荐使用X2C和AsyncLayoutInfalter进行优化,对于冷启动意义不大。**他们可以用于页面启动优化,限制较多,需要注意时机和限制。
3、减少引用临时变量的使用,如果临时变量太多内存抖动触发GC,得不偿失。
5、类加载优化
这里可以通过异步提前new或者Class.forName()对象让它先加载到方法区,减少耗时。
注意:
Class.forName()只加载类本身及其静态变量的引用类。
new 类实例 可以额外加载类成员变量的引用类。
思路总结
经过上述的优化之后,其实就已经没有什么太大的问题了。最重要的还是结合业务进行优化。这里只给出了思路,具体的实现可以参考开源框架。我提供方向,你自己努力,加油!!!