闲鱼如何在2个月内实现Android启动速度翻倍的?
作者: 海潴,錦逸
隨著閑魚App端更多新功能、新技術的加入,應用冷啟動速度越來越慢,這也意味著用戶看到有效內容的時間被拉長,對用戶體驗有著很大的傷害。目前,在內部測試版本中,我們已經將Android的冷啟動時間從原來的10s降低到了5s內。
閑魚是如何快速將啟動時間減少一半的呢?分為建立標準、分析現狀、抓大放小三個步驟。
建立標準
做性能優化不是討論哲學問題,建立合理的數據衡量標準非常重要。盡管已經有了很多關于如何卡口關鍵函數、如何判斷頁面第一幀渲染完成的討論,但從代碼層面進行判斷始終與用戶的感知無法100%得匹配。如何迅速建立起啟動時間的標準?我們借鑒了手淘的方式和標準,利用內部的魔鏡平臺,使用視頻關鍵幀的方式記錄下App圖標被點下到首頁第一屏渲染完成作為一整個應用冷啟動的過程。這與用戶看到的啟動過程吻合。
對于設備的選擇上,我們使用y67這樣一臺現在看起來相對性能較差的機型作為優化的目標機型。低端機存在CPU能力弱,IO速度慢等問題,而慢代碼與IO恰恰是拖慢應用啟動最大的原因。定位優化的目標機型可以更加快速得解決common類型的啟動問題。
閑魚現狀
我們先使用日志打點的方式來統計啟動過程中耗時的大頭,以便可以快速得將啟動性能提高上去??梢钥吹綀D中,進入首頁渲染前,common、interactive兩部分占去了大部分的時間,這是啟動器在執行啟動任務。而在進入首頁后,頁面的請求與view的排版占用了大部分的時間。
基于上面的分析,第一階段我們將”啟動任務治理“和”首頁渲染加速“作為快速提升啟動時間的重點來優化。
啟動任務優化
閑魚的Android端在16年的時候上線了一個基于DAG(有向無環圖)的啟動器,它將啟動任務編排為一個DAG,并使用多核多線程并發的執行任務。上面說到的common與interactive屬于啟動器執行任務的兩個階段,它們都會讓主線程等待階段中的任務全部執行完,所以這兩個階段的任務,我們叫它阻塞型任務。
目前為止,整個閑魚Android在啟動階段有77個任務需要執行,其中阻塞型任務有61個,y67上的總執行耗時在8s以上,并發后需要將近2.5s的時間。
對于啟動階段阻塞型任務,最快的優化方式有三點:
任務拆分與延遲執行
減少阻塞型任務的數量,是加速啟動最直接的手段。這里需要根據任務的DAG進行依賴分析,能夠無傷被延遲執行的任務最明顯的特征就是”沒有其他任務依賴于它“。如果任務之間有依賴,則需要根據后續首頁對于模塊的使用情況來決定是否將整個依賴鏈上的任務全部延遲。
閑魚的首頁金剛位大部分是weex、web和小程序的入口,另外首頁也會用到端智能相關的功能。然而這四個sdk的初始化,普遍都在300-500ms左右,屬于比較”硬核“任務。在將這四個任務移動到異步非阻塞階段后,整個啟動降低了500ms(當然要設置最高優先級以保證用戶盡量少的等待時間)。
非阻塞任務的觸發時機
任務啟動的時機就像跟女生表白一樣,不是你想啟動,啟動就啟動的。錯誤的時機大概率造成災難性得后果。
在我們將幾個大任務移動到非阻塞階段后發現,如果階段啟動的時候首頁還沒開始渲染或者沒有渲染完成,整個首頁的渲染會變得非常緩慢,圖片的加載也隨之變慢??傊褪钦l碰到誰倒霉。實測中,非阻塞階段啟動的時機會對首頁的渲染產生將近1s左右的波動,使得啟動時間不斷得在危險的邊緣瘋狂試探。
這是由于非阻塞階段會在進入首頁后的第一個queueIdle回調之后觸發。而它的執行占用了多過的系統資源,造成CPU占用、網絡請求排隊、IO密集等問題。最終導致主線程、渲染變慢的情況。
那么什么時間才是啟動非阻塞任務的合適時機呢?既然我們選擇首頁渲染為最高優先級,非阻塞任務的啟動就必須排在后面。于是一咬牙一跺腳——”砍“!
我們讓首頁在確認view都上屏后,發信號給啟動器。啟動器這個時候才開始注冊queueIdle回調,并啟動一個延遲6s的runnable作為”備份“,防止message queue過忙長時間無法觸發非阻塞階段的任務。
但這里有個矛盾點,首頁上幾大金剛位都是通往weex、web或者小程序的,如果用戶點擊這些頁面比非阻塞階段的觸發更早,該怎么辦呢?當然是原諒觸發它啊!
這里我們采用的方式是,當這些功能被觸發的時候,需要先去check需要的模塊是否已經初始化完成。如果沒有的話,check非阻塞階段是否已經啟動。如果已啟動,就進入等待,否則強制觸發(這個時候首頁必然已經渲染完成了),并等待所需要的任務執行完成。
任務耗時治理
要快速治理,需要利用一些成熟的工具??梢韵葘θ蝿罩械拿恳恍写a進行時間統計,篩選出執行時間較長的調用后,使用系統提供的method trace進行更細粒度的分析。
既然是要快,那么一定是找通用類型的問題下手:
- 對于IO出來的值,盡量做內存緩存,避免多次IO
- 避免產生大的SharedPreference文件,盡可能得將對commit的調用換成apply
- 注意一些異步接口回調的線程,如果是主線程,也需要保證回調后的代碼快速執行完
首頁啟動優化
優化前,閑魚的首頁需要先進行三個排隊的網絡請求,彈出廣告頁,接著進行動態模板的渲染與數據綁定,總消耗時間在3.5s以上,這里面還不包括圖片上屏的時間。
閑魚首頁部分的啟動優化,主要也從三個方面來做:
廣告頁優化
閑魚之前的廣告頁的流程如下圖:
先拉起啟動頁,然后啟動頁拉起首頁,首頁再拉起廣告頁,廣告頁起來先展示默認圖,然后同時去做是否有廣告的判斷,然后再去做廣告的展示,這個過程如果沒有廣告,也會讓默認的廣告頁展示3秒鐘再關閉。
這個過程顯然是不合理的,廣告有自己的疲勞期,那么在沒有廣告的時候,拉起廣告頁就是一個浪費。其次廣告頁作為一個Activity拉起,需要經歷一些IPC的調用,整個操作也是比較重的。
基于這兩點,我們在廣告頁這塊,先在初始化的時候就做提前的資源拉取和預判,這樣如果確實沒有廣告資源,那么廣告頁直接不做啟動,節省啟動資源。其次,我們將廣告頁由一個activity,改造成一個全屏展示的Dialog,進一步來節省廣告拉起時資源消耗,讓首頁其他內容的加載有更充足的系統資源。
數據預先加載
在性能優化中,空間換時間與提前預加載就像廣為人知的”中間加一層“一樣好用。
閑魚首頁必須的兩個接口,冷啟和熱啟接口耗時在1秒左右,而他們是在首頁第一幀回調回來之后的時機才開始請求的。這里完全可以把請求的時機提前到初始化的過程中并行去做,從而為閑魚啟動-1s。
于是我們設計了針對這種情況下的預取模塊,在初始化的時候,就去做首頁數據的預加載,整體的模塊的時序如下:
這一步做完之后,本地機器測試結果大約節約了950ms的啟動時間。
View預創建
在解決完數據的問題之后,我們通過魔鏡平臺,會發現在y67上,首頁展示之后,有大量的白屏的時間,view的創建和渲染,在這里消耗了大量資源,并占用了很長的時間(這里每一幀是100ms),平均大概在1400ms
于是我們自然而然的想到了在初始化的過程中去提前創建view,但是如果是在初始化過程中的主線程去創建view,那么勢必會跟啟動頁和廣告頁等ui元素競爭主線程的使用,基本等于白干。
于是這里我們采用在子線程預先創建view并執行mesure與layout操作。等待首頁渲染時,使用對應的id進行取出和使用。做完之后,會發現view的上屏時間,在y67上縮短到600ms,減少了一倍的的時間:
總結與下一步優化
通過上面的方式,整個啟動階段的時間從2.5s降低到了1.3s,降低了將近一倍的時間。另外啟動任務所消耗的總時間從8s降低到了3s。首頁的渲染幾乎達到了秒出。整體啟動時間降低到了4.5s左右。
這個階段主要是對啟動過程中的任務與首頁代碼本身的優化。下一階段,我們會對整個啟動過程中的運行環境進行優化:
原文鏈接:https://developer.aliyun.com/article/770680?
版權聲明:本文內容由阿里云實名注冊用戶自發貢獻,版權歸原作者所有,阿里云開發者社區不擁有其著作權,亦不承擔相應法律責任。具體規則請查看《阿里云開發者社區用戶服務協議》和《阿里云開發者社區知識產權保護指引》。如果您發現本社區中有涉嫌抄襲的內容,填寫侵權投訴表單進行舉報,一經查實,本社區將立刻刪除涉嫌侵權內容。總結
以上是生活随笔為你收集整理的闲鱼如何在2个月内实现Android启动速度翻倍的?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 十个问题弄清JVMGC(二)
- 下一篇: 开放下载!《Rocket MQ 使用排查