卡頓的原理
iOS系統界面滑動流暢性的保持主要是依靠CPU和GPU兩大處理硬件間通力合作的結果,一個視圖的顯示需要先經過CPU創建、佈局計算、對圖片解碼、文本繪製,然後CPU將計算的結果交給GPU,GPU可能需要對圖形進行變換、合成、渲染,GPU然後將渲染的結果提交到幀緩衝區等待下一次的垂直同步信號(V-Sync)到來顯示到顯示器上,如果在一個V-Sync間隔內CPU或者GPU由於過高的利用率都可能導致數據的不及時提交,那麼那一幀數據就會被丟棄,等待下一次的V-Sync再顯示,這也就是通常能感受到的界面卡頓現象,也就是掉幀。
優化過程
所以當出現卡頓現象時主要去分析此刻CPU和GPU的利用率,當出現較高的CPU利用率時應及時去分析代碼執行的效率,或者用Time Profier去分析導致較高佔用率的代碼,更多詳細的關於優化CPU的情況本文不做詳細分析。
而對於GPU資源消耗的優化本文將通過一個Demo優化過程來講解優化的步驟,你可以在這裏GitHub下載源碼,Demo是有2個tab組成的,before是優化前視圖,after是優化後的,你可以通過對比學習體會其中的差異。
下載運行demo, 打開Xcode的調試選項,在菜單欄-Debug-View Debugging-Rendering可以找到,運行APP(真機)勾選相關的菜單選項即可
1.視圖混合(Blending)
app中顯示的效果往往是多個視圖重合疊加的效果,而在計算視圖重合顯示顏色的時候就需要考慮透明的影響,當頂部視圖出現透明的情況時,顏色的計算就需要考慮其透明度,這樣無疑增加了計算成本,消耗GPU資源,所以應儘量避免過多的透明視圖數量。
對於UIImageView而言,如果圖片本身是帶有透明通道的同樣會導致Blending,所以應該儘量避免使用帶有透明度的圖片。
對於文本UILable, 如果不設置背景顏色,同樣會出現Blending,所以需要設置UILabel的背景顏色,對於顯示中文的UILabel, 除了設置背景顏色外還需要設置masksToBounds屬性,因爲中文時UILable會多了一個sublayer。
勾選Rendering中的第一個選項Color Blended Layers,此選項就是用於檢測哪些視圖出現了Blending,出現Blending的地方會用紅色標記出來,運行demo可以出現下圖所示情況:
可以看到,before tab下的控制器視圖無論是圖片還是文本都被標紅,表示出現了Blending,對於圖片我們可以在Assets裏面找到對應的圖片,然後查看簡介就可以查看圖片是否有Alpha通道,會看到圖片都具有Alpha通道,也就回導致Blending
對於這種情況,可以讓UI切圖時關閉此Alpha通道,也可以直接用mac自帶的圖片軟件打開圖片,然後導出圖片關閉Alpha通道。所以我們需要將所有有Alpha的圖片都處理一遍,儘量不要使用帶有透明度的圖片。
對於UILabel,上面有提到,我們只需要下面的2行代碼即可處理:
titleLabel.backgroundColor = UIColor.white
titleLabel.layer.masksToBounds = true
通過去掉圖片alpha通道優化後如下圖所示,只有被設置爲圓角的小圖片還存在Blending,因爲我們是直接設置的layer的cornerRadius(IOS9以下會導致離屏渲染),同樣我們可以直接用無透明通道的圓角圖片來替換解決,但這需要UI適配更多背景圖片。
2.光柵化
開啓光柵化是通過設置屬性shouldRasterize,開啓光柵化後CALayer會被保存爲bitmap放到緩存中,這樣在下次需要時可以直接中緩存中取出來顯示,這樣節省了渲染時間,例如對於設置有陰影效果的複雜視圖會對性能有一定的提升。
nameLabel.layer.shouldRasterize = true//開啓光柵化
第二個調試選項 Color Hits Green and Misses Red就是用來查看光柵化視圖的,勾選後若視圖被標記爲綠色,則表示命中了緩存,直接從緩存中取出來顯示,緩存的有效時間爲100ms, 而紅色則表示沒有命中。Demo中,我們對第2個Label開啓了光柵化,滾動會發現被標記爲綠色
更新一個已光柵化的Layer會觸發離屏渲染,所以選擇哪些視圖適合做光柵化需要根據場景權衡,光柵化適合那些佈局複雜而不經常變動的視圖,比如
- 用於避免靜態內容的複雜特效的重繪,例如UIBlurEffect
- 用於避免多個View嵌套的複雜View的重繪。
同時注意緩存是有大小限制的,所以不要過度是使用光柵化,因爲超過緩存大小會導致大量的離屏渲染。
3.顏色格式
Color Copied Images選項能標識出視圖中不能被GPU處理的圖片,因爲來自網絡的圖片格式可能千變萬化,有的圖片的格式是GPU無法識別的所以會交給CPU處理,出現這種情況就需要修改圖片格式,
4.不標準的表面顏色格式
Color Non-Standard Surface Formats 打開此選項後會發現Label會出現灰色的背景顏色,然後經過我們給Label設置了背景顏色後便消失了,關於此選項的相關介紹甚少,期待有人能挖掘挖掘,所以只能猜測,蘋果推薦我們給Label設置一個背景顏色。
5.顏色刷新頻率
默認情況下圖層的顏色更新是有10ms的延遲的,在某些特定情況下可能需要關閉這個延遲,但絕大多數情況用不到這個選項。
6.圖片大小
Color Misaligned Images選項在圖片像素不對齊(也就是圖片帶alpha通道)時,會在圖片上面加一層洋紅色來標識;而圖片被縮放時,會加一層黃色來標識,我們可以看到優化前的圖片會出現圖片縮放,因爲圖片的顯示大小和圖片的大小不匹配。
圖片縮放同樣會消耗GPU資源,所以儘量保證圖片的顯示大小和原圖大小一致來避免縮放,所以demo的圖片在處理後大小都等於顯示的大小來避免縮放。
7.離屏渲染
Color Offscreen-Rendered Yellow會用黃色標識哪些圖層出現了離屏渲染,什麼是離屏渲染?
離屏渲染表示渲染不是發生在當前屏幕的緩衝區中,而是發生在其他緩衝區的渲染,這就需要開闢更多的緩衝區,等到要用的時候再從其他的緩衝區讀取來顯示,所以這樣會消耗更多的GPU資源,所以避免離屏渲染可以有效的提升顯示性能。
如上圖所示,要顯示一個相機圖標和一個蒙層,在前兩個渲染通道中,GPU分別得到了相機的紋理和藍色的蒙版layer的渲染結果。但這兩個渲染結果沒有直接放入Frame Buffer中,這就是離屏渲染。等到第三個渲染通道,才把兩者組合起來放入Frame Buffer中最終顯示到屏幕上,這就是典型的離屏渲染。
運行demo,打開Color Offscreen-Rendered Yellow選項,在beforetab控制器視圖下出現離屏渲染的圖層便會被黃色標識出來,如圖:
可以發現右邊的大圖出現了離屏渲染,而圓角的小圖卻沒有,大圖出現離屏渲染的原因是設置了shadow陰影,而因爲測試機器是ios11所以設置圖片的cornerRadius並不會導致離屏渲染。
同樣設置了以下屬性時,都會觸發離屏渲染:
- shouldRasterize(光柵化)
- masks(遮罩)
- shadows(陰影)
- edge antialiasing(抗鋸齒)
- group opacity(不透明)
- 複雜形狀設置圓角等(ios8)
- 漸變
上面出現離屏渲染的case都應該要注意,所以針對shadow可以通過設置shadowPath來避免,光柵化也應該儘量避免:
優化後代碼如下:
mainImageView.layer.shadowColor = UIColor.black.cgColor
mainImageView.layer.shadowOpacity = 1
mainImageView.layer.shadowRadius = 2
// mainImageView.layer.shadowOffset = CGSize(width: 2, height: 2)
mainImageView.layer.shadowPath = UIBezierPath.init(roundedRect: mainImageView.bounds, cornerRadius: 2).cgPath
smallImageView.layer.cornerRadius = smallImageView.frame.size.width / 2
smallImageView.clipsToBounds = true
nameLabel.backgroundColor = UIColor.white
titleLabel.backgroundColor = UIColor.white
titleLabel.layer.masksToBounds = true
nameLabel.layer.masksToBounds = true
// nameLabel.layer.shouldRasterize = true
8.快速路徑
第七個選項“Color Compositing Fast-Path Blue”用於標記由硬件繪製的路徑,藍色越多越好,demo略
9.變化區域
最後一個Flash Updated Regions 用於標記發生重繪製的區域並用黃色標記出來,對於大多數不變的區域應該儘量的避免重繪而只有小部分經常變化的區域重繪這有助於顯著提高性能。demo略
優化結果
經過對圖片Alpha透明度調整,大小剪裁適配顯示,更改設置陰影,文本背景等一系列的優化,使用Core Animation記錄GPU使用率的變化來觀察優化效果:
before:
可以看到優化前CPU平均佔用率達到60%,這還只是簡單的視圖佈局,如果更加複雜佔用率還會增加,這也就意味着更高的卡頓風險。
after:
對比可以發現在提升了平均顯示幀數的同時大大降低了GPU消耗,性能得到顯著的提升。
測試環境:Xcode9.3、iPhone6s、 iOS11、Swift4
Demo地址:GitHub地址