一款App,啟動速度是最最大的門面,也是使用者接觸app的第一印象,試想,第一次進入app,就打不開,或者過了N秒才能進入主介面,會有多少使用者有耐心繼續使用下去。
所以這篇文章會從Android和iOS兩個維度,根據啟動機制的差異介紹一下啟動速度的優化方式。
Android
啟動分析
對於Android的啟動分析,有一張很經典的圖:
image.png
T0到T1:使用者點選了圖示,這時候首先響應操作的並不是app,而是rom的Launcher,然後就是大家熟知的AMS和WMS操作,這塊講起來有些複雜,之後會放到一個單獨的文章中,而且這裡的優化其實是app根本插不上手的,所以不是本文重點。在系統拉起程序之前,需要先顯示一個預覽視窗,這個是在app中配置的theme,如果theme是透明的,現在還是看到的桌面。這裡不建議進行透明配置,會給使用者一個點選延遲的錯覺。iOS在這裡處理的很好,雖然機制不同,但是在點選圖示後,會先去顯示Launcher畫面,這個是一個固定配置,所以互動感覺更友好一些。T1到T2:程序建立完畢了,就要走Application了,然後根據佈局等等顯示閃屏畫面(如果有閃屏的情況)T2到T3:閃屏結束了,開始顯示首頁了,但是實際上從Application(T1)開始,我們可能需要註冊很多元件,或者載入很多三方庫,這些都是較為耗時的T3到T4:進入了首頁,便是開始載入各類網路請求,以及對應的一些彈窗介面(業務需求)優化方式如果看懂了上述流程,我們可以理出一些優化點:
閃屏優化如果禁用了預覽視窗,那麼使用者到T2才能看到閃屏,之前都是桌面,這個體驗是非常不好的,尤其是對於中低端機型,這時候可以加一個與閃屏介面相同的主題。
但是這裡要特別注意一件事,之前遇到了一種情況,講一個三方庫變成了懶載入,但是由於使用的地方太多,一些case未迴歸,導致個別地方使用的時候未載入成功,所以功能失效了。使用這種方式一定要切記迴歸case
業務優化優化業務,為什麼說要優化業務呢,啟動的每一毫秒都很重要,很多app一啟動,會彈出N多彈窗,常見於各類電商軟體,這些都是需要預載入的,都是需要時間的,所以這裡需要合理衡量業務,砍掉無用的預載入,放到首頁載入完成之後再去彈。
還有一些情況,如需要監聽首次啟動的各類廣播,或者其他型別的監聽器,當事件觸發回撥,可能出現大量程式碼併發。
當然還有其他型別的情況,都是業務太重導致的,這就需要梳理業務,讓啟動(重要的是首次啟動)變得更清晰一些。
還有根據部分業務需求,可能出現在首頁載入大的動畫,這類需求,需要根據機型進行降級,低端機型低端處理,高階機型高階處理。
執行緒優化執行緒優化包括兩個方面,一是優化啟動的執行緒數,這主要是減少CPU的壓力。另一方面就是減少子執行緒和主執行緒互動時的一些block問題,比如雖然我們把耗時操作放到子執行緒了,但是主執行緒執行的一些任務可能等待子執行緒的鎖(或者以回撥形式執行),這就尷尬了,儘量避免這種邏輯發生。之前在iOS時發生過類似的事情,啟動之後需要根據一個變數判斷後續執行,但是這個變數是在子執行緒賦值的,有時候還會出現同時讀寫的問題。
GC優化啟動的過程中儘量避免大量的字串操作,尤其是序列化,反序列化等等,防止出現較多的GC。這時候我們可以儘量複用一些物件,可以頻繁賦值,但是不要頻繁建立。
I/O優化在負載過高的時候,I/O 效能下降得會比較快,一定要清楚啟動的時候進行了哪些I/O操作,讀了什麼檔案,進行了什麼樣的網路請求,請求回來什麼樣的內容,大小是多少等等。如果檔案大小內容都是固定的還好,但是可能出現內容不定的情況。如果xx聊天工具,啟動的時候需要載入聊天記錄,這個檔案可能很大,可能很小,需要根據不同情況進行處理。
資料重排這個思路是之前在網上看到的,感覺很新穎,也記錄下來了。這裡要先介紹一下linux讀取檔案的機制。Linux讀取檔案會以block為單位,一次性在磁碟上讀取4kb的內容,並且放到Page Cache中,這時候我們進行讀取,實際不會發生真正的磁碟I/O。但是我們可能只有許多零碎的小檔案,都是1K左右的,這時候我們可以考慮把資料進行重排,在同一時間需要用的資料放到一個檔案中。只要4k以下,是可以一次讀取的,不會浪費磁碟I/O的時間。
類重排這也是一個新穎的概念,我們可以通過重寫ClassLoader,看一下啟動的過程中類的載入順序,然後通過FaceBook提供的ReDex進行類重排,將啟動過程中用的類往前排。
啟動速度.png
iOS啟動分析
iOS的啟動分析包含兩個部分,一部分是pre-main,一部分是main,這個分類很好理解。
main()執行前:載入可執行檔案(.o檔案)載入動態連結庫Objc 執行時的初始處理,包括 Objc 相關類的註冊,category的註冊,selector唯一性檢查執行+load方法,attribute((constructor)) 修飾的函式的呼叫,C++靜態全域性變數main()執行後,這個主要包括從main開始到appDelegatedidFinishLaunchingWithOptions中程式碼執行完畢。從這裡開始都是我們自己的程式碼了,在這裡一般是各類三方庫的初始化,配置檔案讀寫,首頁載入等等。有的監測軟體,也會將main執行後的程式碼以首屏開始載入為錨點,再進行分割,分成首屏渲染前和首屏渲染後。優化方式減少動態庫的載入pre-main中有個重要的步驟就是載入動態庫,如果動態庫過多,可以將動態庫進行合併。非系統的動態庫,可以支援合併成一個動態庫。
減少類減少載入啟動後不需要的類,有時候業務冗餘,或者程式碼年久失修,會有很多類其實不用了,但是仍然出現在工程中,再或者可能幾個類能夠合併成一個類。
+load優化+load方法是在main函式之前呼叫的,遵從先父類後子類,先本類後列類別的順序呼叫,+initialize方法是在main函式之後呼叫的,+initialize方法遵從懶載入方式,只有在類或它的子類收到第一條訊息之前被呼叫的.+initialize只調用一次,init可多次呼叫。所以少在類的+load方法裡做事情,儘量把這些事情推遲到+initiailize
控制C++全域性變數個數這主要是針對在函式外生命的變數,儘量減少這樣的宣告
業務優化(與android相似)優化業務,為什麼說要優化業務呢,啟動的每一毫秒都很重要,很多app一啟動,會彈出N多彈窗,常見於各類電商軟體,這些都是需要預載入的,都是需要時間的,所以這裡需要合理衡量業務,砍掉無用的預載入,放到首頁載入完成之後再去彈。
還有一些情況,如需要監聽首次啟動的各類廣播,或者其他型別的監聽器,當事件觸發回撥,可能出現大量程式碼併發。
當然還有其他型別的情況,都是業務太重導致的,這就需要梳理業務,讓啟動(重要的是首次啟動)變得更清晰一些。
還有根據部分業務需求,可能出現在首頁載入大的動畫,這類需求,需要根據機型進行降級,低端機型低端處理,高階機型高階處理。
執行緒優化(與android相似)執行緒優化包括兩個方面,一是優化啟動的執行緒數,這主要是減少CPU的壓力。另一方面就是減少子執行緒和主執行緒互動時的一些block問題,比如雖然我們把耗時操作放到子執行緒了,但是主執行緒執行的一些任務可能等待子執行緒的鎖(或者以回撥形式執行),這就尷尬了,儘量避免這種邏輯發生。之前在iOS時發生過類似的事情,啟動之後需要根據一個變數判斷後續執行,但是這個變數是在子執行緒賦值的,有時候還會出現同時讀寫的問題。
專注於首屏資料之前的程式碼中,有這樣的邏輯,在載入首屏的同時,還要載入第二屏的內容(TabBarViewController的第二個Tab),這個是沒有必要的,如果需要預載入,可以將預載入放到首屏載入完成,可互動之後再去執行。
配置檔案讀取優化只加載與首屏相關的內容,其他的配置內容可以放到首屏載入完成後去讀取。
充分利用TimeProfiler在進行首屏資料載入的時候,有很多方法可能造成耗時較長,之前遇到過一個這樣的事情,首頁載入時間較長,使用TimeProfiler看了一下時間,主要集中在首頁圖片載入中,首頁有很多各式各樣的圖片,甚至於一些動畫,這個時候就要看看耗時主要在哪,比如,UIImage存在延遲解壓的問題。+imageNamed這個方法會在載入圖片之後立刻進行解壓,如果圖片過大過大,這個在首屏展示的時候肯定會有效能影響,所以可以考慮使用imageWithContentsOfFile進行非同步載入。當然還有其它方法的耗時,需要根據情況進行優化。
三方庫優化與Android類似,很多三方庫不一定要在初始化的時候進行載入,需要梳理各類三方庫的作用域,將不重要的三方庫,放到首屏的viewDidAppear中去載入。這裡看似簡單,但是是需要認真梳理的,需要扣一下所有的三方庫的應用場景,延遲載入會有什麼樣的影響。
iOS優化建議.png
總結好了,大致就這些內容,裡面很多方法,已經在實際工作中投入使用,還有一些,是對網上主流優化方法的整理,也打算進一步的試用,如果您有更好地方式,歡迎給我留言