Android 優(yōu)化之 App 啟動(dòng)優(yōu)化

背景

前面我們分析了 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ì)做以下事情:

  1. 加載并啟動(dòng)應(yīng)用。
  2. 再啟動(dòng)后立即顯示應(yīng)用的空白啟動(dòng)窗口(不做優(yōu)化時(shí)的白屏現(xiàn)象)。
  3. 創(chuàng)建應(yīng)用進(jìn)程。

創(chuàng)建應(yīng)用進(jìn)程可分為以下階段:

  1. 創(chuàng)建應(yīng)用對(duì)象。
  2. 啟動(dòng)主線程。
  3. 創(chuàng)建主 Activity。
  4. 擴(kuò)充視圖。
  5. 布局屏幕。
  6. 執(zhí)行初始繪制。
  7. 把當(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)化
  • \color{red}{線程優(yōu)化},通過減少 CPU 調(diào)度帶來的波動(dòng),讓啟動(dòng)時(shí)間更穩(wěn)定,如果啟動(dòng)過程中有太多的線程一起啟動(dòng),會(huì)給 CPU 帶來非常大的壓力。
  • \color{red}{GC 優(yōu)化},啟動(dòng)過程中減少 GC 的次數(shù)。如避免進(jìn)行大量的字符串操作,特別是序列化和反序列化;如避免重復(fù)創(chuàng)建對(duì)象,出現(xiàn)這種情況,可以考慮復(fù)用。
  • \color{red}{IO 優(yōu)化},啟動(dòng)過程 IO 負(fù)載較高,盡量較少網(wǎng)絡(luò) IO 和磁盤 IO。
  • \color{red}{內(nèi)存優(yōu)化},啟動(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ù)加載等。

參考文章

最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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