Android性能優(yōu)化[啟動(dòng)優(yōu)化]

啟動(dòng)優(yōu)化的目的

APP啟動(dòng)如果得到很好的優(yōu)化惭适,增強(qiáng)用戶體驗(yàn)增加用戶流量笙瑟;如果app啟動(dòng)時(shí)間過(guò)長(zhǎng)影響用戶體驗(yàn),從而會(huì)造成流失用戶癞志。所以做啟動(dòng)優(yōu)化是有必須的往枷。

谷歌官方針對(duì)以下三種啟動(dòng)方式做了詳細(xì)的介紹,具體可以參考https://developer.android.com/topic/performance/vitals/launch-time凄杯。從官網(wǎng)得知APP啟動(dòng)所需的時(shí)間為:冷啟動(dòng)時(shí)間>溫啟動(dòng)時(shí)間>熱啟動(dòng)時(shí)間

啟動(dòng)分三種:

  • 冷啟動(dòng)

    當(dāng)應(yīng)用在設(shè)備開(kāi)機(jī)或者系統(tǒng)主動(dòng) kill APP 進(jìn)程之后错洁,用戶點(diǎn)擊桌面icon圖標(biāo)啟動(dòng),稱之為冷啟動(dòng)戒突。

  • 溫啟動(dòng)

    溫啟動(dòng)是APP進(jìn)程還存活屯碴,因?yàn)閮?nèi)存不足Activity被回收了,當(dāng)再次啟動(dòng)APP時(shí)就會(huì)重新執(zhí)行Activity生命周期膊存,布局繪制等操作导而。

  • 熱啟動(dòng)

    當(dāng)后臺(tái)存在該應(yīng)用的進(jìn)程或者服務(wù)時(shí),用戶點(diǎn)擊icon圖標(biāo)啟動(dòng)隔崎,稱之為熱啟動(dòng)今艺。一般是用戶按了home鍵回到桌面,或者返回鍵沒(méi)有殺進(jìn)程爵卒,或者app本身做了進(jìn)程重啟的機(jī)制洼滚。

冷啟動(dòng)

冷啟動(dòng)的的過(guò)程:

  • 加載和啟動(dòng)APP
  • 顯示一個(gè)白色的Window窗口
  • 創(chuàng)建APP進(jìn)程
  • 創(chuàng)建Application
  • 創(chuàng)建MainActivity
  • 加載布局
  • 首幀繪制

應(yīng)用的啟動(dòng)過(guò)程

??冷啟動(dòng)啟動(dòng)流程——當(dāng)點(diǎn)擊app的啟動(dòng)圖標(biāo)時(shí),安卓系統(tǒng)會(huì)從Zygote進(jìn)程中fork創(chuàng)建出一個(gè)新的進(jìn)程分配給該應(yīng)用技潘,之后會(huì)依次創(chuàng)建和初始化Application類(attachBaseContext()–>OnCreate())、創(chuàng)建MainActivity類千康、加載主題樣式Theme中的 windowBackground等屬性設(shè)置給MainActivity以及配置Activity層級(jí)上的一些屬性享幽、再inflate布局、當(dāng)onCreate/onStart/onResume方法都走完了拾弃,最后才進(jìn)行contentView的measure/layout/draw顯示在界面上值桩,所以直到這里,應(yīng)用的第一次啟動(dòng)才算完成豪椿,這時(shí)候看到的界面也就是首幀奔坟。

大致流程如下:

  • 點(diǎn)擊桌面圖標(biāo)携栋,Launcher會(huì)啟動(dòng)程序默認(rèn)的Acticity,之后再按照程序的邏輯啟動(dòng)各種Activity咳秉。
  • 啟動(dòng)Activity通過(guò)應(yīng)用程序框架層的ActivityManagerService服務(wù)啟動(dòng)(Service也是由ActivityManagerService來(lái)啟動(dòng)的)婉支。ActivityManagerService是一個(gè)非常重要的接口,它負(fù)責(zé)啟動(dòng)和管理Activity和Service澜建。
    ??1. 無(wú)論是通過(guò)Launcher來(lái)啟動(dòng)Activity向挖,還是通過(guò)Activity內(nèi)部調(diào)用startActivity接口來(lái)啟動(dòng)新的Activity,都通過(guò)Binder進(jìn)程間通信進(jìn)入到ActivityManagerService進(jìn)程中炕舵,并且調(diào)用ActivityManagerService.startActivity接口何之。
    ??2. ActivityManagerService調(diào)用ActivityStack.startActivityMayWait來(lái)做準(zhǔn)備要啟動(dòng)的Activity的相關(guān)信息。
    ??3. ActivityStack通知ApplicationThread要進(jìn)行Activity啟動(dòng)調(diào)度了咽筋,這里的ApplicationThread代表的是調(diào)用ActivityManagerService.startActivity接口的進(jìn)程溶推,對(duì)于通過(guò)點(diǎn)擊應(yīng)用程序圖標(biāo)的情景來(lái)說(shuō),這個(gè)進(jìn)程就是Launcher了奸攻,而對(duì)于通過(guò)在Activity內(nèi)部調(diào)用startActivity的情景來(lái)說(shuō)蒜危,這個(gè)進(jìn)程就是這個(gè)Activity所在的進(jìn)程了。
    ??4. ApplicationThread不執(zhí)行真正的啟動(dòng)操作舞箍,它通過(guò)調(diào)用ActivityManagerService.activityPaused接口進(jìn)入到ActivityManagerService進(jìn)程中舰褪,看看是否需要?jiǎng)?chuàng)建新的進(jìn)程來(lái)啟動(dòng)Activity。
    ??5. 對(duì)于通過(guò)點(diǎn)擊應(yīng)用程序圖標(biāo)來(lái)啟動(dòng)Activity的情景來(lái)說(shuō)疏橄,ActivityManagerService在這一步中占拍,會(huì)調(diào)用startProcessLocked來(lái)創(chuàng)建一個(gè)新的進(jìn)程,而對(duì)于通過(guò)在Activity內(nèi)部調(diào)用startActivity來(lái)啟動(dòng)新的Activity來(lái)說(shuō)捎迫,這一步是不需要執(zhí)行的晃酒,因?yàn)樾碌腁ctivity就在原來(lái)的Activity所在的進(jìn)程中進(jìn)行啟動(dòng)。
    ?? 6. ActivityManagerServic調(diào)用ApplicationThread.scheduleLaunchActivity接口窄绒,通知相應(yīng)的進(jìn)程執(zhí)行啟動(dòng)Activity的操作贝次。
    ??7. ApplicationThread把這個(gè)啟動(dòng)Activity的操作轉(zhuǎn)發(fā)給ActivityThread,ActivityThread通過(guò)ClassLoader導(dǎo)入相應(yīng)的Activity類彰导,然后把它啟動(dòng)起來(lái)蛔翅。

所以相應(yīng)的優(yōu)化方案

  • 減少 onCreate方法的工作量。
  • 不要讓Application參與業(yè)務(wù)邏輯位谋。
  • 不要在Application中做耗時(shí)操作山析,一些初始化操作可以開(kāi)啟子線程來(lái)完成。
  • 不要以靜態(tài)變量方式在Application中保存數(shù)據(jù)掏父。
  • 布局優(yōu)化/mainThread盡量延遲初始化笋轨。
  • 啟動(dòng)畫(huà)面的初始化可以使用設(shè)置主題背景的方式,速度回更快。

啟動(dòng)主題優(yōu)化

冷啟動(dòng)階段:

  1. 加載并啟動(dòng)應(yīng)用程序爵政。
  2. 啟動(dòng)后立即顯示應(yīng)用程序空白的啟動(dòng)窗口仅讽。
  3. 創(chuàng)建應(yīng)用程序進(jìn)程。

??啟動(dòng)主題優(yōu)化钾挟,就是應(yīng)用程序在冷啟動(dòng)的時(shí)候(1~2階段)洁灵,設(shè)置啟動(dòng)窗口的主題。因?yàn)楝F(xiàn)在 App 應(yīng)用啟動(dòng)都會(huì)先進(jìn)入一個(gè)閃屏頁(yè)(LaunchActivity) 來(lái)展示應(yīng)用信息等龙。

默認(rèn)主題情況

??如果我們對(duì)App沒(méi)有做處理(設(shè)置了默認(rèn)主題)处渣,并且在 Application 的onCreate()初始化了其它第三方的服務(wù)(假設(shè)需要加載2000ms),那么系統(tǒng)默認(rèn)會(huì)在啟動(dòng)應(yīng)用程序的時(shí)候啟動(dòng)空白窗口 (產(chǎn)生一個(gè)白屏)蛛砰,直到 App 應(yīng)用程序的入口 Activity 創(chuàng)建成功罐栈,視圖繪制完畢。( 大概是onWindowFocusChanged方法回調(diào)的時(shí)候 )

解決白屏或黑屏問(wèn)題
??在默認(rèn)主題的情況下并且Application初始化服務(wù)耗時(shí)過(guò)長(zhǎng)就會(huì)出現(xiàn)白屏泥畅。如果出現(xiàn)白屏或黑屏閃現(xiàn)荠诬,查看設(shè)置Style的Theme。

透明主題優(yōu)化

  <style name="AppTheme2" parent="Theme.AppCompat.Light.DarkActionBar">
    ...... 
    <item name="android:windowIsTranslucent">true</item> 
    <item name="android:windowNoTitle">true</item> 
  </style>

??為了解決啟動(dòng)窗口白屏問(wèn)題位仁,許多開(kāi)發(fā)者都是使用透明主題來(lái)解決這個(gè)問(wèn)題柑贞,但是治標(biāo)不治本。雖然解決了上面這個(gè)問(wèn)題聂抢,但是仍然有些不足钧嘶。已經(jīng)測(cè)試雖然無(wú)白屏了,不過(guò)從點(diǎn)擊到App仍然存在視覺(jué)延遲琳疏。

設(shè)置閃屏圖片主題

<style name="AppTheme1" parent="Theme.AppCompat.Light.DarkActionBar">  
      <item name="android:windowBackground">@drawable/icon_bg</item>//閃屏頁(yè)圖片
      <item name="android:windowFullscreen">true</item>
      <item name="android:windowContentOverlay">@null</item>
</style>

??為了更順滑無(wú)縫銜接閃屏頁(yè)有决,可以在啟動(dòng) Activity 的 Theme中設(shè)置閃屏頁(yè)圖片(如上面代碼),這樣啟動(dòng)窗口的圖片就會(huì)是閃屏頁(yè)圖片空盼,而不是白屏书幕。這樣設(shè)置的話,就會(huì)在冷啟動(dòng)的時(shí)候揽趾,展示閃屏頁(yè)的圖片台汇,等App進(jìn)程初始化加載入口 Activity (也是閃屏頁(yè)) 就可以無(wú)縫銜接。其實(shí)這種方式并沒(méi)有真正的加速應(yīng)用進(jìn)程的啟動(dòng)速度篱瞎,而只是通過(guò)用戶視覺(jué)效果帶來(lái)的優(yōu)化體驗(yàn)苟呐。

代碼優(yōu)化

??當(dāng)然上面使用設(shè)置主題的方式優(yōu)化用戶體驗(yàn)效果治標(biāo)不治本,關(guān)鍵還在于對(duì)代碼的優(yōu)化俐筋。首先我們可以統(tǒng)計(jì)一下應(yīng)用冷啟動(dòng)的時(shí)間掠抬。

啟動(dòng)耗時(shí)的測(cè)量方式

  • 通過(guò)adb 命令統(tǒng)計(jì)啟動(dòng)耗時(shí)時(shí)間
  • 手動(dòng)埋點(diǎn)計(jì)算時(shí)間差

adb 命令統(tǒng)計(jì)
adb命令 : adb shell am start -S -W 包名/啟動(dòng)類的全限定名 , -S 表示重啟當(dāng)前應(yīng)用
更多的ADB命令

G:\AS_Project_Work\Demo>adb shell am start -S -W com.lu.demo/com.lu.demo.MainActivity
Stopping: com.lu.demo
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.lu.demo/.MainActivity }
Status: ok
Activity: com.lu.demo/.MainActivity
ThisTime: 508
TotalTime: 508
WaitTime: 586
Complete

ThisTime : 最后一個(gè) Activity 的啟動(dòng)耗時(shí)(例如從 LaunchActivity - >MainActivity「adb命令輸入的Activity」 , 只統(tǒng)計(jì) MainActivity 的啟動(dòng)耗時(shí))校哎。
TotalTime : 啟動(dòng)一連串的 Activity 總耗時(shí).(有幾個(gè)Activity 就統(tǒng)計(jì)幾個(gè))。
WaitTime : 應(yīng)用進(jìn)程的創(chuàng)建過(guò)程 + TotalTime 。

ThisTime<=TotalTime<=WaitTime

這時(shí)應(yīng)用就啟動(dòng)了(注意:此時(shí)是冷啟動(dòng))闷哆,當(dāng)我點(diǎn)擊home將應(yīng)用切到后臺(tái)之后腰奋,再次執(zhí)行adb shell am start -W 包名/啟動(dòng)類的全限定名的命令,這時(shí)可以發(fā)現(xiàn)抱怔,這三個(gè)時(shí)間都縮短了劣坊,而且提示Warning: Activity not started, its current task has been brought to the front表示當(dāng)前啟動(dòng)是一個(gè)熱啟動(dòng)

G:\AS_Project_Work\Demo>adb shell am start -W com.lu.demo/com.lu.demo.MainActivity
Stopping: com.lu.demo
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.lu.demo/.MainActivity }
Warning: Activity not started, its current task has been brought to the front
Status: ok
Activity: com.lu.demo/.MainActivity
ThisTime: 508
TotalTime: 508
WaitTime: 586
Complete

手動(dòng)埋點(diǎn)計(jì)算時(shí)間差
手動(dòng)埋點(diǎn)在什么時(shí)機(jī)開(kāi)始計(jì)算時(shí)間屈留,什么時(shí)候結(jié)束計(jì)算時(shí)間局冰?一般會(huì)在 Application attachBaseContext 中開(kāi)始埋點(diǎn)計(jì)算開(kāi)始時(shí)間,然后在 Activity 中第一個(gè) View 中preDraw 時(shí)結(jié)束埋點(diǎn)計(jì)算結(jié)束時(shí)間灌危,兩者之差就是啟動(dòng)耗時(shí)時(shí)間康二。從上面的啟動(dòng)過(guò)程得知,一般會(huì)在 Application attachBaseContext 中開(kāi)始埋點(diǎn)計(jì)算開(kāi)始時(shí)間勇蝙,然后在啟動(dòng)MainActivity 中第一個(gè) View 中preDraw 時(shí)結(jié)束埋點(diǎn)計(jì)算結(jié)束時(shí)間沫勿。參照啟動(dòng)過(guò)程下面開(kāi)始代碼實(shí)現(xiàn):

//Application.java
@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    //開(kāi)始計(jì)算啟動(dòng)時(shí)間(埋點(diǎn))
    LaunchTime.startRecord();
}

//MainActivity.java
TextView text = findViewById(R.id.text);
text.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        // 結(jié)束計(jì)算啟動(dòng)時(shí)間(埋點(diǎn))
        LaunchTime.stopRecord("TextView preDraw");
        return true;
    }
});

//LaunchTime.java
public class LaunchTime {
    private static final String TAG = LaunchTime.class.getSimpleName();
    private static long startTime = 0;
    //開(kāi)始記錄計(jì)時(shí)
    public static void startRecord() {
        startTime = System.currentTimeMillis();
    }
    //停止計(jì)時(shí) 打印出時(shí)間差
    public static void stopRecord(String tag) {
        //打印日志
        Log.e(TAG, tag + "CostTime:" + (System.currentTimeMillis() - startTime));
    }
}

//打印日志
06-1267 21:38:37.230 1701-1701/? E/LaunchTime: TextView preDrawCostTime:398

手動(dòng)計(jì)算啟動(dòng)耗時(shí)時(shí)間是比較精確的方式,可以帶到線上使用味混,不過(guò)需要注意結(jié)束埋點(diǎn)的時(shí)機(jī)产雹,這個(gè)時(shí)機(jī)也可以選擇 addOnDrawListener 但是這個(gè)是在API16以上才能使用。

優(yōu)秀的啟動(dòng)優(yōu)化文章

總結(jié)

  • 記錄啟動(dòng)優(yōu)化日后好復(fù)習(xí)翁锡。
  • 了解APP啟動(dòng)過(guò)程蔓挖,更好設(shè)置優(yōu)化方案。
  • 了解主題啟動(dòng)優(yōu)化方案處理白屏黑屏閃現(xiàn)等問(wèn)題馆衔。
  • 代碼優(yōu)化及啟動(dòng)耗時(shí)測(cè)量方式瘟判,方案選擇。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末哈踱,一起剝皮案震驚了整個(gè)濱河市荒适,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌开镣,老刑警劉巖刀诬,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異邪财,居然都是意外死亡陕壹,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門树埠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)糠馆,“玉大人,你說(shuō)我怎么就攤上這事怎憋∮致担” “怎么了九昧?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)毕匀。 經(jīng)常有香客問(wèn)我铸鹰,道長(zhǎng),這世上最難降的妖魔是什么皂岔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任蹋笼,我火速辦了婚禮,結(jié)果婚禮上躁垛,老公的妹妹穿的比我還像新娘剖毯。我一直安慰自己,他們只是感情好教馆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布逊谋。 她就那樣靜靜地躺著,像睡著了一般活玲。 火紅的嫁衣襯著肌膚如雪涣狗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天舒憾,我揣著相機(jī)與錄音镀钓,去河邊找鬼。 笑死镀迂,一個(gè)胖子當(dāng)著我的面吹牛丁溅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播探遵,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼窟赏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了箱季?” 一聲冷哼從身側(cè)響起涯穷,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎藏雏,沒(méi)想到半個(gè)月后拷况,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掘殴,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年赚瘦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奏寨。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡起意,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出病瞳,到底是詐尸還是另有隱情揽咕,我是刑警寧澤悲酷,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站心褐,受9級(jí)特大地震影響舔涎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逗爹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嚎于。 院中可真熱鬧掘而,春花似錦、人聲如沸于购。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肋僧。三九已至斑胜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嫌吠,已是汗流浹背止潘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辫诅,地道東北人凭戴。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像炕矮,于是被迫代替她去往敵國(guó)和親么夫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 一肤视、前言 APP優(yōu)化是我們進(jìn)階高級(jí)開(kāi)發(fā)工程師的必經(jīng)之路档痪,而APP啟動(dòng)速度的優(yōu)化,也是我們開(kāi)啟APP優(yōu)化的第一步邢滑。用...
    Juslt閱讀 707評(píng)論 0 0
  • 索引 Android中的App啟動(dòng)腐螟,是從用戶點(diǎn)擊應(yīng)用圖標(biāo)的那一刻起,到應(yīng)用界面顯示出來(lái)的過(guò)程殊鞭。這段過(guò)程有的應(yīng)用耗時(shí)...
    tianyl閱讀 560評(píng)論 0 1
  • 請(qǐng)保持淡定遭垛,分析代碼,記撞俨印:性能很重要锯仪。 啟動(dòng)時(shí)間優(yōu)化 毫無(wú)疑問(wèn),應(yīng)用的啟動(dòng)速度越快越好趾盐。 本文可以幫助你優(yōu)化應(yīng)用...
    Mupceet閱讀 11,380評(píng)論 5 19
  • 應(yīng)用啟動(dòng)類型 冷啟動(dòng)場(chǎng)景:開(kāi)機(jī)后第一次啟動(dòng)應(yīng)用 或者 應(yīng)用被殺死后再次啟動(dòng)生命周期:Process.start->...
    please邊去閱讀 2,190評(píng)論 0 2
  • 最近寶寶出事了 這讓我想到一部電影 《風(fēng)箏》 影片和現(xiàn)實(shí)有類似的地方 今天庶喜,小男孩就帶你走進(jìn)這個(gè)故事 本片于201...
    流螢沐川閱讀 365評(píng)論 0 0