1.应用启动的类型
冷启动
冷启动指的是该应用程序在此之前没有被创建,发生在应用程序首次启动或者自上次被终止后的再次启动。简单的说就是app进程还没有,需要创建app的进程启动app。
比如开机后,点击屏幕的app图标启动应用。
冷启动的过程主要分为两步:
1)系统任务。加载并启动应用程序;显示应用程序的空白启动窗口;创建APP进程
2)APP进程任务。启动主线程;创建Activity;加载布局;屏幕布局;绘制屏幕
其实这不就是APP的启动流程嘛?所以冷启动是会完整走完一个启动流程的,从系统到进程。
温启动
温启动指的是App进程存在,但Activity可能因为内存不足被回收,这时候启动App不需要重新创建进程,只需要执行APP进程中的一些任务,比如创建Activity。
比如返回主页后,又继续使用其他的APP,时间久了或者打开的应用多了,之前应用的Activity有可能被回收了,但是进程还在。
所以温启动相当于执行了冷启动的第二过程,也就是APP进程任务,需要重新启动线程,Activity等。
热启动
热启动就是App进程存在,并且Activity对象仍然存在内存中没有被回收。
比如app被切到后台,再次启动app的过程。
所以热启动的开销最少,这个过程只会把Activity从后台展示到前台,无需初始化,布局绘制等工作。
那我们所要做的启动速度优化呢,就是对于冷启动这种情况进行的。
2.启动耗时统计:
2.1adb命令查看耗时:
如果是本地调试的话,统计启动时间还是很简单的,通过命令行方式即可:
adb shell am start -W packagename/activity
例如:
adb shell am start -W com.xxx.xxxx/com.xxxx.login.activity.LauncherActivity
得到冷启动时间:
WaitTime
返回从 startActivity 到应用第一帧完全显示这段时间. 就是总的耗时,包括前一个应用 Activity pause 的时间和新应用启动的时间;
ThisTime
表示一连串启动 Activity 的最后一个 Activity 的启动耗时;
TotalTime
表示新应用启动的耗时,包括新进程的启动和 Activity 的启动;
一般来说,只需查看得到的TotalTime,即应用的启动时间,其包括 创建进程 + Application初始化 + Activity初始化到界面显示 的过程。
多长时间内app启动完成才算秒开?
1秒
既然谈论的目标是秒开率,那必然是需要 1s 内完成用户的启动流程。
启动流程终点选取
大多数的 App 在选择冷启动启动流程终点时,会选择首页 Activity 的 onWindowFocusChanged 时机,此时首页 Activity 已经可见但其内部的 View 还不可见,对于用户侧已经可以看见首页背景,同时会将首页内 View 绘制归入首刷过程中。
2.2 代码打点(函数插桩)
可以写一个统计耗时的工具类来记录整个过程的耗时情况。其中需要注意的有:
在上传数据到服务器时建议根据用户ID的尾号来抽样上报。
在项目中核心基类的关键回调函数和核心方法中加入打点。
class LaunchRecord {
companion object {
private var sStart: Long = 0
fun startRecord() {
sStart = System.currentTimeMillis()
}
fun endRecord() {
endRecord("")
}
fun endRecord(postion: String) {
val cost = System.currentTimeMillis() - sStart
println("===$postion===$cost")
}
}
}
class MyApplication : Application() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
//开始打点
LaunchRecord.startRecord()
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mTextView.viewTreeObserver.addOnDrawListener {
LaunchRecord.endRecord("onDraw")
}
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
LaunchRecord.endRecord("onWindowFocusChanged")
}
}
2.3获取方法耗时
在进程启动过程的调用方法前后加入计时器,打印调用方法耗时数据。
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
initFresco();
initBugly();
initWeex();
}
private void initWeex(){
LaunchRecord.Companion.startRecord();
InitConfig config = new InitConfig.Builder().build();
WXSDKEngine.initialize(this, config);
LaunchRecord.Companion.endRecord("initWeex");
}
private void initFresco() {
LaunchRecord.Companion.startRecord();
Fresco.initialize(this);
LaunchRecord.Companion.endRecord("initFresco");
}
private void initBugly() {
LaunchRecord.Companion.startRecord();
CrashReport.initCrashReport(getApplicationContext(), "注册时申请的APPID", false);
LaunchRecord.Companion.endRecord("initBugly");
}
}
2.4 AOP面向切面编程监控耗时
这里是自己写的 ASM+Transform+Gradle plugin完成函数耗时统计插件:https://www.jianshu.com/p/0672899a5189
可以自己平时在开发、测试过程中监测是否发生耗时过多、卡顿的情况发生。
2.5 使用Profile监控应用启动耗时
-
打开edit configuration
-
勾选profilling下面 start this recording on startup选项,标识在启动过程中做记录
点击profile app 启动按钮,然后等首页启动完成,点击stop,得到启动这个过程的启动数据。启动过程的观察类型,就选用Top down,按照时间先后从上到下的调用栈顺序来查看方法执行耗时。
这里是页面启动调用栈:
这里可以看到,启动页的activity调用的各个函数耗时,这里onCreate方法执行初始化花费377ms,setContentView花费23ms。为什么onCreate里面花费了377ms呢,因为我在onCreate中调用了costTime()函数,其他代码初始化实际花费是77ms。
costTime函数:
fun costTime() {
Thread.sleep(300)
}
至此,启动速度优化要解决的问题找到了,就是这个costTime函数影响了启动速度,需要对这块代码做异步初始化或者延迟执行。
3.启动优化操作:
3.1. 添加一个默认的启动页的Theme能从现象解决这个问题,但是并没有从根本上解决启动慢的问题。
3.2异步加载初始化
Application中主要做了各种三方组件的初始化,参数设置,启动各种服务等;
对于过多的初始化任务,我们考虑以下优化方案:
1.考虑异步初始化三方组件,不阻塞主线程;
2.延迟部分三方组件的初始化;
使用启动器来异步加载初始化任务:
启动器流程:
(1)代码Task化,启动逻辑抽象为Task
(2)根据所有任务依赖关系排序生成一个有向无环图
(3)多线程按照排序后的优先级依次执行
比如创建一个ARouterTask:
class ARouterTask(val application: BaseApplication) : Task("") {
override fun run() {
if (BuildConfig.DEBUG) {
ARouter.openLog()
ARouter.openDebug()
}
ARouter.init(application)
}
}
将task添加进project的task列表中:
fun createCommonTaskGroup() : Task {
var arouterTask = ARouterTask(this)
var refreshTask = RefreshTask()
var builder = Project.Builder()
builder.add(arouterTask)
builder.add(refreshTask)
return builder.create()
}
或者用after给task带有顺序:
Project.Builder builder = new Project.Builder();
builder.add(a);
builder.add(b).after(a);
builder.add(c).after(a);
builder.add(d).after(b, c);
builder.add(e).after(a);
Project group = builder.create();
最后,manager添加project并start各个异步任务。
AlphaManager.getInstance(applicationContext).addProject(createCommonTaskGroup())
AlphaManager.getInstance(applicationContext).start()
3.3延迟初始化
IdleHandler使用:https://www.jianshu.com/p/ab3510efe98f
3.4 懒加载
懒加载就是有些Task只有在特定的页面才会使用,这时候我们就没必要将这些Task放在Application中初始化了,我们可以将其放在进入页面后在进行初始化。
参考:
https://juejin.cn/post/7306692609497546752
https://heapdump.cn/article/3624814
https://blog.csdn.net/u011578734/article/details/109496667
https://segmentfault.com/a/1190000020904556
https://juejin.cn/post/6844904093786308622#heading-132
阿里异步启动框架
https://mp.weixin.qq.com/s/dwhBnvMe-ePa2HSreEWsUw