使用SDWebImage下载高分辨率图,导致内存暴增。
再进一步定位问题,发现内存暴增的罪魁祸首是SDWebImage,这个方法主要是对图像进行解压缩操作。
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
kBitsPerComponent,
0,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
下面谈谈 iOS 中图片的解压缩
http://blog.leichunfeng.com/blog/2017/02/20/talking-about-the-decompression-of-the-image-in-ios/
总结:
图片加载的工作流:
1、将UIImage赋值给UIImageView;
2、接着一个隐式的Transaction捕捉到UIImageView图层的变化;
3、在主线程的下一个runloop到来时,CoreAnimation提交了Transaction,这个过程会产生图片的copy操作,涉及以下几个步骤:
a、分配内存缓冲区进行文件io和解压缩操作;
b、将图片读入内存;
c、将压缩的图片数据解压缩为位图形式,这是一个非常消耗CPU的操作;
d、CoreAnimation将位图渲染到UIImageView的图层。
图片解压缩的过程其实就是将图片的二进制数据转换成像素数据的过程。
位图就是一个像素数组,数组中的每个像素就代表着图片中的一个点。我们在应用中经常用到的 JPEG 和 PNG 图片都是一种压缩的位图图形格式。只不过 PNG 图片是无损压缩,并且支持 alpha 通道,而 JPEG 图片则是有损压缩,可以指定 0-100% 的压缩比。
一张 PNG 图片,像素为 30 × 30 ,文件大小为 843B ,使用下面的代码
UIImage *image = [UIImage imageNamed:@"check_green"]; CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
就可以获取到这个图片的原始像素数据,大小为 3600B 。
解压缩后的图片大小 = 图片的像素宽 30 * 图片的像素高 30 * 每个像素所占的字节数 4
在将图片渲染到屏幕之前,必须先要得到图片的原始像素数据,才能执行后续的绘制操作,这就是为什么需要对图片解压缩的原因。
强制解压缩的原理
未解压缩的图片将要渲染到屏幕时,系统会在主线程对图片进行解压缩,而如果图片已经解压缩了,系统就不会再对图片进行解压缩。图片的解压缩不可避免,而且是在主线程执行,影响我们应用的性能,因此有一个更好的解决方案:在子线程提前对图片进行强制解压缩。
强制解压缩的原理就是对图片进行重新绘制,得到一张新的解压缩后的位图。其中,用到的最核心的函数是 :
CG_EXTERN CGContextRef __nullable CGBitmapContextCreate(void * __nullable data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef cg_nullable space, uint32_t bitmapInfo) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
//bitsPerComponent 表示存入内存中的每个像素中的每一个组件所占的位数;
//bytesPerRow 表示存入内存中的位图的每一行所占的字节数;
这个函数用于创建一个位图上下文,用来绘制一张宽 width 像素,高 height 像素的位图。
注意:这里创建的context是没有透明因素的。在UI渲染的时候,实际上是把多个图层按像素叠加计算的过程,需要对每一个像素进行 RGBA 的叠加计算。当某个 layer 的是不透明的,也就是 opaque 为 YES 时,GPU 可以直接忽略掉其下方的图层,这就减少了很多工作量。这也是调用 CGBitmapContextCreate 时 bitmapInfo 参数设置为忽略掉 alpha 通道的原因。
//绘制一个没有alpha通道的图像
// Draw the image into the context and retrieve the new bitmap image without alpha
//将原始位图绘制到上下文中
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
//创建一张新的解压缩后的位图
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
scale:image.scale
orientation:image.imageOrientation];
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
解压缩操作中,每一个像素点都会分配一个空间来存储相关值,那么分辨率越高的图片,就意味着更多数量的像素点,也就意味着需要分配更多的空间!所以对于高分辨率图来说,如果缓存解压缩之后的数据,即使是几M的图片,也是有可能消耗上G的内存!
网友评论