Android應(yīng)用啟動(dòng)概述及優(yōu)化

一、應(yīng)用啟動(dòng)概述

應(yīng)用啟動(dòng)的一般流程

應(yīng)用的啟動(dòng),從桌面點(diǎn)擊應(yīng)用圖標(biāo)到主界面用戶可以操作,大致遵循以下流程:


應(yīng)用程序啟動(dòng)流程圖

可以看到在應(yīng)用啟動(dòng)過程中,最重要的兩個(gè)進(jìn)程就是SystemServer和App Process。其職責(zé)劃分如下:

  • SystemServer負(fù)責(zé)應(yīng)用的啟動(dòng)流程調(diào)度、進(jìn)程的創(chuàng)建和管理、窗口的創(chuàng)建和管理(StaringWindow和AppWindow)等
  • 應(yīng)用進(jìn)程被SystemServer創(chuàng)建后,進(jìn)行一系列化的進(jìn)程初始化、組件初始化、主頁面的構(gòu)建和內(nèi)容填充等。

應(yīng)用啟動(dòng)類型

  • 冷啟動(dòng):當(dāng)啟動(dòng)應(yīng)用時(shí),后臺(tái)沒有該應(yīng)用的進(jìn)程,系統(tǒng)會(huì)重新創(chuàng)建一個(gè)新的進(jìn)程分配給該應(yīng)用,然后再根據(jù)啟動(dòng)的參數(shù),啟動(dòng)對(duì)應(yīng)的進(jìn)程組件。
    1. 場景:開機(jī)后第一次啟動(dòng)應(yīng)用 或 應(yīng)用被殺死后再次啟動(dòng)
    2. 生命周期:Process.start->Application創(chuàng)建->attachBaseContext->onCreate->onStart->onResume->Activity生命周期
    3. 啟動(dòng)速度:在幾中啟動(dòng)類型中最慢,也是我們優(yōu)化啟動(dòng)速度最大的攔路虎。
  • 溫啟動(dòng):當(dāng)啟動(dòng)應(yīng)用時(shí),后臺(tái)已有該應(yīng)用的進(jìn)程。
    1. 場景:應(yīng)用已經(jīng)啟動(dòng),返回鍵退出
    2. 生命周期:onCreate->onStart->onResume->Activity生命周期
    3. 啟動(dòng)速度:較快
  • 熱啟動(dòng):當(dāng)啟動(dòng)應(yīng)用時(shí),后臺(tái)已有該應(yīng)用的進(jìn)程??梢栽谌蝿?wù)列表中看到。
    1. 場景:Home鍵最小化應(yīng)用
    2. 生命周期:onResume->Activity生命周期
    3. 啟動(dòng)速度:快

冷啟動(dòng)的流程

  • 系統(tǒng)需要做的事:
    1. 加載及啟動(dòng)app
    2. app啟動(dòng)后立即顯示一個(gè)空白的預(yù)覽窗口
    3. 創(chuàng)建app進(jìn)程
  • 系統(tǒng)完成app進(jìn)程創(chuàng)建后,app進(jìn)程的工作:
    1. 創(chuàng)建Application對(duì)象
    2. 創(chuàng)建并啟動(dòng)主線程ActivityThread
    3. 創(chuàng)建啟動(dòng)以一個(gè)Activity
    4. inflate view
    5. 布局屏幕
    6. 執(zhí)行第一次繪制
      一旦app進(jìn)程完成了第一次繪制工作,系統(tǒng)進(jìn)程就會(huì)用main activity替換前面顯示的預(yù)覽窗口,這時(shí),用戶就可以正式開始與app的交互了。
      從冷啟動(dòng)的流程看,我們無法干預(yù)app進(jìn)程創(chuàng)建等系統(tǒng)操作,我們能干預(yù)的有
  • 啟動(dòng)窗口
  • Application生命周期回調(diào)
  • Activity生命周期回調(diào)

下面介紹一下一般App中可以從哪些方面進(jìn)行優(yōu)化:

啟動(dòng)窗口優(yōu)化

啟動(dòng)窗口,也叫啟動(dòng)頁、SplashWindow、StartingWindow等,指的是應(yīng)用啟動(dòng)時(shí)候的預(yù)覽窗口。iOS App 強(qiáng)制有一個(gè)啟動(dòng)頁,用戶點(diǎn)擊桌面 App 圖標(biāo)之后,系統(tǒng)會(huì)立即顯示這個(gè)啟動(dòng)窗口,等 App 主頁加載好之后再顯示主頁面。Android 也有類似的機(jī)制 (啟動(dòng)窗口這個(gè)是 Android 系統(tǒng)提供的),但是也提供了一個(gè)接口,讓應(yīng)用開發(fā)者設(shè)置是否顯示這個(gè)啟動(dòng)窗口(默認(rèn)是顯示),部分開發(fā)者會(huì)把這個(gè)系統(tǒng)提供的啟動(dòng)窗口禁掉,啟動(dòng)自己的窗口。
但是啟動(dòng)自己的窗口需要的時(shí)間要比直接顯示系統(tǒng)的啟動(dòng)窗口所花的時(shí)間要長,這就會(huì)導(dǎo)致用戶在使用的時(shí)候,點(diǎn)擊圖標(biāo)啟動(dòng) App 的時(shí)候,有一定的延遲,表現(xiàn)在點(diǎn)擊圖標(biāo)過了一段時(shí)間才進(jìn)行窗口動(dòng)畫進(jìn)入 App,我們要盡量避免這種情況。

  • 不要禁止系統(tǒng)默認(rèn)的啟動(dòng)窗口:即不要在主題里面設(shè)置android:windowDisablePreview = true
  • 自己定制啟動(dòng)窗口的內(nèi)容,比如將啟動(dòng)頁主題背景設(shè)置成閃屏圖片,或盡量使閃屏頁面的主題和主頁一致??梢詤⒖贾酢⒍兑舻淖龇?/li>
  • 合并閃屏和主頁面的Activity:微信的做法,不過由于微信設(shè)置了 android:windowDisablePreview , 且他在各個(gè)廠商的白名單里面,一般不會(huì)被殺,冷啟動(dòng)的機(jī)會(huì)比較少。不過也是一個(gè)可以思考的方式。

線程優(yōu)化

線程優(yōu)化主要減少CPU調(diào)度帶來的波動(dòng),讓啟動(dòng)時(shí)間更穩(wěn)定。如果啟動(dòng)過程中有太多的線程一起啟動(dòng),會(huì)給CPU帶來非常大的壓力,尤其是比較低端的機(jī)器。過多的線程同時(shí)跑會(huì)讓主線程的 Sleep 和 Runnable 狀態(tài)變多, 增加了應(yīng)用的啟動(dòng)速度,優(yōu)化的過程中要注意:

  • 控制線程數(shù)量,使用線程池
  • 檢查線程間的鎖,防止依賴等待
  • 使用合理的啟動(dòng)架構(gòu)
    • 微信內(nèi)部使用的mmkernel
    • 阿里Alpha

系統(tǒng)調(diào)度優(yōu)化

應(yīng)用啟動(dòng)的時(shí)候,如果主線程的工作過多,也會(huì)造成主線程過于繁忙,下面是幾個(gè)系統(tǒng)調(diào)度相關(guān)的點(diǎn)需要注意:

  • 啟動(dòng)過程中減少系統(tǒng)調(diào)用,避免與AMS、WMS競爭鎖。 啟動(dòng)過程中本身 AMS 和 WMS 的工作就很多,且 AMS 和 WMS 很多操作都是帶鎖的,如果此時(shí) App 再有過多的 Binder 調(diào)用與 AMS、WMS 通信,SystemServer 就會(huì)出現(xiàn)大量的鎖等待,阻塞關(guān)鍵操作。
  • 啟動(dòng)過程中不要啟動(dòng)子線程,如果好幾個(gè)進(jìn)程同時(shí)啟動(dòng),系統(tǒng)負(fù)擔(dān)則會(huì)加倍,SystemServer 也會(huì)更繁忙。
  • 啟動(dòng)過程中除了Activity之外的組件啟動(dòng)要謹(jǐn)慎,因?yàn)樗拇蠼M件的啟動(dòng)都是在主線程的,如果組件啟動(dòng)慢,占用了 Message 通道,也會(huì)影響應(yīng)用的啟動(dòng)速度。
  • Application和主Activity的onCreate中異步初始化某些代碼

GC優(yōu)化

啟動(dòng)過程中減少GC的次數(shù)

  • 避免進(jìn)行大量的字符串操作,特別是序列化和反序列化
  • 頻繁創(chuàng)建的對(duì)象考慮復(fù)用
  • 轉(zhuǎn)移到Native實(shí)現(xiàn)

IO優(yōu)化

啟動(dòng)過程中不建議進(jìn)行網(wǎng)絡(luò)IO,對(duì)磁盤IO要細(xì)扣,要清楚啟動(dòng)過程中讀了什么文件、多少個(gè)字節(jié)、 Buffer 是多大,使用了多長時(shí)間、在什么線程等一系列信息。

資源重排

利用Linux的IO讀取策略,PageCache和ReadAhead機(jī)制,按照讀取順序重新排列,減少磁盤IO次數(shù) 。具體操作可以參考支付寶 App 構(gòu)建優(yōu)化解析:通過安裝包重排布優(yōu)化 Android 端啟動(dòng)性能這篇文章。
利用文件重布局結(jié)合Pagecache 機(jī)制可以減少啟動(dòng)過程中的真正 IO 的次數(shù),簡單的說,通過文件重布局的目的,就是將啟動(dòng)階段需要用到的文件在 APK 文件中排布在一起,盡可能的利用 pagecache 機(jī)制,用最少的磁盤 IO 次數(shù),讀取盡可能多的啟動(dòng)階段需要的文件,減少 IO 開銷,從而達(dá)到提升啟動(dòng)性能的目的。

類重排

類重排的實(shí)現(xiàn)通過 ReDex 的 Interdex 調(diào)整類在 Dex 中的排列順序。Interdex 優(yōu)化不需要去分析類引用,它只需要調(diào)整 Dex 中類的順序,把啟動(dòng)時(shí)需要加載的類按順序放到主 dex 里,這個(gè)工作我們完全可以在編譯過程中實(shí)現(xiàn),而且這個(gè)優(yōu)化可以提升啟動(dòng)速度,優(yōu)化效果從 facebook 公布的數(shù)據(jù)來看也比較可觀,性價(jià)比高。具體實(shí)現(xiàn)可以參考 Redex 初探與 Interdex:Andorid 冷啟動(dòng)優(yōu)化

主頁面布局優(yōu)化

應(yīng)用主界面布局優(yōu)化是老生常談了,綜合起來無非就是下面兩點(diǎn),這個(gè)需要結(jié)合具體的界面布局去做優(yōu)化,網(wǎng)上也有比較多的資料可以查閱。

  • 通過減少冗余或者嵌套布局來降低視圖層次結(jié)構(gòu)
  • 用 ViewStub 替代在啟動(dòng)過程中不需要顯示的 UI 控件
  • 使用自定義 View 替代復(fù)雜的 View 疊加

閑時(shí)調(diào)用

IdleHandler:當(dāng) Handler 空閑的時(shí)候才會(huì)被調(diào)用,如果返回 true, 則會(huì)一直執(zhí)行,如果返回 false,執(zhí)行完一次后就會(huì)被移除消息隊(duì)列。比如,我們可以將從服務(wù)器獲取推送 Token 的任務(wù)放在延遲 IdleHandler 中執(zhí)行,或者把一些不重要的 View 的加載放到 IdleHandler 中執(zhí)行

類加載優(yōu)化

可以在 systrace 生成的文件中看到 verifyClass 過程,因?yàn)樾枰r?yàn)方法的每一個(gè)指令,所以是一個(gè)比較耗時(shí)的操作。

類加載流程

App瘦身

App 瘦身包括代碼瘦身和資源瘦身,通常的做法如下:

  • Inspect Code :Android Studio 提供的代碼審查工具,實(shí)際上是內(nèi)嵌了 Lint。
  • 代碼混淆
  • 圖片格式的選擇:對(duì)圖片的要求不高,可以換成RGB_565編碼格式的圖片,也可以對(duì)圖片本身通過工具壓縮后再引入app項(xiàng)目中
  • 接入資源混淆
  • 減少dex數(shù)量

選擇合適的啟動(dòng)框架

啟動(dòng)優(yōu)化整個(gè)流程的梳理,還要做一些進(jìn)程的判斷,要判斷某些項(xiàng)目是不是要在主進(jìn)程里加載,某些要在初始進(jìn)程里面加載。可以參考淘寶的全鏈路優(yōu)化的案例:歷時(shí)1年,上百萬行代碼!首次揭秘手淘全鏈路性能優(yōu)化(上)

啟動(dòng)網(wǎng)絡(luò)鏈路優(yōu)化

問題和優(yōu)化點(diǎn)

  • 發(fā)送處理階段:網(wǎng)絡(luò)庫bindService影響前x個(gè)請(qǐng)求,圖片并發(fā)限制圖片庫線程排隊(duì)
  • 網(wǎng)絡(luò)耗時(shí):部分請(qǐng)求響應(yīng)size大,包括 SO文件,Cache資源,圖片原圖大尺寸等
  • 返回處理:個(gè)別數(shù)據(jù)網(wǎng)關(guān)請(qǐng)求json串復(fù)雜解析嚴(yán)重耗時(shí)(3s),且歷史線程排隊(duì)設(shè)計(jì)不合適
  • 上屏阻塞:回調(diào)UI線程被阻,反映主線程卡頓嚴(yán)重。高端機(jī)達(dá)1s,低端機(jī)惡化達(dá)3s以上
  • 回調(diào)阻塞:部分業(yè)務(wù)回調(diào)執(zhí)行耗時(shí),阻塞主線程或回調(diào)線程。

優(yōu)化

  • 多次重復(fù)的請(qǐng)求,業(yè)務(wù)方務(wù)必收斂請(qǐng)求次數(shù),減少非必須請(qǐng)求。
  • 數(shù)據(jù)大的請(qǐng)求如資源文件、so文件,非啟動(dòng)必須統(tǒng)一延后或取消。
  • 業(yè)務(wù)方回調(diào)執(zhí)行阻塞主線程耗時(shí)過長整改。我們知道,肉眼可見流暢運(yùn)行,需要運(yùn)行60幀/秒, 意味著每幀的處理時(shí)間不超過16ms。針對(duì)主線程執(zhí)行回調(diào)超過16ms的業(yè)務(wù)方,推動(dòng)主線程執(zhí)行優(yōu)化。
  • 協(xié)議json串過于復(fù)雜導(dǎo)致解析耗時(shí)嚴(yán)重,網(wǎng)絡(luò)并發(fā)線程數(shù)有限,解析耗時(shí)過長意味著請(qǐng)求長時(shí)間占用MTOP線程影響其他關(guān)鍵請(qǐng)求執(zhí)行。推動(dòng)業(yè)務(wù)方handler注入使用自己的線程解析或簡化json串。

預(yù)加載

Activity 打開之前就預(yù)加載數(shù)據(jù),在 Activity 的 UI 布局初始化完成后顯示預(yù)加載的數(shù)據(jù),大大縮短啟動(dòng)時(shí)間。 可以參考這個(gè)示例:預(yù)加載:頁面啟動(dòng)速度優(yōu)化利器

保活

?;?,是應(yīng)用開發(fā)者的噩夢,也是Android廠商關(guān)注和大吉的重點(diǎn)。不過從啟動(dòng)的角度來看,如果應(yīng)用進(jìn)程不被殺,那么啟動(dòng)自然就快了,所以?;顚?duì)應(yīng)用啟動(dòng)速度也是有極大的幫助。

當(dāng)然這里說的?;睿⒉皇墙ㄗh大家用各種黑科技、相互喚醒、通知轟炸這種?;钍侄危翘峁┱嬲墓δ?,能讓用戶覺得你在后臺(tái)是合理的、可以接收的。比如在后臺(tái)的時(shí)候,資源能釋放的都釋放掉,不要一直在后臺(tái)做耗電操作,該停的服務(wù)停掉,該關(guān)的動(dòng)畫關(guān)掉。

當(dāng)然對(duì)于應(yīng)用開發(fā)者來說,上面說的都太多理想化了,而且目前的手機(jī)廠商也會(huì)很暴力,應(yīng)用到了后臺(tái)就會(huì)處理掉,不過這畢竟是一個(gè)方向,Google 也在規(guī)范應(yīng)用后臺(tái)行為和規(guī)范廠商處理應(yīng)用這兩方面都在做努力,Android 系統(tǒng)的生態(tài),還是需要應(yīng)用開發(fā)者和 Android 廠商一起取改善。

當(dāng)然?;钸€有一條路就是走跟廠商的合作,優(yōu)化后臺(tái)內(nèi)存、去掉重復(fù)拉起、去掉流氓邏輯、積極響應(yīng)低內(nèi)存警告,做好這些話后可以跟系統(tǒng)廠商聯(lián)系,談放到查殺白名單和自啟動(dòng)白名單的可行性

業(yè)務(wù)梳理

這里涉及到具體的業(yè)務(wù),每個(gè) App 都不一樣,但是所要做的事情都是一樣的,下面是邵文在高手課里面提到的:

  • 梳理清楚啟動(dòng)過程中的每一個(gè)模塊,哪些是一定需要的,那些是可以砍掉,那些是可以懶加載的。
  • 根據(jù)不同的業(yè)務(wù)場景決定不同的啟動(dòng)模式。
  • 懶加載防止集中化

可以把具體的業(yè)務(wù)分為下面四個(gè)維度:

  • 必要且耗時(shí):啟動(dòng)初始化,考慮用線程來初始化
  • 必要不耗時(shí):首頁繪制
  • 非必要但耗時(shí):數(shù)據(jù)上報(bào)、插件初始化
  • 非必要不耗時(shí):不用想,這塊直接去掉,在需要用的時(shí)再加載

然后按需進(jìn)行加載優(yōu)化
優(yōu)化方向

業(yè)務(wù)優(yōu)化

  1. 優(yōu)化業(yè)務(wù)中的代碼效率,抓大放小,先從比較明顯的瓶頸處下手,逐步進(jìn)行優(yōu)化;
  2. 歷史債務(wù)要償還,歷史的代碼要重構(gòu),不能一直拖著

具體的業(yè)務(wù)會(huì)有具體的優(yōu)化場景,比如:

  1. 數(shù)據(jù)庫及IO操作都移到工作線程,并且設(shè)置線程優(yōu)先級(jí)為THREAD_PRIORITY_BACKGROUND,這樣工作線程最多能獲取到10%的時(shí)間片,優(yōu)先保證主線程執(zhí)行
  2. 流程梳理,延后執(zhí)行;實(shí)際上,這一步對(duì)項(xiàng)目啟動(dòng)加速最有效果。通過流程梳理發(fā)現(xiàn)部分流程調(diào)用時(shí)機(jī)偏失等, 例如
- 更新等操作無需在首屏尚未展示就調(diào)用,造成資源競爭
- 調(diào)用了IOS為了規(guī)避審核而做的開關(guān),造成網(wǎng)絡(luò)請(qǐng)求密集
- 自有統(tǒng)計(jì)在Application的調(diào)用里創(chuàng)建數(shù)量固定為5的線程池,造成資源競爭
- 修改廣告閃屏邏輯為下次生效
  1. 去掉無效或未被執(zhí)行的代碼
  2. 去掉調(diào)用三方SDK里或者Demo里的多余代碼
  3. 信息緩存,常用信息只在第一次獲取,之后從緩存中取
  4. 項(xiàng)目是多進(jìn)程架構(gòu),只在主進(jìn)程執(zhí)行Application的onCreate()

減少Activity的跳轉(zhuǎn)層次

StartingWindow 會(huì)在用戶點(diǎn)擊 App 后立即創(chuàng)建并顯示(前提是 App 沒有禁止 StartingWindow),在 AppWindow 創(chuàng)建好之后,StartingWindow 消失,AppWindow 顯示

  • 默認(rèn) App 的啟動(dòng)窗口流程:StartingWindow(SystemWindow)——>MainActivity(AppWindow)
  • 大部分三方App的啟動(dòng)流程:StartingWindow(SystemWindow) ——> SplashActivity(AppWindow)——> MainActivity(AppWindow)
  • 糟糕一點(diǎn)的啟動(dòng)流程:StartingWindow(SystemWindow) ——> MainActivity(AppWindow) ——> SplashActivity(AppWindow)——> MainActivity(AppWindow)
  • 更糟糕的啟動(dòng)流程(去掉了 StartingWindow):SplashActivity(AppWindow)——> MainActivity(AppWindow)

其實(shí)對(duì)用戶來說,第一種啟動(dòng)流程是最好的,只涉及到一次窗口的切換;但是部分 App 由于廣告頁的需求,會(huì)使用第二種流程 ;但是盡量不要使用第三種和第四種啟動(dòng)流程,體驗(yàn)非常不好。

廠商優(yōu)化

除了 App 自身的優(yōu)化之外,Android 框架對(duì)應(yīng)用啟動(dòng)也是非常關(guān)注的,做了比較多的優(yōu)化,下面簡單說一下思路,各個(gè)廠商的實(shí)現(xiàn)也不太一樣,但是基本上都會(huì)有,有些是硬核代碼優(yōu)化,有的是利用系統(tǒng)策略做優(yōu)化。廠商的策略各不相同,這里只是簡單的提一下思路。

PreFork

Android Q 加入了 PreFork 機(jī)制,會(huì)先 fork 幾個(gè)空進(jìn)程,當(dāng) App 啟動(dòng)的時(shí)候,可以直接復(fù)用這幾個(gè)空進(jìn)程,而不用重新去 fork

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容