談一談Android 啓動優化的一些理解和方案

 

前言

假如我們去到一家餐廳,叫了半天都沒有人過來點菜,那等不了多久就沒耐心想走了。

對於 App 也是一樣的,如果我們打開一個應用半天都打不開,那很快的我們也會失去耐心。

啓動速度是用戶對我們應用的第一體驗,用戶只有啓動我們的應用才能使用我們應用中的功能。

就算我們應用內部設計得再精美,其他性能優化地再好,如果打開速度很慢的話,用戶對我們應用的第一印象還是很差。

你可以追求完美,要做到應用在 1 毫秒內啓動。

但是一般情況下, 我們只要做到超越競品或者遠超競品,就能在啓動速度這一個點上讓用戶滿意。

用戶選擇 App 的時候會考慮各種因素,而我們 App 開發者能做的就是在爭取通過各種技術讓我們的 App 從衆多競品中脫穎而出。

1. 三種啓動狀態

啓動速度對 App 的整體性能非常重要,所以谷歌官方給出了一篇啓動速度優化的文章。

在這篇文章中,把啓動分爲了三種狀態:熱啓動、暖啓動和冷啓動。

下面我們來看下三種啓動狀態的特點。

1.1 熱啓動

熱啓動是三種啓動狀態中是最快的一種,因爲熱啓動是從後臺切到了前臺,不需要再創建 Applicaiton,也不需要再進行渲染布局等操作。

1.2 暖啓動

暖啓動的啓動速度介於冷啓動和熱啓動之間,暖啓動只會重走 Activity 的生命週期,不會重走進程創建和 Application 的創建和生命週期等。

1.3 冷啓動

冷啓動經歷了一系列流程,耗時也是最多的,理解冷啓動整體流程的理解,可以幫助我們尋找之後的一個優化方向。

冷啓動也是優化的衡量標準,一般在線上進行的啓動優化都是以冷啓動速度爲指標的。

啓動速度的優化方向是 Application 和 Activity 生命週期階段,這是我們開發者能控制的時間,其他階段都是系統做的。

冷啓動流程可以分爲三步:創建進程、啓動應用和繪製界面。

  1. 創建進程

    創建進程階段主要做了下面三件事,這三件事都是系統做的。

    • 啓動 App
    • 加載空白 Window
    • 創建進程
  2. 啓動應用

    啓動應用階段主要做了下面三件事,從這些開始,隨後的任務和我們自己寫的代碼有一定的關係。

    • 創建 Application
    • 啓動主線程
    • 創建 MainActivity
  3. 繪製界面

    繪製界面階段主要做了下面三件事。

    • 加載佈局
    • 佈置屏幕
    • 首幀繪製

2. 兩種測量方法

上一節介紹了三種啓動狀態,這一節我們來看一下常用的兩種測量啓動時間的方法:命令測量和埋點測量。

2.1 命令測量

命令測量指的是用 adb 命令測量啓動時間,通過下面兩步就能實現 adb 命令測量應用啓動時間

  1. 輸入測量命令
  2. 分析測量結果

2.2.1 輸入測量命令

我們在終端中輸入一條 adb 命令打開我們要測量的應用,打開後系統會輸出應用的啓動時間。

下面就是測量啓動時間的 adb 命令。


首屏 Activity 也要加上包名,比如下面這樣的。

 

2.2.2 分析測量結果

 

上面是命令執行完成後顯示的內容,在輸出中可以看到三個值:ThisTime、TotalTime 和 WaitTime。

下面我們來看下這三個值分別代表什麼。

  • ThisTime

    ThisTime 代表最後一個 Activity 啓動所需要的時間,也就是最後一個 Activity 的啓動耗時。

  • TotalTime

    TotalTime 代表所有 Activity 啓動耗時,在上面的輸出中,TotalTime 和 ThisTime 是一樣的,因爲這個 Demo 沒有寫 Splash 界面。

    也就是這個 App 打開了 Application 後就直接打開了 MainActivity 界面,沒有啓動其他頁面。

  • WaitTime

    WaitTime 是 AMS 啓動 Activity 的總耗時。

這三者之間的關係如下。

ThisTime <= TotalToime < WaitTime

2.2 埋點測量

埋點測量指的是我們在應用啓動階段埋一個點,在啓動結束時再埋一個點,兩者之間的差值就是 App 的啓動耗時。

通過下面三步可以實現埋點測量。

  1. 定義埋點工具類
  2. 記錄啓動時間
  3. 計算啓動耗時

2.2.1 定義埋點工具類

使用埋點測量的第一步是定義一個記錄埋點工具類。

在這裏要注意的是,除了 System.currentTimeMillis() 以外,我們還可以用 SystemClock.currentThreadTimeMillis() 記錄時間。

通過 SystemClock 拿到的是 CPU 真正執行的時間,這個時間與下一大節要講的 Systrace 上記錄的時間點是一樣的。


2.2.2 記錄啓動時間

使用埋點測量的第二步是記錄啓動時間。

開始記錄的位置放在 Application 的 attachBaseContext 方法中,attachBaseContext 是我們應用能接收到的最早的一個生命週期回調方法。

2.2.3 計算啓動耗時

計算啓動耗時的一個誤區就是在 onWindowFocusChanged 方法中計算啓動耗時。

onWindowFocusChanged 方法只是 Activity 的首幀時間,是 Activity 首次進行繪製的時間,首幀時間和界面完整展示出來還有一段時間差,不能真正代表界面已經展現出來了。

按首幀時間計算啓動耗時並不準確,我們要的是用戶真正看到我們界面的時間。

正確的計算啓動耗時的時機是要等真實的數據展示出來,比如在列表第一項的展示時再計算啓動耗時。

在 Adapter 中記錄啓動耗時要加一個布爾值變量進行判斷,避免 onBindViewHolder 方法被多次調用導致不必要的計算。

2.3 小結

2.3.1 命令測量優缺點

  • 命令測量優點

    • 線下使用方便

      adb 命令測量啓動速度的方式在線下使用比較方便,而且這種方式還能用於測量競品。

  • 命令測量缺點

    • 不能帶到線上

      如果一條 adb 命令帶到線上去,沒有 app 也沒有系統幫我們執行這一條 adb 命令,我們就拿不到這些數據,所以不能帶到線上。

    • 不嚴謹和精確

      不能精確控制啓動時間的開始和結束。

2.3.2 埋點測量的特點

  • 精確

    手動打點的方式比較精確,因爲我們可以精確控制開始和結束的位置。

  • 可帶到線上

    使用埋點測量進行用戶數據的採集,可以很方便地帶到線上,把數據上報給服務器。

    服務器可以針對所有用戶上報的啓動數據,每天做一個整合,計算出一個平均值,然後對比不同版本的啓動速度。

3. 兩個分析工具

常用的分析方法耗時的工具有 Systrace 和 Traceview,它們兩個是相互補充的關係,我們要在不同的場景下使用不同的工具,這樣才能發揮工具的最大作用。

本節內容如下。

  • Traceview
  • Systrace
  • 小結

3.1 Traceview

Traceview 能以圖形的形式展示代碼的執行時間和調用棧信息,而且 Traceview 提供的信息非常全面,因爲它包含了所有線程。

Traceview 的使用可以分爲兩步:開始跟蹤、分析結果。

下面我們來看看這兩步的具體操作。

3.1.1 開始跟蹤

我們可以通過 Debug.startMethodTracing("輸出文件") 就可以開始跟蹤方法,記錄一段時間內的 CPU 使用情況。

當我們調用了 Debug.stopMethodTracing() 停止跟蹤方法後,系統就會爲我們生成一個文件,我們可以通過 Traceview 查看這個文件記錄的內容。

文件生成的位置在 Android/data/包名/files 下,下面我們來看一個示例。

我們在 Application 的 onCreate 方法的開頭開始追蹤方法,然後在結尾結束追蹤,在這裏只是對 BlockCanary 卡頓監測框架進行初始化。


startMethodTracing 方法真正調用的其實是另一個重載方法,在這個重載方法可以傳入 bufferSize。

bufferSize 就是分析結果文件的大小,默認是 8 兆。

我們可以進行擴充,比如擴充爲 16 兆、32 兆等。

這個重載方法的第三個參數是標誌位,這個標誌位只有一個選項,就是 TRACE_COUNT_ALLOCS。


3.1.2 分析結果

運行了程序後,有兩種方式可以獲取到跟蹤結果文件。

第一種方式是通過下面的命令把文件拉到項目根目錄。

第二種方式是在 AS 右下方的文件資源管理器中定位到 /sdcard/android/data/包名/files/ 目錄下,然後自己找個地方保存。

我們在 AS 中打開跟蹤文件 mytrace.trace 後,就可以用 Profiler 查看跟蹤的分析結果。

 

在分析結果上比較重要的是 5 種信息。

  • 代碼指定的時間範圍

    這個時間範圍是我們通過 Debug 類精確指定的

  • 選中的時間範圍

    我們可以拖動時間線,選擇查看一段時間內某條線程的調用堆棧

  • 進程中存在的線程

    在這裏可以看到在指定時間範圍內進程中只有主線程和 BlockCanary 的線程,一共有 4 條線程。

  • 調用堆棧

    在上面的跟蹤信息中,我選中了 main,也就是主線程。

    還把時間範圍縮小到了特定時間區域內,放大了這個時間範圍內主線程的調用堆棧信息

  • 方法耗時

    當我們把鼠標放到某一個方法上的時候,我們可以看到這個方法的耗時,比如上面的 initBlockCanary 的耗時是 19 毫秒。

3.2 Systrace

Systrace 結合了 Android 內核數據,分析了線程活動後會給我們生成一個非常精確 HTML 格式的報告。

Systrace 提供的 Trace 工具類默認只能 API 18 以上的項目中才能使用,如果我們的兼容版本低於 API 18,我們可以使用 TraceCompat。

Systrace 的使用步驟和 Traceview 差不多,分爲下面兩步。

  • 調用跟蹤方法
  • 查看跟蹤結果

3.2.2 調用跟蹤方法

首先在 Application 中調用 Systrace 的跟蹤方法。


然後連接設備,在終端中定位到 Android SDK 目錄下,比如我的 Android SDK 目錄在 /users/oushaoze/library/Android/sdk 。

這時候我打開 SDK 目錄下的 platform-tools/systrace 就能看到 systrace.py 的一個 python 文件。

Systrace 是一個 Python 腳本,輸入下面命令,運行 systrace ,開始追蹤系統信息。


這行命令附加了下面一些選項。

  • -t ...

    -t 後面表示的是跟蹤的時間,比如上面設定的是 10 秒就結束。

  • -o ...

    -o 後面表示把文件輸出到指定目錄下。

  • -a ...

    -a 後面表示的是要啓動的應用包名

輸入完這行命令後,可以看到開始跟蹤的提示。看到 Starting tracing 後可以打開打開我們的應用。

10 秒後,會看到 Wrote trace HTML file: ....。


上面這段輸出就是說追蹤完畢,追蹤到的信息都寫到 trace.html 文件中了,接下來我們打開這個文件。

3.2.3 查看跟蹤結果

 

打開文件後我們可以看到上面這樣的一個視圖,在這裏有幾個需要特別關注的地方。

  • 8 核

    我運行 Systrace 的設備是 8 核的,所以這裏的 Kernel 下面是 8 個 CPU。

  • 縮放

    當我們選中縮放後,縮放的方式是上下移動,不是左右移動。

  • 移動

    選擇移動後,我們可以拖動我們往下查看其它進程的分析信息。

  • 時間片使用情況

    時間片使用情況指的是各個 CPU 在特定時間內的時間片使用情況,當我們用縮放把特定時間段內的時間片信息放大,我們就可以看到時間片是被哪個線程佔用了。

  • 運行中的進程

    左側一欄除了各個內核外,還會顯示運行中的進程。

我們往下移動,可以看到 MyAppplication 進程的線程活動情況。


在這個視圖上我們主要關注三個點。

  • 主線程

    在這裏我們主要關注主線程的運行了哪些方法

  • 跟蹤的時間段

    剛纔在代碼中設置的標籤是 AppOnCreate,在這裏就顯示了這個跟蹤時間段的標籤

  • 耗時

    我們選中 AppOnCreate 標籤後,就可以看到這個方法的耗時。

    在 Slice 標籤下的耗時信息包括 Wall Duration 和 CPU Duration,下面是它們的區別。

    • Wall Duration

      Wall Time 是執行這段代碼耗費的時間,不能作爲優化指標。

      假如我們的代碼要進入鎖的臨界區,如果鎖被其他線程持有,當前線程就進入了阻塞狀態,而等待的時間是會被計算到 Wall Time 中的。

    • CPU Duration

      CPU Duration 是 CPU 真正花在這段代碼上的時間,是我們關心的優化指標。

      在上面的例子中 Wall Duration 是 84 毫秒,CPU Duration 是 34 毫秒,也就是在這段時間內一共有 50 毫秒 CPU 是處於休息狀態的,真正執行代碼的時間只花了 34 毫秒。

3.3 小結

3.3.1 Traceview 的兩個特點

Traceview 有兩個特點:可埋點、開銷大。

  • 可埋點

    Traceview 的好處之一是可以在代碼中埋點,埋點後可以用 CPU Profiler 進行分析。

    因爲我們現在優化的是啓動階段的代碼,如果我們打開 App 後直接通過 CPU Profiler 進行記錄的話,就要求你有單身三十年的手速,點擊開始記錄的時間要和應用的啓動時間完全一致。

    有了 Traceview,哪怕你是老年人手速也可以記錄啓動過程涉及的調用棧信息。

  • 開銷大

    Traceview 的運行時開銷非常大,它會導致我們程序的運行變慢。

    之所以會變慢,是因爲它會通過虛擬機的 Profiler 抓取我們當前所有線程的所有調用堆棧。

    因爲這個問題,Traceview 也可能會帶偏我們的優化方向。

    比如我們有一個方法,這個方法在正常情況下的耗時不大,但是加上了 Traceview 之後可能會發現它的耗時變成了原來的十倍甚至更多。

3.3.2 Systrace 的兩個特點

Systrace 的兩個特點:開銷小、直觀。

  • 開銷小

    Systrace 開銷非常小,不像 Traceview,因爲它只會在我們埋點區間進行記錄。

    而 Traceview 是會把所有的線程的堆棧調用情況都記錄下來。

  • 直觀

    在 Systrace 中我們可以很直觀地看到 CPU 利用率的情況。

    當我們發現 CPU 利用率低的時候,我們可以考慮讓更多代碼以異步的方式執行,以提高 CPU 利用率。

3.3.3 Traceview 與 Systrace 的兩個區別

  • 查看工具

    Traceview 分析結果要使用 Profiler 查看。

    Systrace 分析結果是在瀏覽器查看 HTML 文件。

  • 埋點工具類

    Traceview 使用的是 Debug.startMethodTracing()。

    Systrace 用的是 Trace.beginSection() 和 TraceCompat.beginSection()。

4. 兩種優化方法

常用的兩種優化方法有兩種,這兩種是可以結合使用的。

第一種是閃屏頁,在視覺上讓用戶感覺啓動速度快,第二種是異步初始化。

4.1 閃屏頁

閃屏頁是優化啓動速度的一個小技巧,雖然對實際的啓動速度沒有任何幫助,但是能讓用戶感覺比啓動的速度要快一些。

閃屏頁就是在 App 打開首屏 Activity 前,首先顯示一張圖片,這張圖片可以是 Logo 頁,等 Activity 展示出來後,再把 Theme 變回來。

冷啓動的其中一步是創建一個空白 Window,閃屏頁就是利用這個空白 Window 顯示佔位圖。

通過下面四個步驟可以實現閃屏頁。

  1. 定義閃屏圖
  2. 定義閃屏主題
  3. 設置主題
  4. 換回主題

4.1.1 定義閃屏圖

第一步是在 drawable 目錄下創建一個 splash.xml 文件。


4.1.2 定義閃屏主題

第二步是在 values/styles.xml 中定義一個 Splash 主題。

4.1.3 設置主題

第三步是在清單文件中設置 Theme。

4.1.4 換回主題

第四步是在調用 super.onCreate 方法前切換回來

 

4.2 異步初始化

我們這一節來看一下怎麼用線程池進行異步初始化。

本節內容包括如下部分,

  • 異步初始化簡介
  • 線程池大小
  • 線程池基本用法

4.2.1 異步初始化簡介

異步優化就是把初始化的工作分細分成幾個子任務,然後讓子線程分別執行這些子任務,加快初始化過程。

如果你對怎麼在 Android 中實現多線程不瞭解,可以看一下我的上一篇文章:探索 Android 多線程優化,在這篇文章中我對在 Android 使用多線程的方法做了一個簡單的介紹。

有些初始化代碼在子線程執行的時候可能會出現問題,比如要求在 onCreate 結束前執行完成。

這種情況我們可以考慮使用 CountDownLatch 實現,實在不行的時候就保留這段初始化代碼在主線程中執行。

4.2.2 線程池大小

我們可以使用線程池來實現異步初始化,使用線程池需要注意的是線程池大小的設置。

線程池大小要根據不同的設備設置不同的大小,有的手機是四核的,有的是八核的,如果把線程池大小設爲固定數值的話是不合理的。

我們可以參考 AsyncTask 中設置的線程池大小,在 AsyncTask 中有 CPU_COUNT 和 CORE_POOL_SIZE。

  • CPU_COUNT

    CPU_COUNT 的值是設備的 CPU 核數。

  • CORE_POOL_SIZE

    CORE_POOL_SIZE 是線程池核心大小,這個值的最小值是 2,最大值是 Math.min(CPU_COUNT - 1, 4)。

    當設備的核數爲 8 時,CORE_POOL_SIZE 的值爲 4,當設備核數爲 4 時,這個值是 3,也就是 CORE_POOL_SIZE 的最大值是 4。

4.2.3 線程池基本用法

在這裏我們可以參考 AsyncTask 的做法來設置線程池的大小,並把初始化的工作提交到線程池中。


6. 改進優化方案

上一節介紹了怎麼通過線程池處理初始化任務,這一節我們看一下改進的異步初始化工具:啓動器(LaunchStarter)。

這一節的內容包括如下部分。

  • 線程池實現的不足
  • 啓動器簡介
  • 啓動器工作流程
  • 實現任務等待執行
  • 實現任務依賴關係

6.1 線程池實現的不足

通過線程池處理初始化任務的方式存在三個問題。

  • 代碼不夠優雅

    假如我們有 100 個初始化任務,那像上面這樣的代碼就要寫 100 遍,提交 100 次任務。

  • 無法限制在 onCreate 中完成

    有的第三方庫的初始化任務需要在 Application 的 onCreate 方法中執行完成,雖然可以用 CountDownLatch 實現等待,但是還是有點繁瑣。

  • 無法實現存在依賴關係

    有的初始化任務之間存在依賴關係,比如極光推送需要設備 ID,而 initDeviceId() 這個方法也是一個初始化任務。

6.2 啓動器簡介

啓動器的核心思想是充分利用多核 CPU ,自動梳理任務順序。

第一步是我們要對代碼進行任務化,任務化是一個簡稱,比如把啓動邏輯抽象成一個任務。

第二步是根據所有任務的依賴關係排序生成一個有向無環圖,這個圖是自動生成的,也就是對所有任務進行排序。

比如我們有個任務 A 和任務 B,任務 B 執行前需要任務 A 執行完,這樣才能拿到特定的數據,比如上面提到的 initDeviceId。

第三步是多線程根據排序後的優先級依次執行,比如我們現在有三個任務 A、B、C。

假如任務 B 依賴於任務 A,這時候生成的有向無環圖就是 ACB,A 和 C 可以提前執行,B 一定要排在 A 之後執行。

6.3 啓動器工作流程


  • Head Task

    Head Task 就是所有任務執行前要做的事情,在這裏初始化一些其他任務依賴的資源,也可以只是打個 Log。

  • Tail Task

    Tail Task 可用於執行所有任務結束後打印一個 Log,或者是上報數據等任務。

  • Idle Task

    Idle Task 是在程序空閒時執行的任務。

如果我們不使用異步的方案,所有的任務都會在主線程執行。

爲了讓其他線程分擔主線程的工作,我們可以把初始化的工作拆分成一個個的子任務,採用併發的方式,使用多個線程同時執行這些子任務。

6.4 實現任務等待執行

啓動器(LaunchStarter)使用了有向無環圖實現任務之間的依賴關係,具體的代碼可以在本文最下方找到。

使用啓動器需要完成 3 個步驟。

  • 添加依賴
  • 定義任務
  • 開始任務

下面我們來看下這 3 個步驟的具體操作。

6.4.1 添加依賴

首先在項目根目錄的 build.gradle 中添加 jitpack 倉庫。


 

allprojects {
  repositories {
    // ...
    maven { url 'https://jitpack.io' }
  }
}

然後在 app 模塊的 build.gradle 中添加依賴

dependencies {
  // 啓動器
  implementation 'com.github.zeshaoaaa:LaunchStarter:0.0.1'
}  

6.4.2 定義任務

定義任務這個步驟涉及了幾個概念:MainTask、Task、needWait 和 run。

  • MainTask

    MainTask 是需要在主線程執行的任務

  • Task

    Task 就是在工作線程執行的任務。

  • needWait

    InitWeexTask 中重寫了 needWait 方法,這個方法返回 true 表示 onCreate 的執行需要等待這個任務完成。

  • run

    run() 方法中的代碼就是需要做的初始化工作


 

6.4.3 開始任務

定義好了任務後,我們就可以開始任務了。

這裏需要注意的是,如果我們的任務中有需要等待完成的任務,我們可以調用 TaskDispatcher 的 await() 方法等待這個任務完成,比如 InitWeexTask。

使用 await() 方法要注意的是這個方法要在 start() 方法調用後才能使用。


 

6.5 實現任務依賴關係

除了上面提到的等待功能以外,啓動器還支持任務之間存在依賴關係,下面我們來看一個極光推送初始化任務的例子。

在這一節會講實現任務依賴關係的兩個步驟。

  • 定義任務
  • 開始任務

6.5.1 定義任務

在這裏我們定義兩個存在依賴關係的任務:GetDeviceIdTask 和 InitJPush Task。

首先定義 GetDeviceIdTask ,這個任務負責初始化設備 ID 。


然後定義InitJPushTask,這個任務負責初始化極光推送 SDK,InitJPushTask 在啓動器中是尾部任務 Tail Task。

InitJPushTask 依賴於 GetDeviceIdTask,所以需要重寫 dependsOn 方法,在 dependsOn 方法中創建一個 Class 列表,把想依賴的任務的 Class 添加到列表中並返回。


 

6.5.2 開始任務

GetDeviceIdTask 和 InitJPushTask 這兩個任務都不需要等待 Application 的 onCreate 方法執行完成,所以我們這裏不需要調用 TaskDispatcher 的 await 方法。

6.5.3 小結

上面這兩個步驟就能實現通過啓動器實現任務之間的依賴關係。

7. 延遲執行任務

在我們應用的 Application 和 Activity 中可能存在部分優先級不高的初始化任務,我們可以考慮把這些任務進行延遲初始化,比如放在列表的第一項顯示出來後再進行初始化。

常規的延遲初始化方法有兩種:onPreDraw 和 postDelayed。

除了常規方法外,還有一種改進的延遲初始化方案:延遲啓動器。

本節包括如下內容。

  • onPreDraw

    onPreDraw 指的是在列表第一項顯示後,在 onPreDraw 回調中執行初始化任務

  • postDelayed

    通過 Handler 的 postDelayed 方法延遲執行初始化任務

  • 延遲啓動器

7.1 onPreDraw

這一節我們來看下怎麼通過 OnPreDrawListener 把任務延遲到列表顯示後再執行。

下面是 onPreDraw 方式實現延遲初始化的 3 個步驟。

  • 聲明回調接口
  • 調用接口方法
  • 在 Activity 中監聽
  • 小結

7.1.1 聲明回調接口

第一步先聲明一個 OnFeedShowCallback。


7.1.2 調用接口方法

第二步是在 Adapter 中的第一條顯示的時候調用 onFeedShow() 方法。

7.1.3 在 Activity 中監聽

第三步是在 Activity 中調用 setOnFeedCallback 方法。

 

7.1.4 小結

直接在 onFeedShow 中執行初始化任務的弊端是有可能導致滑動卡頓。

如果我們 onPreDraw 的方式延遲執行初始化任務,假如這個任務耗時是 2 秒,那就意味着在列表顯示第一條後的 2 秒內,列表是無法滑動的,用戶體驗很差。

7.2 postDelayed

還有一種方式就是通過 Handler.postDelayed 方法發送一個延遲消息,比如延遲到 100 毫秒後執行。

假如在 Activity 中有 1 個 100 行的初始化方法,我們把前 10 行代碼放在 postDelayed 中延遲 100 毫秒執行,把前 20 行代碼放在 postDelayed 中延遲 200 毫秒執行。

這種實現的確緩解了卡頓的情況,但是這種實現存在兩個問題

  • 不夠優雅

    假如按上面的例子,可以分出 10 個初始化任務,每一個都放在 不同的 postDelayed 中執行,這樣寫出來的代碼不夠優雅。

  • 依舊卡頓

    假如把任務延遲 200 毫秒後執行,而 200 後用戶還在滑動列表,那還是會發生卡頓。

7.3 延遲啓動器

7.3.1 延遲啓動器基本用法

除了上面說到的方式外,現在我們來說一個更好的解決方案:延遲啓動器。

延遲啓動器利用了 IdleHandler 實現主線程空閒時才執行任務,IdleHandler 是 Android 提供的一個類,IdleHandler 會在當前消息隊列空閒時才執行任務,這樣就不會影響用戶的操作了。

假如現在 MessageQueue 中有兩條消息,在這兩條消息處理完成後,MessageQueue 會通知 IdleHandler 現在是空閒狀態,然後 IdleHandler 就會開始處理它接收到的任務。

DelayInitDispatcher 配合 onFeedShow 回調來使用效果更好。

下面是一段使用延遲啓動器 DelayInitDispatcher 執行初始化任務的示例代碼。


結語

看完了上面提到的一些啓動優化技巧,你有沒有得到一些啓發呢?

又或者是你有沒有自己的一些啓動優化技巧,不妨在評論區給大家說說。

可能你覺得不值一提的技巧,能解決了其他同學的一個大麻煩。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章