1.應用啟動的類型
冷啟動
冷啟動指的是該應用程序在此之前沒有被創(chuàng)建恶导,發(fā)生在應用程序首次啟動或者自上次被終止后的再次啟動。簡單的說就是app進程還沒有,需要創(chuàng)建app的進程啟動app蒋失。
比如開機后,點擊屏幕的app圖標啟動應用四敞。
冷啟動的過程主要分為兩步:
1)系統(tǒng)任務。加載并啟動應用程序拔妥;顯示應用程序的空白啟動窗口忿危;創(chuàng)建APP進程
2)APP進程任務。啟動主線程毒嫡;創(chuàng)建Activity癌蚁;加載布局;屏幕布局兜畸;繪制屏幕
其實這不就是APP的啟動流程嘛努释?所以冷啟動是會完整走完一個啟動流程的,從系統(tǒng)到進程咬摇。
溫啟動
溫啟動指的是App進程存在伐蒂,但Activity可能因為內存不足被回收,這時候啟動App不需要重新創(chuàng)建進程肛鹏,只需要執(zhí)行APP進程中的一些任務逸邦,比如創(chuàng)建Activity。
比如返回主頁后在扰,又繼續(xù)使用其他的APP缕减,時間久了或者打開的應用多了,之前應用的Activity有可能被回收了芒珠,但是進程還在桥狡。
所以溫啟動相當于執(zhí)行了冷啟動的第二過程,也就是APP進程任務皱卓,需要重新啟動線程裹芝,Activity等。
熱啟動
熱啟動就是App進程存在娜汁,并且Activity對象仍然存在內存中沒有被回收饰抒。
比如app被切到后臺徙垫,再次啟動app的過程。
所以熱啟動的開銷最少,這個過程只會把Activity從后臺展示到前臺洋幻,無需初始化元莫,布局繪制等工作深胳。
那我們所要做的啟動速度優(yōu)化呢盏浙,就是對于冷啟動這種情況進行的。
2.啟動耗時統(tǒng)計:
2.1adb命令查看耗時:
如果是本地調試的話享完,統(tǒng)計啟動時間還是很簡單的灼芭,通過命令行方式即可:
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萤衰,即應用的啟動時間,其包括 創(chuàng)建進程 + Application初始化 + Activity初始化到界面顯示 的過程猜旬。
多長時間內app啟動完成才算秒開脆栋?
1秒
既然談論的目標是秒開率,那必然是需要 1s 內完成用戶的啟動流程洒擦。
啟動流程終點選取
大多數的 App 在選擇冷啟動啟動流程終點時椿争,會選擇首頁 Activity 的 onWindowFocusChanged 時機,此時首頁 Activity 已經可見但其內部的 View 還不可見熟嫩,對于用戶側已經可以看見首頁背景秦踪,同時會將首頁內 View 繪制歸入首刷過程中。
2.2 代碼打點(函數插樁)
可以寫一個統(tǒng)計耗時的工具類來記錄整個過程的耗時情況掸茅。其中需要注意的有:
在上傳數據到服務器時建議根據用戶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面向切面編程監(jiān)控耗時
這里是自己寫的 ASM+Transform+Gradle plugin完成函數耗時統(tǒng)計插件:http://www.reibang.com/p/0672899a5189
可以自己平時在開發(fā)、測試過程中監(jiān)測是否發(fā)生耗時過多逗鸣、卡頓的情況發(fā)生裁僧。
2.5 使用Profile監(jiān)控應用啟動耗時
-
打開edit configuration
-
勾選profilling下面 start this recording on startup選項,標識在啟動過程中做記錄
點擊profile app 啟動按鈕慕购,然后等首頁啟動完成,點擊stop茬底,得到啟動這個過程的啟動數據沪悲。啟動過程的觀察類型,就選用Top down阱表,按照時間先后從上到下的調用棧順序來查看方法執(zhí)行耗時殿如。
這里是頁面啟動調用棧:
這里可以看到,啟動頁的activity調用的各個函數耗時最爬,這里onCreate方法執(zhí)行初始化花費377ms,setContentView花費23ms涉馁。為什么onCreate里面花費了377ms呢,因為我在onCreate中調用了costTime()函數爱致,其他代碼初始化實際花費是77ms烤送。
costTime函數:
fun costTime() {
Thread.sleep(300)
}
至此,啟動速度優(yōu)化要解決的問題找到了糠悯,就是這個costTime函數影響了啟動速度帮坚,需要對這塊代碼做異步初始化或者延遲執(zhí)行妻往。
3.啟動優(yōu)化操作:
3.1. 添加一個默認的啟動頁的Theme能從現象解決這個問題,但是并沒有從根本上解決啟動慢的問題试和。
3.2異步加載初始化
Application中主要做了各種三方組件的初始化讯泣,參數設置,啟動各種服務等阅悍;
對于過多的初始化任務,我們考慮以下優(yōu)化方案:
1.考慮異步初始化三方組件好渠,不阻塞主線程;
2.延遲部分三方組件的初始化节视;
使用啟動器來異步加載初始化任務:
啟動器流程:
(1)代碼Task化拳锚,啟動邏輯抽象為Task
(2)根據所有任務依賴關系排序生成一個有向無環(huán)圖
(3)多線程按照排序后的優(yōu)先級依次執(zhí)行
比如創(chuàng)建一個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使用:http://www.reibang.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