http://www.ox-holdings.com

这些库的命名处处体现着技术和人文的结合,一个高效、开源、Android设备上的媒体管理框架

摘要LKImageKit 是一个来自腾讯的高性能iOS平台图片框架,包括了图片控件,图片下载、内存缓存、磁盘缓存、图片解码、图片处理等一系列能力。合理的架构和线程模型,并特别针对不同场景进行优化,能充分发挥硬件的性能。基本介绍LKImageKit 是一个高性能的图片框架,包括了图片控件,图片下载、内存缓存、磁盘缓存、图片解码、图片处理等一系列能力。合理的架构和线程模型,并特别针对不同场景进行优化,能充分发挥硬件的性能。该框架具有高度的扩展性。在此框架下,开发者可以自定义图片框架中的任何一个部分,比如:自定义图片显示逻辑、自定义缓存、自定义下载组件、自定义解码器、自定义图片处理算法等等。该组件旨在提供 iOS 平台上使用最简单,功能最强大的高性能图片解决方案。组件特性提供演示视频和 DEMODEMO中演示了如何在图片墙场景的数千张图片下,配合预加载、优先级控制、分级加载等技术,实现图片在快速滑动场景的高速下载和显示模块插件化可定制缓存、解码、加载、绘制等多个模块支持取消不再显示的图片迅速取消请求,节约内存占用支持优先级、优先级可动态调整通过对不同区域优先级的设置,使页面加载获得更好的体验支持预加载可以预先加载图片,预加载和图片正常显示会自动合并动图支持支持多图动态播放,包括正向播放、逆向播放、来回播放等雪碧图支持提供将雪碧图解码成序列帧的能力滤镜支持支持在图片显示前异步对图片进行滤镜处理渐进式加载支持图片边下载边显示多级加载支持多级请求,比如先加载小图再加载大图后台解码使用后台线程解码,提升页面流畅度请求合并相同类型的请求会被合并,不会导致重复的运算和下载并发数控制可以分别对加载、解码、处理等多个模块进行分别并发控制API调用顺序无关无需考虑 API 调用顺序,并不需要将 setURL 作为发送请求的接口加载有多快,有图有真相!开源地址详见:

支持流式,图片的渐进式呈现

磁盘缓存

当加载网络图片时,我们往往会将图片下载下来,缓存在磁盘中,因此会涉及到磁盘缓存。Picasso 内置了图片下载器 OkHttp3Downloader,本质上是使用自家的 OkHttp 进行图片下载,并内置了缓存策略 DiskLruCache,默认可缓存的文件大小总数为 50M 。值得一提的是,DiskLruCache 也是由 JakeWharton 提供的。

如果需要更换图片下载器和磁盘缓存策略,则可以自定义 Downloader 的实现类进行扩展。

以上所述的线程池、缓存策略等均是面向接口编程,因此都可以扩展,扩展的套路便是在 Picasso.Builder 中设置属性,这种建造者模式的写法我们见惯不怪,源码中的方法声明如下:

public Builder executor(@NonNull ExecutorService executorService){}
public Builder memoryCache(@NonNull Cache memoryCache){}
public Builder downloader(@NonNull Downloader downloader){}
2、当来回滑动 ScrollView,如何避免 Cell 反复发起异步请求?

这种情况经常出现,如果脱离业务来思考,对于同一个异步请求多次调用,应该使用一个数组来将所有发起请求的 Block 回调存储起来,并且若正在异步请求要及时返回,当异步请求完成,遍历数组中的回调 Block 分别调用。

实际上关于网络的框架都有类似的处理,比如 SDWebImage,它通过 URL 来判断是否是重复的请求。

落地到图片浏览器中,若想判断某个异步请求是否是同一个,通过请求参数来判断有些复杂,最直接的方法就是把异步请求都写在 data 中,比如图片压缩异步请求,对于同一个 data 就很好判断是否正在压缩,只需要一个 BOOL 值。

在图片浏览器的功能设计中,笔者加入了预加载的功能,也就是说,data 中的这些异步操作并不都是在显示界面的时候由 cell 来调用,而是在创建 data 的时候就会调用。

比如在创建网络图片 data 的时候,就要发起异步请求下载图片,而当图片浏览器展示当前 data 对应的 cell 的时候,异步请求还未完成,cell 又调用 data 发起了相同的异步请求。这时候在异步请求中就要用一个指针存储这个 cell 发起异步请求的回调 Block,在异步请求成功的时候调用这个 Block,这带来了潜在的循环引用问题,并且代码观感非常差。

并且实际情况比这个更为复杂,在笔者的图片浏览器中,一个 data 需要进行的异步请求可能有好几个,比如异步查询缓存、异步解压、异步下载、异步压缩、异步裁剪,若统统使用这种方式处理,将会是代码维护的灾难。

问题的本质就是,data 中的异步任务结果要在 cell 需要的时候通知它,而在 cell 不需要的时候默默执行。

笔者最终决定采用观察者模式,考虑到业务的特殊性,对于同一个 data,基本上异步操作是串联的,也就是说,不会在下载的同时异步压缩,不会在异步查询缓存的时候下载。所以,基本上同一时刻,data 的状态是唯一的,如此,对于组件中的 YBImageBrowseCellData,定制了一系列的状态:

typedef NS_ENUM(NSInteger, YBImageBrowseCellDataState) { YBImageBrowseCellDataStateInvalid, YBImageBrowseCellDataStateImageReady, ... YBImageBrowseCellDataStateIsDownloading, YBImageBrowseCellDataStateDownloadProcess, YBImageBrowseCellDataStateDownloadSuccess, YBImageBrowseCellDataStateDownloadFailed,};

在异步请求的过程中,更新这些状态。

而对于 cell,只需要在赋值 data 的时候观察这个 state,在进入复用池等情况移除就行了。state 改变的时候,就做一些 UI 操作,比如 YBImageBrowseCellDataStateDownloadProcess 更新下载进度条,在YBImageBrowseCellDataStateDownloadFailed 显示下载失败文案。

这是观察者模式比较好的实践,但有一点需要注意,若有某些异步任务不是串联的,需要设置另外一个 state 枚举。

有两个概念,一个是设备的方向通过 UIDeviceOrientationDidChangeNotification 添加通知,一个是状态栏的方向通过 UIApplicationDidChangeStatusBarOrientationNotification 添加通知。

通常情况下,状态栏的方向可以确定当前控制器的布局方向,所以通过监听状态栏的方向更新子视图的布局。

组件采用 UIViewController 作为主体,通过重写如下方法自定义旋转方向:

- shouldAutorotate { return YES;}- (UIInterfaceOrientationMask)supportedInterfaceOrientations { return self.supportedOrientations;}

但其实当前控制器实际允许旋转的方向受很多因素控制。一是 general -> deployment info -> Device Orientation 中勾选的设备支持的旋转方向,它的优先级是最高的;二是在 AppDelegate 中实现的 <UIApplicationDelegate> 代理方法 -application:supportedInterfaceOrientationsForWindow:,它的优先级次之;三是若当前控制器是栈内的,它的旋转方向由 UINavagationController 重载的 -shouldAutorotate-supportedInterfaceOrientations 方法控制,若存在 UITabBarController,它将控制它管理的那些控制器的旋转方向。

所以,实际上组件内部可以说无法准确的获取到 YBImageBrowser 这个控制器实际支持的方向,这些逻辑需要开发者自行去解决。

动画的支持

内存缓存

LruCache 为 Picasso 中的缓存实现,该类的主要实现与 Android 默认提供的基本一致,区别有两点:

  1. 前者重载了构造器,定制了缓存大小的计算,其计算逻辑为:应用所分配内存的 15% ,源码在 Utils.calculateMemoryCacheSize(context) 中,缓存大小的申请比例也可以作为有类似应用场景时的参考。
  2. 抽象出接口 Cache,面向接口编程,如此一来,只要开发者提供实现类,便可扩展缓存策略。
SDImageCache.shared().store(UIImage.init(named: ""), forKey: "")
TODO

关于自定义转场,需要设置如下代码:

self.transitioningDelegate = ...;self.modalPresentationStyle = UIModalPresentationCustom;

UIModalPresentationCustom 模式下,才能做到完美的出场和入场动效,但是有个非常蛋疼的地方,若在该模式下,图片浏览器旋转的时候,它的 presentingViewController 会跟着旋转,不管 presentingViewController 是否支持这个方向。然后在图片浏览器 dismiss 的时候,presentingViewController 方向并不会恢复。

这个问题笔者未找到完美的解决方案,看了一下“微博”的图片浏览器貌似也是类似的实现方式,在横屏的时候出场是立即触发的,猜测可能是此刻将屏幕旋转回来。

所以,尝试了一下,若当前图片浏览器的方向和 presentingViewController 起始的方向不同,将取消手势交互动效,直接 dimiss 转场,并且在转场的同时强制旋转屏幕。

然而预期的效果和“微博”并不一样,强制转场有一定的延时。若读者朋友有解决方案还望指点一下,目前就采用这个处理方案,作为一个待完成的优化吧。

上一个版本是使用 SDWebImage + FLAnimatedImage 来处理的,但是感觉使用体验不太好,在创建本地图片的时候需要用户判断当前图片是不是 gif,所以后来笔者选择了功能更强、代码质量很高的 YYImage 做为 GIF 的处理框架,它还支持 APNG、WebP 等格式,使用也很简单,完全兼容 UIImage。YYImage 原理可看笔者的一篇博客:YYImage 源码剖析:图片处理技巧。

它的内存缓存就是一个 hash 容器,没有缓存策略,不及基于 LRU 淘汰算法的 YYMemeryCache。

SDWebImage 缓存策略中有一个逻辑,在磁盘缓存中查找到了缓存,会解压过后放入内存缓存,若这个图片是 GIF 的,它就会解压为第一帧图片,不能满足我们的需求。

从解压过后是否放入缓存说起:它是由 [SDImageCache sharedImageCache].config.shouldCacheImagesInMemory 决定的,所以一开始我想要在框架生命周期内禁止它。然而 shouldCacheImagesInMemory 同时决定了调用 -stroreImage:imageData:forKey:toDisk 的时候是否缓存到内存,所以这个属性是不能设置为 NO 的,否则内存缓存永远存不进去。

发现了么,死循环,要想 -stroreImage:imageData:forKey:toDisk 支持内存缓存,就要 shouldCacheImagesInMemory 为 YES,而它为 YES 就会错误的同步 GIF 的第一帧到内存缓存。

以 SD 的思路,最好的解决方案就是使用 SDWebImage 的 GIF 分类 + FLAnimatedImage 显示了,SD 解压的 GIF 图片类型可以由 FLAnimatedImageView 解析。这个设计让我有些无语,有种捆绑销售的感觉

郑重声明:本文版权归新匍京a奥门-最全网站手机版app官方下载所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。