Android應用啟動速度優(yōu)化全解

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末抗楔,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子拦坠,更是在濱河造成了極大的恐慌连躏,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贞滨,死亡現場離奇詭異入热,居然都是意外死亡,警方通過查閱死者的電腦和手機晓铆,發(fā)現死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門勺良,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人骄噪,你說我怎么就攤上這事尚困。” “怎么了链蕊?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵事甜,是天一觀的道長。 經常有香客問我滔韵,道長逻谦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任陪蜻,我火速辦了婚禮邦马,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己勇婴,他們只是感情好忱嘹,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耕渴,像睡著了一般拘悦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上橱脸,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天础米,我揣著相機與錄音,去河邊找鬼添诉。 笑死屁桑,一個胖子當著我的面吹牛,可吹牛的內容都是我干的栏赴。 我是一名探鬼主播蘑斧,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼须眷!你這毒婦竟也來了竖瘾?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤花颗,失蹤者是張志新(化名)和其女友劉穎捕传,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體扩劝,經...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡庸论,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了棒呛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聂示。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖簇秒,靈堂內的尸體忽然破棺而出鱼喉,到底是詐尸還是另有隱情,我是刑警寧澤宰睡,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站气筋,受9級特大地震影響拆内,放射性物質發(fā)生泄漏。R本人自食惡果不足惜宠默,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一麸恍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦抹沪、人聲如沸刻肄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽敏弃。三九已至,卻和暖如春噪馏,著一層夾襖步出監(jiān)牢的瞬間麦到,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工欠肾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瓶颠,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓刺桃,卻偏偏與公主長得像粹淋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瑟慈,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內容