前语
项目中完结了一个歌曲列表,Cell 的内容是歌曲封面和标题的简单内容,但是在滑动时会出现卡顿,本文记录下排查进程。
运用 Instruments
关于卡顿掉帧问题大多数都是在主线程进行了较大的耗时操作,所以运用 Instruments 中 Time Profile 来检测下详细是什么方法耗时较长。
能够看到在 Runloop 的回调中执行了 CA::Transaction::commit(),之后调用了 ImageIO 框架进行图片的解码。那么问题来了,为什么加载其他图片不会?这里执行解码的类是 PNGReadPlugin
,会不会是 PNG 图片解码就会卡顿?
检查源码
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
if (image == nil) {
return NO;
}
// do not decode animated images
if (image.images != nil) {
return NO;
}
CGImageRef imageRef = image.CGImage;
BOOL hasAlpha = SDCGImageRefContainsAlpha(imageRef);
// do not decode images with alpha
if (hasAlpha) {
return NO;
}
return YES;
}
经过一番查找,找到了这段要害代码。能够看到,这里判断了图片是否包括 Alpha 通道,假如包括则不进行解码。所以方才的猜想是正确的,PNG 图片会包括 Alpha 通道,SDWebImage 不会对其进行解码,形成了卡顿。
之前关于文章《iOS 处理图片的一些小 Tip》中说到的“UIImage 第一次显现到屏幕时才会被解码”不太理解,这次算是遇上了。
解决计划
晋级 SDWebImage
由于一些历史原因,项目中运用的 SDWebImage 版别还是比较老的 4.2.3 版别,而最新版别的 SDWebImage 已经支持对包括 alpha 通道的 PNG 图片进行解码
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
// Avoid extra decode
if (image.sd_isDecoded) {
return NO;
}
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
if (image == nil) {
return NO;
}
// do not decode animated images
if (image.images != nil) {
return NO;
}
return YES;
}
服务端裁剪成小图
形成卡顿的另一个原因是直接运用了原图,分辨率较大,能够经过在图片 URL 上拼接相关参数获取一张小图,这个计划有许多优点:
- 显现效果更佳
- 节约用户流量
- 提高加载速度
延伸思考
NSData 转化成 UIImage 不是就完结解码了吗?
SDWebImage 的解码做了什么,和体系 ImageIO 的解码有什么不同?
体系为什么要把图片解码这么耗时的操作放到主线程?
带着这些疑问找到 SDWebImage 的首要保护作者的文章 干流图片加载库所运用的预解码究竟干了什么,解答了我的一切疑惑。
这里简单总结一下:
- NSData 转化成 UIImage 之后,它的 Bitmap 是没有当即创立的,这个 UIImage 仅仅包括一些元信息的空壳,等到最终显现到屏幕上才会创立。
- SDWebImage 等第三方库经过在子线程调用
CGContextDrawImage
让 ImageIO 当即解码分配 Bitmap 内存。 - iOS 前期设备的内存十分有限,一切图片都提早进行解码会增加内存的压力。