使用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的內存!