內(nèi)存過(guò)高
- 項(xiàng)目中有時(shí)候會(huì)遇到當(dāng)前頁(yè)面用到大量gif的情況,這個(gè)時(shí)候如果僅僅用SDWebImage去加載gif的話(huà),會(huì)出現(xiàn)內(nèi)存暴增的現(xiàn)象.
- 這是因?yàn)?SD在對(duì) gif 的處理過(guò)程中采用了一個(gè)數(shù)組存儲(chǔ) gif 的幀圖片雕什,當(dāng)有大量動(dòng)態(tài)圖時(shí),大量圖片存在內(nèi)存中,造成了內(nèi)存暴增的現(xiàn)象.
原因分析
- 先看SDWebImage的源代碼,SDWebImage通過(guò)這個(gè)類(lèi)UIImage+GIF.h來(lái)處理gif,我們進(jìn)入頭文件發(fā)現(xiàn)會(huì)調(diào)用一個(gè)+ (UIImage *)sd_animatedGIFWithData:(NSData *)data 這樣的類(lèi)方法
下面是這個(gè)方法的源代碼,我已經(jīng)加了很詳細(xì)的注釋,并且把問(wèn)題的所在也寫(xiě)的很清楚.
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data {
//安全判斷
if (!data) {
return nil;
}
//二進(jìn)制類(lèi)型的轉(zhuǎn)換
//CGImageSourceRef是個(gè)什么呢? 我們可以看到這是一個(gè)typedef CGImageSource * CGImageSourceRef;
//這是一個(gè)指針,CGImageSource是對(duì)圖像數(shù)據(jù)讀取任務(wù)的抽象扶供,通過(guò)它可以獲得圖像對(duì)象凌受、縮略圖涣脚、圖像的屬性(包括Exif信息)。
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
//獲取有幾張圖片
size_t count = CGImageSourceGetCount(source);
//返回的動(dòng)態(tài)圖片
UIImage *animatedImage;
//如果為一張圖片,那就只顯示一張圖片
if (count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data];
}
//如果為多張圖片,就開(kāi)始創(chuàng)建動(dòng)態(tài)圖片
else {
//集合 存放單張的圖片
NSMutableArray *images = [NSMutableArray array];
//時(shí)長(zhǎng)
NSTimeInterval duration = 0.0f;
for (size_t i = 0; i < count; i++) {
//取出gif單張的圖片
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
// 計(jì)算出單張圖片的播放時(shí)長(zhǎng)
duration += [self sd_frameDurationAtIndex:i source:source];
//添加到數(shù)組中
[images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
//釋放
CGImageRelease(image);
}
//安全判斷同時(shí)計(jì)算需要播放的時(shí)間
if (!duration) {
duration = (1.0f / 10.0f) * count;
}
//把靜態(tài)的圖片轉(zhuǎn)換為動(dòng)態(tài)的image,所以會(huì)有大量單張的圖片存放在內(nèi)存中
animatedImage = [UIImage animatedImageWithImages:images duration:duration];
}
//釋放圖像數(shù)據(jù)讀取任務(wù)的抽象對(duì)象
CFRelease(source);
//返回動(dòng)態(tài)圖片
return animatedImage;
}
解決方案
- gif播放其實(shí)就是一張一張圖片返回去,我們要寫(xiě)一個(gè)方法,只要不斷地取出當(dāng)前的那一張圖片,這樣就可以有效的避免內(nèi)存中存儲(chǔ)了大量圖片.那如何實(shí)現(xiàn)不斷地去取呢,我們可以開(kāi)一個(gè)定時(shí)器,定時(shí)器不斷的去掉我們寫(xiě)的方法,不斷地去取圖片賦值給imageView.
代碼重寫(xiě)
- 我采用創(chuàng)建一個(gè)UIImageView子類(lèi)來(lái)封裝定時(shí)器方法.
- 需要注意導(dǎo)入相關(guān)的頭文件<ImageIO/ImageIO.h>.
- 這個(gè)方法的本質(zhì)就是用一個(gè)定時(shí)器不斷的去獲取一張圖片給imageView,這樣就避免了大量圖片存入內(nèi)存中.
- 附上github源碼github源碼,懶得下的人,源碼就在下面.我也寫(xiě)了很詳細(xì)的注釋.
- 實(shí)例代碼中,在一個(gè)界面上用了6個(gè)gif,如果用原生的方法,內(nèi)存會(huì)達(dá)到130M左右,如果采用計(jì)時(shí)器方法,則只有30M左右
#import "WBWebImage.h"
#import <SDWebImageManager.h>
#import <NSData+ImageContentType.h>
#import <UIImage+GIF.h>
#import <ImageIO/ImageIO.h>
@implementation WBWebImage {
//記錄當(dāng)前是第幾張gif
NSInteger _currentIndex;
//定時(shí)器
NSTimer *_timer;
//gif圖片的二進(jìn)制數(shù)據(jù)
NSData *_data;
}
/*
//1.根據(jù)url去下載圖片的二進(jìn)制數(shù)據(jù)
//2.根據(jù)圖片的類(lèi)型判斷如果是gif特殊處理
//3.如果是其他類(lèi)型,直接顯示
*/
- (void)WB_downloadIMGOrGif:(NSURL *)url {
_timer = [NSTimer timerWithTimeInterval:0.12 target:self selector:@selector(updateIMG) userInfo:nil repeats:YES];
[self downloadIMGData:url];
}
- (void)updateIMG {
//不斷的調(diào)用生成gif的方法,并且不斷的賦值給imageView
self.image = [self wb_animatedGIFWithData:_data];
}
//下載圖片
- (void)downloadIMGData:(NSURL *)url {
//從管理者進(jìn)行查找
[[SDWebImageManager sharedManager].imageDownloader downloadImageWithURL:url options:0 progress:nil completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
if (error) {
NSLog(@"下載錯(cuò)誤%@",error);
return;
}
//根據(jù)圖片的類(lèi)型進(jìn)行判斷;
//UI操作放在主線(xiàn)程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if ([[NSData sd_contentTypeForImageData:data] isEqualToString:@"image/gif"]) {
//據(jù)圖片的類(lèi)型判斷如果是gif特殊處理
_data = data;
//將定時(shí)器加入到運(yùn)行循環(huán)中
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
} else {
self.image = image;
}
}];
}];
}
//原始代碼是把所有的gif全部加載處理完畢才去播放,內(nèi)存占用過(guò)多
//修改: 開(kāi)啟一個(gè)定時(shí)器,不斷的去gif中取出對(duì)應(yīng)的單張圖片
- (UIImage *)wb_animatedGIFWithData:(NSData *)data {
if (!data) {
return nil;
}
//類(lèi)型轉(zhuǎn)換
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
//幾張圖片
size_t count = CGImageSourceGetCount(source);
//返回的變量
UIImage *animatedImage;
if (count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data];
}
else {
//取出gif中的單張圖片
CGImageRef image = CGImageSourceCreateImageAtIndex(source, _currentIndex % count, NULL);
_currentIndex ++;
//類(lèi)型的轉(zhuǎn)換
animatedImage = [UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];
CGImageRelease(image);
}
CFRelease(source);
return animatedImage;
}
@end
總結(jié)
- 利用定時(shí)器方法,會(huì)稍微增加一些cpu的負(fù)荷,原因是cpu不斷的再計(jì)算.
- 利用圖片數(shù)組來(lái)做gif,雖然cpu輕松了,但是內(nèi)存負(fù)荷大.
- 兩個(gè)方法,一個(gè)是用性能去換空間,一個(gè)是用空間換性能.
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者