背景
之前蝸牛在瀑布流展示數(shù)據(jù)的時(shí)候發(fā)現(xiàn)gif多哩至,而且每個(gè)gif很大的時(shí)候內(nèi)存會(huì)暴漲酣倾,索性研究了一下圖片的加載過程苗桂,對(duì)比了SDWebImage和FLAnimatedImage 對(duì)gif的處理過程矗烛,做一個(gè)小結(jié)
知識(shí)背景
關(guān)于緩存:
當(dāng)我們通過imageNamed:
去讀取一張本地的圖片的時(shí)候玫镐,系統(tǒng)只是在Bundle那查找文件名倒戏,然后把這個(gè)文件放到UIImage里面返回,并沒有做實(shí)際的文件讀取和解碼恐似,當(dāng)UIImage第一次顯示到屏幕上時(shí)候杜跷,內(nèi)部的解碼方法才被調(diào)用,同時(shí)解碼的結(jié)果會(huì)被保存待一個(gè)全局的緩存中去矫夷。在圖片解碼后葛闷,App第一次退到后臺(tái)收到內(nèi)存警告,該圖片的緩存才會(huì)被清空双藕。
當(dāng)我們用imageWithData
去讀取一張圖片的時(shí)候淑趾,UIImage底層是通過調(diào)用ImageIO的CGImageSourceCreateWithData
方法去創(chuàng)建source對(duì)象的,這里的截圖截自FLAnimatedImage
,可以傳一個(gè)shouldCache字段忧陪,默認(rèn)條件下扣泊,這個(gè)參數(shù)是YES近范。所以用imageWithData
時(shí)候也不能避免解碼后圖片的緩存,而解碼發(fā)生的時(shí)機(jī)也是在圖片第一次顯示到屏幕上的時(shí)候延蟹。但是用這種方式解碼數(shù)據(jù)是被緩存到CGImage內(nèi)部评矩,如果這個(gè)圖片被釋放,內(nèi)部的解碼數(shù)據(jù)也會(huì)被釋放阱飘。
如何避免緩存
手動(dòng)調(diào)用 CGImageSourceCreateWithData()
來創(chuàng)建圖片斥杜,并把 ShouldCache
和 ShouldCacheImmediately
關(guān)掉。這么做會(huì)導(dǎo)致每次圖片顯示到屏幕時(shí)沥匈,解碼方法都會(huì)被調(diào)用蔗喂,造成很大的 CPU 占用。
如何提前解碼
- 把圖片用
CGContextDrawImage()
繪制到畫布上咐熙,然后把畫布的數(shù)據(jù)取出來當(dāng)作圖片弱恒。這也是常見的網(wǎng)絡(luò)圖片庫的做法。解碼消耗cpu資源 - 直接讀取
1.
CGImageSourceCreateWithData(data)
創(chuàng)建 ImageSource棋恼。
2.
CGImageSourceCreateImageAtIndex(source)
創(chuàng)建一個(gè)未解碼的 CGImage返弹。
3.
CGImageGetDataProvider(image)
獲取這個(gè)圖片的數(shù)據(jù)源。
4.
CGDataProviderCopyData(provider)
從數(shù)據(jù)源獲取直接解碼的數(shù)據(jù)爪飘。
ImageIO
解碼發(fā)生在最后一步义起,這樣獲得的數(shù)據(jù)是沒有經(jīng)過顏色類型轉(zhuǎn)換的原生數(shù)據(jù)(比如灰度圖像)。
SDWebImage對(duì)Gif的支持
無論圖片是從disk尋找圖片或者下載完成后sdwebImage
都會(huì)調(diào)用sd_animatedWithData:
如果是gif圖片走gif的加載邏輯师崎。
gif加載邏輯默终,這里注意兩點(diǎn):
1.當(dāng)圖像被解碼后,解碼數(shù)據(jù)是會(huì)被緩存的犁罩,而且被緩存在CGImage中齐蔽,生命周期和image一致。
2.到此得到的image還是沒有發(fā)生解碼床估。
那么對(duì)于SDWebImage 中對(duì)于gif圖的解碼發(fā)生在什么時(shí)候呢含滴,
decodedImageWithImage:
在SDWebImage中一共有三處調(diào)用
1. 加載完成的時(shí)候,只對(duì)非gif圖片有效丐巫,并且由shouldDecompressImages
屬性控制是否解碼谈况,默認(rèn)為YES。
2. 從Disk讀取image的時(shí)候递胧,非gif都有效碑韵,且由shouldDecompressImages
屬性控制。默認(rèn)為YES缎脾。
3. 在下載中的時(shí)候祝闻。因?yàn)樾枰鰣D片的漸變出現(xiàn)的效果,sdwebImage會(huì)在didReceiveData
中對(duì)加了一部分的圖片做解碼并傳遞給業(yè)務(wù)方遗菠。對(duì)gif和非gif都有效治筒,且由shouldDecompressImages
屬性控制屉栓。默認(rèn)為YES。
所以在默認(rèn)條件下耸袜,如果下載的資源為gif時(shí)候友多,在下載的過程中會(huì)對(duì)gif資源進(jìn)行decode,在從disk讀取gif后堤框,也會(huì)做一次decode域滥,由于讀取gif都是用CGImageSourceCreateWithData
(默認(rèn)參數(shù)) 或者 imageWithData
,所以decode后會(huì)帶有緩存蜈抓。且緩存的生命周期和image綁定启绰。所以解釋了,在有大gif的條件下沟使,為何默認(rèn)條件下進(jìn)入feed流會(huì)引起內(nèi)存飆高委可,但是CPU的的比較低。
decodedImageWithImage:
內(nèi)部做了判斷腊嗡,只解碼非gif圖片
默認(rèn)條件下着倾,所以默認(rèn)條件下,gif的解碼放到gif顯示的時(shí)候燕少,解碼后的buffer會(huì)被系統(tǒng)cache卡者,又由于SDWebImage帶有cache緩存,會(huì)cache剛剛使用過的image客们,所以退出feed流頁面后崇决,還是內(nèi)存還是居高不下,只有在手動(dòng)清理webImage的cache后底挫,內(nèi)存才會(huì)下降恒傻。
SDWebImage對(duì)gif的處理
為什么SDWebImage對(duì)gif的處理效率低,而且對(duì)大的gif來說尤為明顯建邓。
我們知道一張圖片從網(wǎng)絡(luò)到顯示盈厘,解碼的過程必不可少,解碼的過程必定需要消耗cpu資源涝缝,解碼后必定會(huì)使得內(nèi)存增大。如果提前解碼緩存譬重,cpu壓力小拒逮,但是內(nèi)存會(huì)高,如果不緩存臀规,cpu壓力大滩援,內(nèi)存使用少。這里的緩存包括代碼的內(nèi)存指定的內(nèi)存緩存和ios系統(tǒng)對(duì)解碼后的圖片的緩存塔嬉。
SDWebImage對(duì)gif的操作玩徊,即使關(guān)掉解碼和存儲(chǔ)到內(nèi)存(sd里的memoryCache)租悄,解碼數(shù)據(jù)還是會(huì)在展示的時(shí)候被系統(tǒng)緩存,所以性能不好恩袱。
FLAnimatedImageView對(duì)gif的處理
FLAnimatedImage 是由Flipboard開源的iOS平臺(tái)上播放GIF動(dòng)畫的一個(gè)優(yōu)秀解決方案泣棋,在內(nèi)存占用和播放體驗(yàn)都有不錯(cuò)的表現(xiàn)。
FLAnimatedImageView
的源碼結(jié)構(gòu)非常簡單畔塔,FLAnimatedImage
負(fù)責(zé)處理GIF潭辈,然后從緩存中提供給FLAnimatedImageView
當(dāng)前需要顯示的圖像
關(guān)鍵方法解析
初始化
- 初始化緩存字典
- 初始化imageSource,根據(jù)
kCGImageSourceShouldCache
的官方文檔描述, 所以設(shè)置kCGImageSourceShouldCache
為NO,可以避免系統(tǒng)對(duì)圖片進(jìn)行緩存,
Whether the image should be cached in a decoded form. The value of this key must be a CFBoolean value. The default value is kCFBooleanFalse in 32-bit, kCFBooleanTrue in 64-bit
.
- 判斷是否gif
- 取出gif播放次數(shù)
- 遍歷每幀圖片
- 取出幀圖片
- 取出的第一張圖片為GIF動(dòng)畫的封面圖片
- 取出幀圖片的信息
- 取出幀圖片的展示時(shí)間
- GIF動(dòng)畫緩存策略
- 確認(rèn)最佳的GIF動(dòng)畫的解碼后幀圖片緩存數(shù)量
讀取UIImage對(duì)象
- 對(duì)索引位置進(jìn)行判斷澈吨,避免出現(xiàn)越界情況
- 記錄當(dāng)前取出的幀圖片的索引位置
- 判斷GIF動(dòng)畫的幀圖片的是否全部緩存下來了,因?yàn)橛锌赡芫彺娌呗允蔷彺嫠械膸瑘D片
- 根據(jù)緩存策略得到接下來需要緩存的幀圖片索引把敢,
- 除去已經(jīng)緩存下來的幀圖片索引
- 將需要緩存索引扔給其他線程進(jìn)行解碼裝載,解碼的過程是在其他線程,不會(huì)發(fā)生堵塞谅辣,解碼也是通過
CGContextDrawImage
的方式進(jìn)行解碼修赞。這個(gè)和SDWebImage一致。 - 取出幀圖片
-
根據(jù)緩存策略清緩存
gif播放部分
gif播放部分在startAnimating
時(shí)候開開啟CADisplayLink
,進(jìn)行重繪桑阶。
這里有兩點(diǎn)需要注意柏副,
每次
CADisplayLink
回調(diào)取的都是cache里的圖片,如果cache里面沒有圖片联逻,就跳過這次繪制機(jī)會(huì)累加器的存在意義在與搓扯,避免反復(fù)去繪制同一幀
所以FLAnimatedImageView
和FLAnimatedImage
是標(biāo)準(zhǔn)的生產(chǎn)者和消費(fèi)者的模式。FLAnimatedImage
開啟一個(gè)子線程解碼圖片包归,FLAnimatedImageView
消費(fèi)圖片進(jìn)行展示锨推。
總結(jié)
其實(shí)SDWebImage
和FLAnimatedImageView
在gif的支持上,性能差別的主要原因有兩個(gè):
-
FLAnimatedImageView
存在一個(gè)緩存策略公壤,每次也只解碼一部分的幀數(shù)據(jù)换可,而且嚴(yán)格把控緩存數(shù)量。而SDWebImage
依賴于系統(tǒng)在展示的時(shí)候的統(tǒng)一的全部幀解碼厦幅,緩存的生命周期不好進(jìn)行細(xì)粒度的控制沾鳄。 -
FLAnimatedImageView
解碼的過程放到了子線程中,而SDWebImage
默認(rèn)對(duì)gif不解碼确憨,所以解碼發(fā)生在gif第一次顯示時(shí)候译荞,發(fā)生在主線程。
但是對(duì)于比較小且數(shù)量少的gif休弃,其實(shí)兩個(gè)性能差別不大吞歼,但是對(duì)于數(shù)量多或者gif本身比較大時(shí)候,性能差距會(huì)異常明顯塔猾。
注意:最新的SDWebImage
可以pod導(dǎo)入SDWebImage/GIF
,對(duì)gif的處理自動(dòng)支持了FLAnimatedImage