iOS卡頓原因與優化
1. 卡頓簡介
卡頓: 指用戶在使用過程中出現了一段時間的阻塞,使得用戶在這一段時間內無法進行操作,屏幕上的內容也沒有任何的變化。
卡頓作為App的重要性能指標,不僅影響著用戶體驗,更關系到用戶留存、DAU等重要產品數據。因此,需要關注APP的卡頓
2. 卡頓產生的原因
首先,屏幕上看到的所有內容都是計算機繪制出來的圖像
幀率:Frames Per Second(fps),表示每秒渲染幀數,
通常用于衡量畫面的流暢度,每秒幀數越多,則表示畫面越流暢。
通常,60fps比較流暢,也就是60張/秒,每張圖片需要的渲染時間大約是:
1s/60張 = 1000ms/60張 = 16.7ms/1張
也就是1張圖像在16.7ms內出現一次,就不會造成卡頓現象。
- CPU 中計算顯示內容,比如視圖的創建、布局計算、圖片解碼、文本繪制等
- GPU 進行變換、合成、渲染,把渲染結果提交到幀緩沖區去,在下一次 VSync 信號到來時顯示到屏幕上
卡頓產生的原因:當單位時間內,界面要刷新的時候,CPU或GPU由于計算量大,沒有做好準備,
就會造成界面顯示前一個時間段的界面,從而造成卡頓、掉幀現象
3. 如何避免卡頓
核心: 減少CPU、GPU的資源消耗
CPU
- 創建對象:對象的創建會分配內存、調整屬性,因此,盡量使用輕量級的對象
- 布局計算:視圖布局的計算是 App 中最為常見的消耗 CPU 資源的地方,不要頻繁的調用UIView的相關屬性;盡量提前計算好布局,在有需要時一次性調整到對應的屬性,不要多次、頻繁的計算和調整這些屬性
- 線程處理:控制一下線程的最大并發數量;盡量把耗時的操作放到子線程,包括:文本計算、布局計算、圖片的解碼編碼
GPU
- 盡量避免短時間內大量圖片的顯示,盡可能將多張圖片合成一張進行顯示
- GPU能處理的最大紋理尺寸是4096x4096,一旦超過這個尺寸,就會占用CPU的資源進行處理,所以紋理尺寸盡量不要超過這個尺寸
- 盡量減少視圖數量和層次
- 減少透明的視圖(alpha<1),不透明的就設置opaque為YES
- 盡量避免出現離屏渲染
在OpenGL中,GPU有2種渲染方式:
1 當前屏幕渲染:在當前用于顯示的屏幕緩沖區進行渲染操作
2 離屏渲染:在當前屏幕緩沖區以外新開辟一個緩沖區進行渲染操作
離屏渲染消耗性能的原因?
- 需要創建新的緩沖區
- 離屏渲染的整個過程,需要多次切換上下文環境,先是從當前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結束以后,將離屏緩沖區的渲染結果顯示到屏幕上,又需要將上下文環境從離屏切換到當前屏幕
哪些操作會觸發離屏渲染?
- 光柵化,layer.shouldRasterize = YES
- 遮罩,layer.mask
- 圓角,同時設置layer.masksToBounds = YES、layer.cornerRadius大于0考慮通過CoreGraphics繪制裁剪圓角,或者叫美工提供圓角圖片
- 陰影,layer.shadowXXX,如果設置了layer.shadowPath就不會產生離屏渲染
4. 卡頓檢測
- 監控FPS
- 監控RunLoop
- ping主線程
4.1 監控FPS
幀率:Frames Per Second(fps),表示每秒渲染幀數
使用系統CADisplayLink監控,CADisplayLink是一個與屏幕刷新率相同的定時器,大約1/60s調用一次。
將其注冊到RunLoop里面,計算當前畫面的幀數。
delta為時間差等于1
4.2 監控RunLoop
由于UI刷新只能在主線程操作,因此,平時所說的“卡頓”,主要是因為主線程執行了比較耗時的操作
因此,可以添加observer到主線程Runloop中,通過監聽Runloop狀態切換的耗時,以達到監控卡頓的目的
RunLoop在BeforeSources和AfterWaiting后會進行任務的處理。可以在此時阻塞監控線程并設置超時時間,若超時后RunLoop的狀態仍為RunLoopBeforeSources或AfterWaiting,表明此時RunLoop仍然在處理任務,主線程發生了卡頓
4.3 子線程Ping主線程
ping是常用的網絡測試工具,用來測試數據包能否到達ip地址。
ping主線程的核心思想是向主線程發送一個信號,一定時間內收到了主線程的回復,即表示當前主線程流暢運行。
沒有收到主線程的回復,即表示當前主線程在做耗時運算,發生了卡頓。
子線程Ping主線程的實現思路:
- 創建一個子線程進行循環檢測,每次檢測時設置標記位為YES
- 然后派發任務到主線程中將標記位設置為NO。
- 接著子線程休眠設定的閾值,判斷標志位是否成功設置成NO,如果沒有說明主線程發生了卡頓。
相當于:
子線程設置一個標識YES
如果發生了主線程的卡頓,那么到規定時間,主線程內的代碼沒有執行,則標識還是YES,這時候,代表卡頓
如果沒有發生卡頓,那么到規定時間,主線程內的代碼執行,則表示變為NO,這時候,代表沒有發生卡頓