本文來自於Dev Club 開發者社區,非經做者贊成,請勿轉載,原文地址:http://dev.qq.com/topic/57fc8cea302e4725036142f6算法
#使用多張圖片作幀動畫的性能優化緩存
##背景性能優化
QQ羣的送禮物功能須要加載幾十張圖而後作幀動畫,可是多張圖片加載形成了很是大的性能開銷,致使圖片開始加載到真正播放動畫的時間間隔比較長。因此須要研究一些優化方案提高加載圖片和幀動畫的性能。微信
##原理分析 iOS系統從磁盤加載一張圖片,使用UIImageView顯示到屏幕上,須要通過如下步驟:網絡
###加載圖片 加載圖片和圖片解碼是比較容易影響性能的一些因素。iOS設備上的閃存雖然是很是快的,可是仍是要比內存慢接近200倍左右。圖片加載的性能取決於CPU和IO,因此適當的減小IO次數能夠提高一部分性能。異步
一般狀況下,應用應該在用戶不會察覺的時候加載圖片,能夠針對狀況採用預加載圖片或者延遲加載。若是是幀動畫這種狀況,圖片很難作延遲加載,由於幀動畫的時間比較短,延遲加載很難保證播放每一幀時能提早加載到圖片。有些好比列表滑動之類的狀況延遲加載就會比較合理,而且能夠採用子線程異步之類的方法防止滑動卡頓。ide
###解碼工具
圖片加載結束以後在被渲染到屏幕以前,若是是未解碼的JPEG或者PNG格式,圖片會先被解碼爲位圖數據。通過實際的測試,圖片解碼一般要比圖片加載耗費更多的時間。iOS默認會在主線程對圖像進行解碼。不少庫都解決了圖像解碼的問題,不過因爲解碼後的圖像太大,通常不會緩存到磁盤,SDWebImage的作法是把解碼操做從主線程移到子線程,讓耗時的解碼操做不佔用主線程的時間。oop
#####解碼與加載圖片耗時對比性能
測試機型:iPhone 6+
--- | 只加載不解碼平均時長 | 加載和解碼平均時長 |
---|---|---|
同一張圖加載三十次 | 0.000858531 | 0.005955906 |
加載三十張不一樣的圖 | 0.002828871 | 0.015458194 |
解碼的時間一般是加載圖像數據時間的三到四倍左右。
#####各類加載圖片API的解碼的時機 UIKit:
+imageNamed://加載到原圖後馬上進行解碼 +imageWithContentsOfFile://圖像渲染前進行解碼
UIImageView的image被賦值時會馬上進行解碼。
圖像用UIKit內的繪圖API繪製時會馬上進行解碼,這個API的好處是能夠在子線程進行。
ImageIO:
NSURL *imageURL = [NSURL fileURLWithPath:str]; NSDictionary *options = @{(__bridge id)kCGImageSourceShouldCache: [@YES](https://my.oschina.net/u/150059), (__bridge id)kCGImageSourceShouldCacheImmediately: @NO}; CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, NULL); CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0,(__bridge CFDictionaryRef)options); UIImage *image = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef); CFRelease(source);
kCGImageSourceShouldCacheImmediately 決定是否會在加載完後馬上開始解碼。
###緩存 緩存分爲原圖像的緩存和解碼後位圖數據的緩存。 通常狀況下,解碼後的位圖數據的緩存會跟隨着原圖UIImage,而且UIImage被拷貝後,指針變了以後圖像須要從新去解碼。
解碼後的位圖數據能夠經過如下的API獲取。
1.CGImageSourceCreateWithData(data) 建立 ImageSource。 2.CGImageSourceCreateImageAtIndex(source) 建立一個未解碼的 CGImage。 3.CGImageGetDataProvider(image) 獲取這個圖片的數據源。 4.CGDataProviderCopyData(provider) 從數據源獲取直接解碼的數據。
#####各類加載圖片API的緩存策略 UIKit:
+imageNamed://原圖和解碼後的位圖數據都會保存在系統緩存下,只有在內存低之類的時候纔會被釋放。因此這個API適合在應用屢次使用的圖片上使用。 +imageWithContentsOfFile://不會對原圖作緩存,只用一次的圖片應該使用這個API。
ImageIO:
ImageIO的API的kCGImageSourceShouldCache選項決定了是否會對解碼後的位圖數據作緩存。64位設備上默認爲開,32位設備上默認爲關。
任什麼時候候,選取如何緩存老是一件比較難的事,正如菲爾 卡爾頓曾經說過:「在計算機科學中只有兩件難事:緩存和命名」。
解碼後位圖數據的緩存很差控制,咱們通常也不會去操做它,可是原圖像仍是能夠作一些緩存的。除了使用系統API來作緩存之外,應用也能夠自定義一些緩存策略。或者可使用NSCache。
NSCache的API和NSDictionary很像,而且會根據所保存數據的使用頻率,內存佔用狀況會適時的釋放掉一其中一部分數據。
###圖片格式 經常使用的圖片格式通常分爲PNG和JPEG。
對於PNG圖片來講,加載會比JPEG更長,由於文件可能更大,可是解碼會相對較快,並且Xcode會把PNG圖片進行解碼優化以後引入工程。JPEG圖片更小,加載更快,可是解壓的步驟要消耗更長的時間,由於JPEG解壓算法比基於zip的PNG算法更加複雜。
JPEG 對於噪點大的圖片效果比較好,PNG適合鋒利的線條或者漸變色的圖片。對於不友好的png圖片,相同像素的JPEG圖片老是比PNG加載更快,除非一些很是小的圖片、但對於友好的PNG圖片,一些中大尺寸的圖效果仍是很好的
本文測試時使用的全部圖片均爲PNG格式。
###渲染
關於圖像的渲染,主要從如下三點分析:
Offscreen rendering指的是在圖像在繪製到當前屏幕前,須要先進行一次渲染,以後才繪製到當前屏幕。在進行offscreen rendring的時候,顯卡須要另外alloc一塊內存來進行渲染,渲染完畢後在繪製到當前屏幕,並且對於顯卡來講,onscreen到offscreen的上下文環境切換是很是昂貴的(涉及到OpenGL的pipelines和barrier等),
會形成offscreen rendring的操做有:
- layer.mask 的使用
- layer.maskToBounds 的使用
- layer.allowsGroupOpacity 設置爲yes 和 layer.opacity 小於1.0
- layer.shouldRasterize 設置爲yes
- layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing
Blending 會致使性能的損失。在iOS的圖形處理中,Blending主要指的是混合像素顏色的計算。最直觀的例子就是,咱們把兩個圖層疊加在一塊兒,若是第一個圖層的透明的,則最終像素的顏色計算須要將第二個圖層也考慮進來。這一過程即爲Blending。更多的計算,致使性能的損失,在一些不須要透明度的地方,能夠設置alpha爲1.0 或者減小圖層的疊加。
Rasterize啓用shouldRasterize屬性會將圖層繪製到一個屏幕以外的圖像。而後這個圖像將會被緩存起來並繪製到實際圖層的contents和子圖層。若是有不少的子圖層或者有複雜的效果應用,這樣作就會比重繪全部事務的全部幀划得來得多。可是光柵化原始圖像須要時間,並且還會消耗額外的內存。 當咱們使用得當時,光柵化能夠提供很大的性能優點可是必定要避免做用在內容不斷變更的圖層上,不然它緩存方面的好處就會消失,並且會讓性能變的更糟。
##優化點
###加載 應用能夠經過減小IO次數來優化圖像加載性能。這時候就可使用精靈序列來把多張小圖合成一張大圖。 這樣就能極大的減小IO次數了。
由於IOS設備的限制,大圖的大小不能超過2048 * 2048,有部分設備上這個限制能夠達到4096 * 4096,但咱們這裏以小的爲準。因此一張大圖內最多隻能容納10張測試用的小圖。
精靈序列這種技術在cocos2d-x等平臺上使用的不少。把多張小圖拼成一張大圖時可使用TexturePacker軟件來完成。 #####精靈序列
<img src="http://ww2.sinaimg.cn/mw690/6f298b86gw1f5l5fyfogvj20lj0o9do9.jpg" height="291" width="258.3"/>
簡單來講精靈序列就是把多張小圖拼成一張大圖,附加一份plist文件保存着每一張小圖對應在大圖上的位置。而後iOS上就能夠經過以下代碼在不一樣小圖之間切換。
layer.contents = (id)img_.CGImage;//img爲對應的大圖 layer.contentsRect = CGRectMake(0.1, 0.1, 0.2, 0.2);//contentsRect就是對應大圖上小圖的位置,更改這個值就能夠在不一樣的小圖間切換了。
#####精靈序列與普通幀動畫的性能對比 下面的性能對比是經過20張小圖和小圖拼接而成的2張大圖完成一組動畫的對比數據。 測試機型是iPhone4s.
1.文件大小
小圖總大小 | 大圖總大小 |
---|---|
758K | 827K |
由於大圖內很難平鋪全部小圖,因此大圖內很容易有空白像素,這就致使拼接後大圖的總分辨率很容易超過全部小圖的總分辨率,也就形成大圖的體積會比全部小圖的體積大。可是也有大圖的體積小於全部小圖整體積的狀況,由於TexturePacker在拼接的時候會把小圖內空白的像素適當的作點裁剪,而後把這個偏移值保存在plist文件內。
2.加載速度
小圖的加載時間 | 大圖的加載時間 |
---|---|
≈25ms | ≈5ms |
一張大圖能容納10張小圖,極大的減少了IO數量,因此加載速度能大大提高。
3.解碼時間
小圖的解碼時間 | 大圖的解碼時間 |
---|---|
≈35ms | ≈40ms |
由於大圖的數據量比較大,因此大圖的整體解碼時間要比小圖的解碼時間長。
4.CPU佔用(CPU佔用是經過同時執行兩種動畫,而後分別計算出兩種動畫的CPU佔用率)
小圖方案的CPU佔用率 | 大圖方案的CPU佔用率 |
---|---|
≈8% 峯值比較高 | ≈20% 峯值比較低 |
佔用CPU最多的一個是從本地加載數據,另外一個是圖片解碼。由於大圖的IO次數比較少,因此加載大圖時的CPU佔用率要比加載全部小圖時的低,可是大圖的解碼和兩張大圖切換時比較耗費CPU。整體來講大圖這種方案的CPU佔用率要高一點。
5.內存佔用
小圖方案的內存佔用 | 大圖方案的內存佔用 |
---|---|
≈27MB | ≈30MB |
6.幀率
小圖方案的幀率 | 大圖方案的幀率 |
---|---|
≈7fps | ≈7fps |
大圖方案和小圖方案的幀率基本一致。
#####精靈序列總結
整體來講精靈序列這種方案能明顯減少圖片開始加載到動畫開始播放的延遲。可是精靈序列的文件大小容易變大,而且CPU佔用率也要高一點。
-- | 文件大小 | 加載速度 | 解碼時間 | CPU佔用 | 內存佔用 ------------ | ------------- | ------------- | ------------- | ------------- 經過大圖實現的精靈序列 | 大 | 快 | 長 | 高 | 大 經過小圖實現的普通幀動畫 | 小 | 慢 | 短 | 低 | 小
精靈序列這種方案或許也能夠直接用解碼後的數據做爲原圖數據,這樣雖然會讓內存佔用更高,文件大小進一步加大,可是能下降解碼時間和CPU佔用率。這個能夠做爲一個優化點繼續研究一下。
精靈序列的風險點:
###延遲解碼 根據上文的原理分析,針對多圖幀動畫,應用能夠將圖片解碼延遲到圖片渲染前,不要讓圖片加載後立馬開始解碼。這樣也能下降圖片加載到動畫開始播放的延遲。
###緩存 由於QQ羣的送禮物功能中同一副幀動畫重複播放的頻率並不高,因此不須要考慮對原圖或者對解碼後位圖數據作緩存。
###渲染 渲染圖形方面應用只能在將須要繪製的內容提交給GPU前作一些優化。針對幀動畫這種場景,咱們能夠經過保證圖像素材作到像素對其,儘可能減小透明像素來作一些優化。
若是使用精靈序列來作幀動畫,應用必須經過CALayer進行渲染。播放幀動畫時能夠用NSTimer也可使用CADisplayLink來不斷的刷新圖像數據。
若是不使用精靈序列,應用也能夠經過自定義一個UIImageView,本身經過CADisplayLink或者NSTimer實現幀動畫,這樣的話應用就能夠自由控制圖像解碼的時間,圖像數據的緩存等。
##總結 本文經過對圖片加載,多圖作幀動畫進行了一些原理分析,而且給出了一些優化點。也簡單介紹了一下用精靈序列作幀動畫的方案。除了QQ羣送禮物的功能外其餘經過圖片作幀動畫的功能也能夠針對具體的業務狀況選取其中的一些優化點進行一些優化。
更多精彩內容歡迎關注騰訊優測的微信公衆帳號:
騰訊優測是專業的移動雲測試平臺,爲應用、遊戲,H5混合應用的研發團隊提供產品質量檢測與問題解決服務。不只在線上平臺提供「全面兼容測試」、「雲手機」等多種質量檢測工具,同時在線下爲VIP客戶配備專家團隊,提供定製化綜合測試解決方案。真機實驗室配備上千款手機,覆蓋億級用戶,7*24小時在線運行,爲各種測試工具提供支持。