前言
假如我們去到一家餐廳,叫了半天都沒有人過來點菜,那等不了多久就沒耐心想走了。
對于 App 也是一樣的,如果我們打開一個應用半天都打不開,那很快的我們也會失去耐心。
啟動速度是使用者對我們應用的第一體驗,使用者隻有啟動我們的應用才能使用我們應用中的功能。
就算我們應用内部設計得再精美,其他性能優化地再好,如果打開速度很慢的話,使用者對我們應用的第一印象還是很差。
你可以追求完美,要做到應用在 1 毫秒内啟動。
但是一般情況下, 我們隻要做到超越競品或者遠超競品,就能在啟動速度這一個點上讓使用者滿意。
使用者選擇 App 的時候會考慮各種因素,而我們 App 開發者能做的就是在争取通過各種技術讓我們的 App 從衆多競品中脫穎而出。
1. 三種啟動狀态
啟動速度對 App 的整體性能非常重要,是以谷歌官方給出了一篇啟動速度優化的文章。
在這篇文章中,把啟動分為了三種狀态:熱啟動、暖啟動和冷啟動。
下面我們來看下三種啟動狀态的特點。
1.1 熱啟動
熱啟動是三種啟動狀态中是最快的一種,因為熱啟動是從背景切到了前台,不需要再建立 Applicaiton,也不需要再進行渲染布局等操作。
1.2 暖啟動
暖啟動的啟動速度介于冷啟動和熱啟動之間,暖啟動隻會重走 Activity 的生命周期,不會重走程序建立和 Application 的建立和生命周期等。
1.3 冷啟動
冷啟動經曆了一系列流程,耗時也是最多的,了解冷啟動整體流程的了解,可以幫助我們尋找之後的一個優化方向。
冷啟動也是優化的衡量标準,一般線上上進行的啟動優化都是以冷啟動速度為名額的。
啟動速度的優化方向是 Application 和 Activity 生命周期階段,這是我們開發者能控制的時間,其他階段都是系統做的。
冷啟動流程可以分為三步:建立程序、啟動應用和繪制界面。
-
建立程序
建立程序階段主要做了下面三件事,這三件事都是系統做的。
- 啟動 App
- 加載空白 Window
- 建立程序
-
啟動應用
啟動應用階段主要做了下面三件事,從這些開始,随後的任務和我們自己寫的代碼有一定的關系。
- 建立 Application
- 啟動主線程
- 建立 MainActivity
-
繪制界面
繪制界面階段主要做了下面三件事。
- 加載布局
- 布置螢幕
- 首幀繪制
2. 兩種測量方法
上一節介紹了三種啟動狀态,這一節我們來看一下常用的兩種測量啟動時間的方法:指令測量和埋點測量。
2.1 指令測量
指令測量指的是用 adb 指令測量啟動時間,通過下面兩步就能實作 adb 指令測量應用啟動時間
- 輸入測量指令
- 分析測量結果
2.2.1 輸入測量指令
我們在終端中輸入一條 adb 指令打開我們要測量的應用,打開後系統會輸出應用的啟動時間。
下面就是測量啟動時間的 adb 指令。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL1gTN4MDMxYTM3ATNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
首屏 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 的啟動耗時。
通過下面三步可以實作埋點測量。
- 定義埋點工具類
- 記錄啟動時間
- 計算啟動耗時
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 顯示占位圖。
通過下面四個步驟可以實作閃屏頁。
- 定義閃屏圖
- 定義閃屏主題
- 設定主題
- 換回主題
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 執行初始化任務的示例代碼。
結語
看完了上面提到的一些啟動優化技巧,你有沒有得到一些啟發呢?
又或者是你有沒有自己的一些啟動優化技巧,不妨在評論區給大家說說。
可能你覺得不值一提的技巧,能解決了其他同學的一個大麻煩。