背景
之前公司APP中gif下载的主要代码就一句话NSData *gifData = [NSData dataWithContentsOfURL:[NSURL URLWithString:newUrlString]];
所有的下载都交给dataWithContentsOfURL:
处理。这种做法太过简单,随着业务的壮大,很多需求满足不了,并且这种方式不支持https,并且该方法阻塞线程,很多gif图片较大会有明显卡顿,因此急需自己创建一套gif下载器,用来展示gif图片。
思路
gif图片加载
gif图片加载和普通图片加载显示过程基本一致,无非就是先从缓存中获取,如果没命中就从沙盒缓存中获取,如果还没命中就从远程下载并缓存。唯一有区别的是,gif在列表中只需要显示一张静态的图片,在查看详情的时候需要加载动图,所以一个gif下载完成需要缓存2种图片。
gif图片缓存
缓存分两种,内存缓存和本地缓存。由于我们项目中使用SDWebImage,并且设置图片最大开销为100M,为了统一方便内存管理,我们直接使用SDImageCache进行读取和存储缓存。由于一张gif对应一个静态图片和一个动态图片,所以需要用两个key来区别是动态图还是静态图,但是一般一次下载图片只有一个key值(默认URL地址),所以在缓存图片时,在原来key值上加上前缀static_
和dynamic_
来区别。
沙盒缓存就比较简单了,我们是创建了2个目录,一个存放动态图,一个存放静态图。由于iOS本地沙盒文件名称不能太长(之前被坑过),所以我们采用md5(key+图片唯一标志)来保证名称唯一性和长度。
gif图片下载
下载的方式有很多种,这里我们打算采用AFNetworking中封装好的AFURLSessionManager
的downloadTaskWithRequest:progress:destination:completionHandler:
方法进行下载,因为其很好的封装了下载进度和指定存储地址,这正是我们业务上需要用到的。这里需要注意,AFURLSessionManager最好写成单例,否则有内存泄漏。
静态gif生成
老版本中SDWebImage显示gif图片都是静态的,不知道什么版本起就变成动态的了,这里用原始gif生成静态图片的代码就是参考老版本SDWebImage库中的代码。
静态gif生成.png
图片的展示形式
由于我们项目中是采用SDWebImage进行图片展示,因此为了代码上的统一,gif也采用UIImageView类别的形式进行加载图片。
图片加载.png
可以看到,加载方式比较简单明了,两个set方法和一个cancel方法就搞定动态和静态gif,还带有下载进度。
cell复用等问题
为了解决cell复用问题,实际上就是再每次赋值时取消UIImageView上次任务的block回调,用新的回调替换它。我们可以创建一个队列,可以采用NSOperationQueue和信号量结合,实现控制每次最多只有3个线程下载图片,每次下载任务都是以NSBlockOperation
形式加入队列,每次下载任务前检查下任务是否被取消,如果取消了就跳过该任务。这样就可以简单得实现cell复用问题的规避。这个原理和SDWebImage有点类似,具体可以参考其内部实现。
代码实现后遇到的问题
SDImageCache计算内存cost不准确
之前为了方便内存管理和图方便,采用SDImageCache进行缓存gif静态图和原图。但是实际测试后发现,明明限制最大内存消耗为100M,但是实际上会远远超过。排查后发现问题出现在这:
内存1.png
memCache计算cost主要是靠SDCacheCostForImage()方法,而SD计算Image在内存中的大小居然只是简单的根据公式:图片大小=图片长度x图片高度x图片比例x图片比例
内存2.png
一个gif原图里面有多张图片组成,如果按这个计算方法,只能算计出一张图片的大小,其误为gif有多少张就差多少倍(还不算其他时间等信息)。
再后来进过尝试,如果把Gif原图存内存中,随便一张网上下载的gif图片就占用了200M内存,远超限制100M,所以将gif原图存储到内存中不是一个明智的选择,所以我们对gif只采用沙盒缓存,不采用内存缓存,实际测试效果不会差太多。
Cell复用,划出屏幕的gif动图未释放
上面所述,gif显示会占用大量内存,一般都放在cell中的UIImageView中。这里叫考虑cell的复用了,由于cell复用机制,cell的imageView自然也不会释放,那么image也不会释放,存在内存中,所以如果是动态gif图片显示,还需要主要当cell划出显示区域后手动将UIImageView.image置为nil,减少内存占用。
总结
总体下载,本次尝试还是比较顺利的,中间遇到一些问题,最后也能得到很好的解决。由于gif图片较大,还测试出很多问题,比如长图,超大图片都有类似的内存问题。所以之后在内存管理方面还是要多留一个心眼,多测试极端情况。
网友评论