背景
前面我們分析了 App 啟動(dòng)流程分析(基于 Android 10) ,這次我們一鼓作氣,來擼一擼 App 啟動(dòng)優(yōu)化,本文主要就一些常規(guī)手段做一些梳理,畢竟不同的 App 要優(yōu)化的目的會(huì)有一些不同和側(cè)重。
應(yīng)用啟動(dòng)類型(冷啟動(dòng)、溫啟動(dòng)、熱啟動(dòng))
冷啟動(dòng)
冷啟動(dòng)是指應(yīng)用從頭開始啟動(dòng),冷啟動(dòng)開始后,系統(tǒng)會(huì)做以下事情:
- 加載并啟動(dòng)應(yīng)用。
- 再啟動(dòng)后立即顯示應(yīng)用的空白啟動(dòng)窗口(不做優(yōu)化時(shí)的白屏現(xiàn)象)。
- 創(chuàng)建應(yīng)用進(jìn)程。
創(chuàng)建應(yīng)用進(jìn)程可分為以下階段:
- 創(chuàng)建應(yīng)用對(duì)象。
- 啟動(dòng)主線程。
- 創(chuàng)建主 Activity。
- 擴(kuò)充視圖。
- 布局屏幕。
- 執(zhí)行初始繪制。
- 把當(dāng)先顯示的后窗口(即前面提到的白屏窗口)替換為主 Activity,回調(diào)生命周期方法。
冷啟動(dòng)在幾種啟動(dòng)類型中最慢,一般我們做啟動(dòng)優(yōu)化大部分工作也是消耗在這里。
溫啟動(dòng)
溫啟動(dòng)比冷啟動(dòng)的效率高一點(diǎn),比如說用戶退出應(yīng)用后(不是按 Home 鍵退后臺(tái)),馬上又打開 App,這時(shí)候進(jìn)程大概率會(huì)繼續(xù)運(yùn)行,即免去了創(chuàng)建進(jìn)程那一步,而直接創(chuàng)建主 Activity 并回調(diào)生命周期。
熱啟動(dòng)
熱啟動(dòng)在這三種啟動(dòng)類型中開銷最低,一般來說就是應(yīng)用的 Activity 都還駐留在內(nèi)存中,應(yīng)用無須再創(chuàng)建 Activity 實(shí)例,布局的繪制和呈現(xiàn)。一個(gè)比較的場(chǎng)景是用戶按 Home 鍵然后在系統(tǒng)殺死進(jìn)程前重新進(jìn)入 App。
優(yōu)化分析測(cè)量工具
我們首先創(chuàng)建一個(gè)空項(xiàng)目,如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.tandeneck.launchtime">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".SplashActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
class SplashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
}
}
首先要明確啟動(dòng)優(yōu)化的目的是使我們的應(yīng)用啟動(dòng)速度更快,讓用戶所點(diǎn)即所達(dá),解決問題的前提是找出問題,因此我們要確定啟動(dòng)的時(shí)間,在哪里耗時(shí)嚴(yán)重,下面就介紹幾種常用的方法來幫助我們找到耗時(shí)的原因。
1. Logcat 中 篩選 Displayed 關(guān)鍵字
這個(gè) Displayed 的值代表從啟動(dòng)進(jìn)程到第一個(gè) Activity 完成繪制所經(jīng)過的時(shí)間。如下圖:創(chuàng)建一個(gè)空項(xiàng)目并運(yùn)行,發(fā)現(xiàn)啟動(dòng)需要 462 ms,當(dāng)然這個(gè)值每一次都會(huì)有點(diǎn)差異的:

2. 使用 ADB Shell Activity Manager 命令運(yùn)行應(yīng)用測(cè)量顯示時(shí)間,命令如下:
adb shell am start -S -W [packageName]/[ packageName. AppstartActivity]
-S 表示在啟動(dòng) Activity,強(qiáng)行停止目標(biāo)應(yīng)用,-W 表示等待啟動(dòng)完成,更多信息有興趣的同學(xué)可以查看 adb 命令
在 Terminal 運(yùn)行上面命令可得到以下信息:

可以發(fā)現(xiàn) LaunchState 為 COLD,代表的是冷啟動(dòng),還有 TotalTime 和 WaitTime,可以發(fā)現(xiàn) TotalTime 和 Diapalyed 打印處理的值是一樣的,而 WaitTime 會(huì)大點(diǎn),因?yàn)樗鼤?huì)把系統(tǒng)初始化的一些工作時(shí)間算進(jìn)去,而這部分的時(shí)間我們是比較難進(jìn)行優(yōu)化的,因此我們關(guān)注 TotalTime 即可。
前面這兩種方法可以幫助我們對(duì) App 啟動(dòng)的速度有個(gè)概覽,但是具體到哪個(gè)方法耗時(shí),哪個(gè)調(diào)用時(shí)間長(zhǎng)我們是無法得知的。下面就介紹一些檢測(cè)耗時(shí)操作的方法。
3. 代碼埋點(diǎn)
乍聽起來好高大上的感覺,其實(shí)就是打印日志,在方法執(zhí)行開始前獲取當(dāng)前時(shí)間,再在方法結(jié)束后獲取時(shí)間,兩個(gè)時(shí)間相減即可得到方法執(zhí)行時(shí)間,相信大家都有試過這種方法。這樣會(huì)寫很多樣板代碼,可以引入第三方庫(kù)比如 JakeWharton 的 Hugo 來減少樣板代碼,這個(gè)庫(kù)可以通過注解的方式獲取執(zhí)行時(shí)間,主要運(yùn)用了 AOP 技術(shù),不過這個(gè)庫(kù)有點(diǎn)年頭了,有興趣的同學(xué)還是可以去了解下的。
4.使用 TraceView、SysTrace 等性能分析工具
TraceView 和 SysTrace 這些都是分析性能的神器,由于篇幅原因,這里不做具體展開,有興趣的同學(xué)可以自行了解相關(guān)資料,或者期待下我后續(xù)的博文。
優(yōu)化手段
1. 啟動(dòng)窗口優(yōu)化
前面也提到過,默認(rèn)情況下會(huì)啟動(dòng)一個(gè)空白窗口,如下圖:

白屏窗口雖然短暫,但是還是可以明顯感知它的存在,這還是在簡(jiǎn)單的 Demo 情況下,而且測(cè)試手機(jī)性能也算中規(guī)中矩。
為了應(yīng)對(duì)這個(gè)問題,有些同學(xué)會(huì)用透明主題來解決,如下:
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowFullscreen">true</item>
<item name="android:windowIsTranslucent">true</item>
</style>
我們簡(jiǎn)單來看下效果:

哎?空白窗口消失啦,不過細(xì)心的同學(xué)會(huì)發(fā)現(xiàn)點(diǎn)擊后會(huì)有點(diǎn)延時(shí),這是因?yàn)槲覀冊(cè)O(shè)置的透明主題起作用了,即我們之前看到的白屏窗口現(xiàn)在是透明的,所以會(huì)延遲。這時(shí),如果我們可以把透明主題替換為閃屏頁(yè)圖片,比如下面這張圖:

style 設(shè)置為:
<style name="MyTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowBackground">@drawable/bg</item>
</style>
然后設(shè)置 SplashActivityActivity 的啟動(dòng) Theme:
<activity android:name=".SplashActivity"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
效果如下:

可以看到,首先顯示的是在主題中設(shè)置的背景圖,然后是 SplashActivity (下面的那行文本其實(shí)是SplashActivity的布局文件),最后是 MainActivity,整個(gè)流程是這樣:

啟動(dòng)頁(yè)主題背景圖在某些手機(jī)會(huì)出現(xiàn)拉伸的情況,可以通過使用點(diǎn) 9 圖或者使用 layer-list 來解決。
2. Application 優(yōu)化和主 Activity 優(yōu)化。
以上的這種優(yōu)化只是視覺優(yōu)化,并不能真正減少用戶的啟動(dòng)時(shí)間,用大白話說就是 Application 和 主 Activity 創(chuàng)建的時(shí)間該是多少就是多少。實(shí)際項(xiàng)目中并不會(huì)像 Demo 這樣只是一個(gè)空殼 Applicaiton 和 Activity,比如在 Application 中會(huì)做許多繁重的初始化操作。這種情況下就要結(jié)合業(yè)務(wù)要進(jìn)行優(yōu)化啦,首先通過工具比如 Systrace 獲取耗時(shí)的函數(shù),然后對(duì)耗時(shí)的函數(shù)進(jìn)行優(yōu)化,如果放在子線程中加載不影響業(yè)務(wù)的情況,則優(yōu)先選擇放在子線程中加載。主 Activity 的優(yōu)化同理,不過主 Activity 涉及到界面,還可以從頁(yè)面優(yōu)化的方向著手,無非就是減少冗余或者嵌套的布局來減少底視圖層次結(jié)構(gòu),用 ViewStub 替代在啟動(dòng)過程紅不需要顯示的 UI 控件。
3.類重排
類重排的實(shí)現(xiàn)通過 ReDex 的 Interdex 調(diào)整類在 Dex 中的排列順序,把啟動(dòng)時(shí)需要加載的類按順序放在主 dex 里。具體實(shí)現(xiàn)可以參考 Redex 初探與 Interdex:Andorid 冷啟動(dòng)優(yōu)化。
4.減少冷啟動(dòng)的次數(shù)
從前面得知,冷啟動(dòng)的耗時(shí)是最長(zhǎng)的,因此我們可以在用戶非主動(dòng)退出應(yīng)用的情況下不再退出進(jìn)程。在我們的 Activity 棧底 activity (一般是 MainActivity)加入以下代碼:
override fun onBackPressed() {
// super.onBackPressed()
moveTaskToBack(true)
}
moveTaskToBack 的作用是把 Activity 隱藏在后臺(tái),相當(dāng)于觸發(fā) Home 鍵的效果。
5. 其他一些常規(guī)優(yōu)化
-
,通過減少 CPU 調(diào)度帶來的波動(dòng),讓啟動(dòng)時(shí)間更穩(wěn)定,如果啟動(dòng)過程中有太多的線程一起啟動(dòng),會(huì)給 CPU 帶來非常大的壓力。
-
,啟動(dòng)過程中減少 GC 的次數(shù)。如避免進(jìn)行大量的字符串操作,特別是序列化和反序列化;如避免重復(fù)創(chuàng)建對(duì)象,出現(xiàn)這種情況,可以考慮復(fù)用。
-
,啟動(dòng)過程 IO 負(fù)載較高,盡量較少網(wǎng)絡(luò) IO 和磁盤 IO。
-
,啟動(dòng)的應(yīng)用如果需要大內(nèi)存,而這時(shí)如果沒有足夠的內(nèi)存,那么系統(tǒng)必須要通過殺死其他應(yīng)用的方式來滿足這個(gè) App 內(nèi)存的需要,這個(gè)過程中會(huì)對(duì)內(nèi)存進(jìn)行頻繁的操作,導(dǎo)致啟動(dòng)速度變慢。
總結(jié)
App 啟動(dòng)優(yōu)化除了以上的優(yōu)化手段外,還有許多手段由于篇幅原因未能一一進(jìn)行詳細(xì)說明,比如保活、預(yù)加載等。