前言
假如我們去到一家餐廳,叫了半天都沒有人過來點菜,那等不了多久就沒耐心想走了。
對於 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 命令。
首屏 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 執行初始化任務的示例代碼。
結語
看完了上面提到的一些啓動優化技巧,你有沒有得到一些啓發呢?
又或者是你有沒有自己的一些啓動優化技巧,不妨在評論區給大家說說。
可能你覺得不值一提的技巧,能解決了其他同學的一個大麻煩。