下图是一个客户端图片加载模块常见的处理流程。本文以UniversalImageLoader为例分析了这一流程,然后分析了Fresco的优势和问题,最终推荐大家使用Glide。
从UniversalImageLoader分析图片加载中需要处理的问题
网络
主要用于下载网络图片,在UIL中是将图片地址变为InputStream。UIL支持多种类型来源的图片显示,包括:
- 网络
- 文件
- Uri资源(如果是视频,会找到缩略图显示)
- assets
- drawable
缓存
- UIL使用两级缓存:磁盘缓存图片文件、内存缓存Bitmap
- UIL已经实现了多种缓存策略,但一般都使用LRU缓存
- UIL可以指定图片缓存的路径,和缓存文件名的生成规则
- 需要自己确定缓存的大小,确定内存缓存的大小尤其重要
- 通过
ActivityManager#getMemoryClass
获得单个应用的最大内存,最多划分1/4的最大内存,否则容易导致OOM。???Runtime.getRuntime().maxMemory() - 图片较多,大图较多的应用,需要使用较大的缓存,提高缓存的命中率
- 内存也不宜太小,最少应该能缓存2~3个屏幕大小的Bitmap
- 通过
解码
- 要按需解码,否则会造成内存的浪费,主要通过
options.inJustDecodeBounds
进行预解析 - ImageAware可以帮助控制解码图片的大小,ImageViewAware就是对ImageView的一个封装
- 注意照片方向Exif,避免图片错误旋转
显示
实现接口BitmapDisplayer可以自定义显示效果,已实现的包括:带描边的圆形、渐入、圆角矩形(不能四个角分别指定)等。
某些设计可能会出现两个角圆角、另外两个直角的特殊裁剪模式。自己实现这类Displayer时,不要生成一个新的Bitmap,定义一个Drawable会更高效。因为生成新的Bitmap会引起内存分配和回收,从而使GC更加频繁,而Drawable只是在绘制时会使用很少的计算资源。可以参考源码中RoundedBitmapDisplayer。
多线程
图片的下载和解码都需要再后台线程中处理,而且为了提高效率,一般都使用多个线程分别进行解码和网络请求。
共有三个Executor,分别用于
- 分发任务
- 处理已缓存图片
- 处理未缓存图片
已缓存的图片主要占用计算资源,未缓存的图片则主要占用网络资源,所以不应该在一个Executor中竞争。可以分别指定Executor的线程数量,UIL默认为3个。
监听
UIL向外提供了两类监听:ImageLoadingListener和ImageLoadingProgressListener
PauseOnScrollListener主要用于,在列表滚动时暂停图片加载,但在现在的RecyclerView中无法使用。需要自己使用ImageLoader#pause
和ImageLoader#resume
了解了UIL是如何处理图片加载的问题之后,其他的第三方库也都是大同小异,下面再介绍下Fresco和Glide。
Fresco的优势和问题
Fresco在解决图片加载问题上的思路和其他框架有很大的不同。它最大的问题有三个:
- 不能直接使用ImageView
- 源码很复杂,使用时写的代码也很复杂
- 需要指定宽高
相比UIL,它的优点主要包括:
- 5.0以下的系统上,使用ASHMEM,不会占用Java堆内容
- 多一级未解码图片的内存缓存,减少文件IO(很多Android机器使用1年之后变慢,很大的原因就是IO变慢了,所以这一优化的效果还是很显著的)
- 支持多图请求,可以在大图显示之前展现缩略图
- 支持Gif动画和渐进式JPEG(图片还未下载完成的时候就可以先显示一部分)
缓存和网络:Image Pipeline
在5.0系统以下,Image Pipeline 使用 pinned purgeables 将Bitmap数据避开Java堆内存,存在ashmem中。这要求图片不使用时,要显式地释放内存。SimpleDraweeView自动处理了这个释放过程,所以没有特殊情况,尽量使用SimpleDraweeView。
Fresco的缓存虽然有很多优势,但有一个问题:要直接从缓存中获取图片很麻烦。
不仅需要定义一个订阅者,还需要处理回收,甚至还需要找到一个合适的executor。
|
|
显示
修改图片的显示效果需要使用DraweeHolder,它不仅能控制图片的显示,还可以处理View的Touch事件。默认支持圆角和圆形。
另一个控制图片显示的方法是在Postprocessor
中修改Bitmap,但效率很低。
监听
- ControllerListener:监听图片显示的过程
- RequestListener:监听图片获取的过程
Glide是个不错的选择
优点
- 支持本地Video
- 分别控制每次请求的优先级
- 支持缩略图
- 可直接更新AppWidget和Notification中的图片
- 自定义图片转换效果,还可使用GPU转换(使用GPU处理图片变换,并保持到缓存文件中,在Demo中可以看到GPU变换的10种特效)
- 定义加载动画
- 很方便的使用图片裁剪服务
可参考Glide相关文章了解怎么通过自定义的GlideModule优化加载的图片。本文也提供给了参考代码,在代码中引入七牛裁图服务,同时也可体验10种GPU变化的特效。
关于Transformation和BitmapImageViewTarget的使用
Transformation#transform
:在缓存前对图片进行处理,处理之后的图片才会进行缓存。处理图片的过程中会产生额外的内存消耗,处理后的图片会占据独立的缓存空间。但第二次使用的时候,不再需要处理,直接从缓存中读取。BitmapImageViewTarget#setResource
:控制图片的显示逻辑,每次显示的时候都会处理。因此在setResource中应该定义特殊的Drawable来控制显示效果,而不应该对Bitmap进行处理。(Bitmap的频繁生成和回收会导致gc;Drawable是在绘制的时候,通过Paint设置特殊的绘制效果,不会产生新的Bitmap)