QQ空間掉幀率優化實戰
商業轉載請聯系騰訊WeTest獲得授權,非商業轉載請註明出處。
WeTest 導讀
空間新業務需求日益增多,在業務開發階段的疏忽,或者是受到其他業務的影響(比如一些非空間的業務網絡回包或者邏輯在主線程進行),導致空間的某些頁面掉幀率上升。
本文從兩個方向介紹優化掉幀率:
● Time Profiler時間分析工具
● 一些優化手段
Time Profiler(Xcode 9.1)
Time Profiler分析原理:它按照固定的時間間隔來跟蹤每一個線程的堆棧信息,通過統計比較時間間隔之間的堆棧狀態,來推算某個方法執行了多久,並獲得一個近似值。
下面是Time Profiler的界面說明和一些使用建議。
1、主線程使用波峰
開始模擬用戶使用App的時候,可以看到主線程的使用情況,它的波峰會忽高忽低,說明app正在進行耗時計算/正常計算,我們可以截取不同時間段的波峰區間進行探究,比如剛進入空間的5秒內,或者拉取到新feeds流之後平緩的5秒等不同場景(小tips:使用觸控板向左右兩邊挪動可以進一步細化時間區間)
2、關於篩選面板的使用
● Separate by State:此選項會根據應用程序的生命周期狀態對結果進行分組,並且是檢查應用程序在多大程度上執行以及何時執行的有用方法。
● Separate by Thread:根據線程類別分開,方便查看哪些線程占用了最大的CPU。
● Invert Call Tree:調用樹倒返過來,將習慣性的從根向下一級一級的顯示,如選上就會返過來從最底層調用向一級一級的顯示。如果想要查看那個方法調用為最深時使用會更方便些。
● Hide System Libraries:選上它只會展示與應用有關的符號信息,一般情況下我們只關心自己寫的代碼所需的耗時,而不關心系統庫的CPU耗時。(但是很多我們的代碼往往是由系統的函數進來,隱藏的話往往可能會丟失很重要的信息)
● Flatten Recursion:將遞歸函數視為每個堆棧跟蹤中的一個條目,而不是多個。
● Top Functions:將花費在函數中的總時間視為直接在該函數內的時間總和以及該函數所調用的函數花費的時間。如果函數A調用B,那麽A的時間被報告為A的時間並且加上在B中花費的時間。
為了可以獲取更多的信息,建議只勾選 “Separate by Thread”
3、操作小技巧
在折疊的堆棧,按住“alt”點開旁邊的三角形即可展開全部折疊堆棧,如果發現耗時嚴重的堆棧中,可以右鍵點開菜單,選中“Reveal in Xcodes”即可跳轉到對應的代碼區域 。
實戰應用
在好友動態頁面來回滑動,筆者分四種情況來模擬用戶的使用習慣:
● 剛進入空間(無緩存),下拉刷新
● 剛進入空間(有緩存),下拉刷新
● 來回滑動
● 上拉加載更多
1、將耗時操作(如文件IO)放到工作線程
在我們讀取Gif首幀的時候,-[QZoneGIFDecode firstFrameWithURL:viewSize:]裏面是有一部是從磁盤裏讀取二進制文件並且轉換成NSData,然後再進行解碼,這部分的IO操作優化後是放到了工作線程,異步讀取完成解碼之後再展示圖片,不阻塞主線程。
再舉一個例子:異步解析網絡回包數據和異步排版
將耗時操作放到工作線程異步執行占了優化工作的大頭,在這個過程中,要註意多線程問題,比如線程安全問題、死鎖、野指針等,比如容器類的讀寫操作,最常用的NSMutableArray, NSMutableDictionary等, 這類集合在調用讀寫方法時並不是線程安全,簡單地在裏面進行加鎖操作是可以保證線程安全,不過也可能會導致其他耗時問題。
2、耗時函數優化
上圖堆棧表示的是展示圖片, 整個流程如下:
由於空間裏面存在大部分圖片,其中走網絡下載的圖片就是上述這個流程。在這個過程中,刨開網絡下載的部分,我們會根據圖片URL來存取。存取過程首先會將URL 進行MD5加密之後作為Key來進行存取,其實這一步不是必要的,而且系統提供的MD5函數比較耗時。
優化手段:
優化緩沖池存取過程,直接使用URL作為Key來存取,去掉MD5調用。
3、減少- (void)scrollViewDidScroll:
(UIScrollView *)scrollView 這個函數裏面的耗時操作
這個方法在任何方式觸發 contentOffset 變化的時候都會被調用(包括用戶拖動,減速過程,直接通過代碼設置等),可以用於監控 contentOffset 的變化,並根據當前的 contentOffset 對其他 view 做出隨動調整。但是這個方法在滾動的時候每秒調用上百次,如果在裏面加入耗時操作就可能對掉幀率造成很大影響。
解決方法:優化調用耗時,或者將耗時操作放到別的地方去
4、提前進行(耗時操作不可避免)
在進入空間之前,我們會有很多初始化工作,比如初始化用戶的空間裝扮,讀取用戶的一些配置等,有時候還會涉及IO操作,這部分的耗時是必不可免的。為了保證用戶的體驗問題,進入空間前,我們可以提前初始化(preload),將一些耗時操作選擇在適當的時機提前進行。
5、緩存
在業務上,我們會讀取一些設置項來展示或者進行不同的功能,這些選項的即時讀取可能是非常耗時的(尤其是涉及非線程安全容器的讀取,裏面往往是利用了互斥鎖或者信號量等機制保證線程安全,耗時就更加嚴重),我們可以使用靜態變量和dispatch_once來保存起來,避免每次都去要讀取一遍。
再舉一個例子:
我們在渲染的時候會用到很多字體顏色,每次創建這些新的字體和顏色也是耗時的一部分,我們可以將這些UIColor和UIFont緩存起來,過程如下圖:
這裏還可以做進一步的優化,就是在進入空間前,把常用的字體生成並且緩存起來,減少渲染時再生成的耗時。
6、減少不必要的操作
為了方便回溯用戶的操作行為,我們會在App裏面加上很多log,一般log都涉及IO操作,不是必要的log我們要減少,盡量只在關鍵點打log。
7、懶加載view
不要在cell裏面嵌套太多的view,這會很影響滑動的流暢感,而且更多的view也需要花費更多的CPU跟內存。假如由於view太多而導致了滑動不流暢,那就不要在一次就把所有的view都創建出來,把部分view放到需要顯示cell的時候再去創建。比如:
8、利用主線程不同的runloop
優化緩沖池存取過程,直接使用URL作為Key來存取,去掉MD5調用。
上圖是進入空間的時候,需要初始化混合Cover掛件的耗時問題。
我們可以利用不同的runloop來優化這個耗時問題。主線程的 RunLoop 裏有兩個預置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。這兩個 Mode 都已經被標記為"Common"屬性。DefaultMode 是 App 平時所處的狀態,TrackingRunLoopMode 是追蹤 ScrollView 滑動時的狀態。當我們初始化掛件並加到 DefaultMode 時,這個事件 會得到重復回調,但此時滑動一個TableView時,RunLoop 會將 mode 切換為 TrackingRunLoopMode,這時 初始化掛件函數就不會被回調,因而也不會影響到滑動操作。
很多App會用到這個點來進行優化,比如我們操作微信的時候,在朋友圈上拉加載更多的時候,如果不松開手是不會加載出來的,只有放開手才會展示出更多的feeds,也是利用了不同的runloop。
解決掉幀的方法還有很多,希望本文能提供給大家一些思路,後續會繼續更新。
UPA—— 一款針對Unity遊戲/產品的深度性能分析工具,由騰訊WeTest和unity官方共同研發打造,可以幫助遊戲開發者快速定位性能問題。旨在為遊戲開發者提供更完善的手遊性能解決方案,同時與開發環節形成閉環,保障遊戲品質。
目前,限時內測正在開放中,即日起至2017.12.21,所有預約成功的WeTest平臺認證用戶,均可以免費、不限次數地使用最完整的UPA服務,點擊http://wetest.qq.com/cube/ 立即預約。
對UPA感興趣的開發者,歡迎加入QQ群:633065352
如果對使用當中有任何疑問,歡迎聯系騰訊WeTest企業QQ:800024531
QQ空間掉幀率優化實戰