Android中的图片加载框架


概述

引子

对于很多App来说,都会有图片加载的需求,毕竟有图片的App才能显得更加多姿多彩。要实现图片加载,其基础的原理并不麻烦,大概有这么几点:

  • 图片应该是异步加载的,从网络上下载图片会是一个比较耗时的过程,如果是在UI主线程里进行,那么ANR肯定无法避免了。任何耗时操作都在异步线程里进行

  • 图片从网上下载完之后,需要在本地进行Bitmap的处理,最重要的就是图片的压缩。这一步是比较难办的,压缩的力度太大,图片的显示效果就会比较差;压缩的不够,那么图片就会占用大量内存,动辄就搞出OOM

  • 最好实现一个自己的缓存策略,即把图片下载到本地内存和磁盘中,需要保存下来并有一定的策略,这样下次再加载同样的图片的时候,就可以直接在本地加载而不需要去网络下载了,大大提升了加载速度并节省了流量,当然这一步也是比较麻烦的

实现了以上3点,你就可以做出一个基本的图片加载库了,当然,可能会比较粗糙。实际上呢,网上有大量的图片加载库,而且是开源的,最重要的在于,这些库都有各自的缓存策略,而且经过了大量开发者的实践考验,都很可靠。其中,比较有名的有:Universal-Image-Loader、Picasso、Glide和Fresco,今天的工作就是对它们进行比较。

四大图片缓存框架基本信息

基本概念

在正式对比前,先了解几个图片缓存框架通用的概念:

  • RequestManager:请求生成和管理模块
  • Engine:引擎部分,负责创建任务(获取数据),并调度执行
  • GetDataInterface:数据获取接口,负责从各个数据源获取数据

    比如 MemoryCache 从内存缓存获取数据、DiskCache 从本地缓存获取数据,下载器从网络获取数据等。

  • Displayer:资源(图片)显示器,用于显示或操作资源。

    比如 ImageView,这几个图片缓存都不仅仅支持 ImageView,同时支持其他 View 以及虚拟的 Displayer 概念。

  • Processor 资源(图片)处理器

    负责处理资源,比如旋转、压缩、截取等。

以上概念的称呼在不同图片缓存框架中可能不同,比如 Displayer 在 ImageLoader 中叫做 ImageAware,在 Picasso 和 Glide 中叫做 Target

共同的优点

  • 使用简单

    一句代码就可以实现图片获取和显示

  • 可配置度高,自适应程度高

    图片缓存的下载器(重试机制)、解码器、显示器、处理器、内存缓存、本地缓存、线程池、缓存算法等大都可轻松配置。
    自适应程度高,根据系统性能初始化缓存配置、系统信息变更后动态调整策略。比如根据 CPU 核数确定最大并发数,根据可用内存确定内存缓存大小,网络状态变化时调整最大并发数等。

  • 多级缓存

    至少两级缓存,提高加载速率

  • 支持多种数据源

    网络、本地、资源、Assets等

  • 支持多种displayer

    不仅支持ImageView 还支持其他的View以及虚拟的 Displayer 概念等

  • 其他小的共同点包括支持动画、支持 transform 处理、获取 EXIF 信息等


Universal Image Loader(UIL)

Github

很早开源的图片缓存,在早期被很多应用使用,作者前阵子(2015/11/27)宣布不再维护这个项目了,换言之,以后它不会更新了——虽然有可能被其他开发者接手,但考虑到现在图片加载库十分丰富,有这个兴致的人怕是不多。如果你要开发新项目,可以不考虑UIL了,但如果是一个比较老而又庞大的项目并且使用了UIL,那么倒也不必太着急替换它,至少目前它还是能很好的完成图片加载任务的,不推荐使用

总体设计及流程

上面是 ImageLoader 的总体设计图。整个库分为 ImageLoaderEngine,Cache 及 ImageDownloader,ImageDecoder,BitmapDisplayer,BitmapProcessor 五大模块,其中 Cache 分为 MemoryCache 和 DiskCache 两部分。

简单的讲就是 ImageLoader 收到加载及显示图片的任务,并将它交给 ImageLoaderEngine,ImageLoaderEngine 分发任务到具体线程池去执行,任务通过 Cache 及 ImageDownloader 获取图片,中间可能经过 BitmapProcessor 和 ImageDecoder 处理,最终转换为Bitmap 交给 BitmapDisplayer 在 ImageAware 中显示。

添加库文件

universal-image-loader-1.9.5.jar

基本使用

// 单例模式 在application中创建配置图片的参数,直接使用
ImageLoader imageloader = Imageloader.getInstance();
imageloder.displayerImage(imagerUrl[position],holder.image,
options,loadingListener)

使用参考

特点

  • 支持下载进度监听

    如果你有特殊需求,则可以在图片开始下载前、刚开始下载等各个时间段来做一些额外的事情,非常方便

  • 可以在View滚动中暂停图片加载

    通过pauseOnScrollListener接口可以在view滚动中暂停图片加载,有利于提升界面的流畅度

  • 默认实现多种内存缓存算法(多种缓存策略)

    这几个图片缓存框架都可以配置缓存算法,不过 ImageLoader 默认实现了较多缓存算法,如 最大尺寸先删除、使用最少先删除、最近最少使用、先进先删除、时间最长先删除等。

  • 支持本地缓存文件名规则定义


Picasso

Github & 官网

Square 开源的项目,总体来看它比较小巧,但也有着一些自己的特色,最重要的是他的主导者是 JakeWharton,所以广为人知。Picasso使用起来比较简单,不过对于新项目来说,也不是很推荐,原因就在于,Glide比它更优秀,而且使用起来几乎是一样的。

总体设计及流程

上面是 Picasso 的总体设计图。整个库分为 Dispatcher,RequestHandler 及 Downloader,PicassoDrawable 等模块。

Dispatcher 负责分发和处理 Action,包括提交、暂停、继续、取消、网络状态变化、重试等等。

简单的讲就是 Picasso 收到加载及显示图片的任务,创建 Request 并将它交给 Dispatcher,Dispatcher 分发任务到具体 RequestHandler,任务通过 MemoryCache 及 Handler(数据获取接口) 获取图片,图片获取成功后通过 PicassoDrawable 显示到 Target 中。

需要注意的是上面 Data 的 File system 部分,Picasso 没有自定义本地缓存的接口,默认使用 http 的本地缓存,API 9 以上使用 okhttp,以下使用 Urlconnection,所以如果需要自定义本地缓存就需要重定义 Downloader。

添加依赖

compile 'com.square.picasso:picasso:2.5.1'

基本使用

Picasso.with(context)
   .load("图片url")
   .into(显示控件);

参考

特点

  • 自带统计监控功能

    支持图片缓存使用的监控,包括缓存命中率,已使用内存大小,节省的流量等

  • 支持优先级处理

    每次任务调度前会选择优先级高的任务,比如app页面的Banner的优先级高于Icon时很使用。

  • 支持延迟到图片尺寸计算完成加载

  • 支持飞行模式,并发线程根据网络类型而改变

    手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数,比如 wifi 最大并发为 4, 4g 为 3,3g 为 2。这里 Picasso 根据网络类型来决定最大并发数,而不是 CPU 核数。这里Picasso 根据网络类型来决定最大并发数,而不是cpu核数。

  • “无”本地缓存(本身没有什么缓存策略,而是依赖所用的网络库的缓存策略)

    无”本地缓存,不是说没有本地缓存,而是 Picasso 自己没有实现,交给了 Square 的另外一个网络库 okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。

  • 在adapter中需要取消已经不在视野范围的ImageView图片资源的加载,否则会导致图片错位,Picasso已经解决了这个问题。

  • 使用复杂的图片压缩转换来尽可能的减少内存消耗

  • 自带内存和磁盘二级缓存功能,存储的结构采用了LinkedHashMap, 底层的缓存功能使用的是LruCache类,缓存路径默认是放在应用目录的/cache/picasso-cache/

  • 如果加载发生错误会重复三次请求,三次都失败才会显示erro Place holder

    Picasso.with(context)
       .load(url)
       .placeholder(R.drawable.user_placeholder)
       .error(R.drawable.user_placeholder_error)
       .into(imageView);
    
  • 使用简单,源码简单易懂。Picasso 代码虽然只在一个包下,没有严格的包区分,但代码简单、逻辑清晰

  • Picasso的方式则因为需要在显示之前重新调整大小而导致一些延迟,Glide加载显示更快。

  • ARGB_8888

  • 包大小:1.2MB


Glide

Github

来自于Google员工bumptech的开源项目,被Google官方所推荐,专注于流畅的滚动,甚至在许多Android的原生应用中都采用了Glide来加载图片,可见其受推崇的程度,所以,推荐使用

总体设计及流程

上面是 Glide 的总体设计图。整个库分为 RequestManager(请求管理器),Engine(数据获取引擎)、 Fetcher(数据获取器)、MemoryCache(内存缓存)、DiskLRUCache、Transformation(图片处理)、Encoder(本地缓存存储)、Registry(图片类型及解析器配置)、Target(目标) 等模块。

简单的讲就是 Glide 收到加载及显示资源的任务,创建 Request 并将它交给RequestManager,Request 启动 Engine 去数据源获取资源(通过 Fetcher ),获取到后 Transformation 处理后交给 Target。

Glide 依赖于 DiskLRUCache、GifDecoder 等开源库去完成本地缓存和 Gif 图片解码工作。

添加依赖

compile 'com.github.bumptech.glide:glide:3.5.2'

基本使用

Glide.with(context)
   .load("图片url")
   .into(显示控件);

Picasso和Glide的基本使用方法是很相似的,从某种程度上说,Glide可以看作是Picasso的改进版。

详细使用方法

特点

  • 支持优先级处理(与Picasso类似)

  • Glide更易用,因为其with方法不但可以接受context,还可以接受Activity和Fragment,Context会自动的从它们中获取。同时将Activity/Fragment作为with的参数的好处是:图片加载会和Activity/Fragment的生命周期保持一致,比如Paused状态在暂停加载,在Resumed状态的时候又会自动重新开始加载,所以,建议传递参数Activity和Fragment给Glide,而不是Context

  • 支持trimMemory(OnTrimMemory是Android 4.0之后提供的API,系统会根据不同的内存状态来回调。根据不同的内存状态,来响应不同的内存释放策略。)Glide对每个Context都保持一个RequestManager ,通过FragmentTransaction保持和activity/fragment生命周期一致,并且有相应的trimMemory接口可以实现

  • 内存友好

    • Glide的内存缓存有个active的设计

      从内存缓存中取数据时,不像一般的实现用get,而是remove,再把这个缓存数据放到一个value为软引用的activeResurces map中,并计数引用数,在图片加载完成后进行判断,如果引用数为空,则回收掉。

  • 内存缓存更小的图片

    Glide以url, view_width,view_height,屏幕分辨率等作为联合的Key,将处理后的图片缓存在内存缓存中,而不是原始图片以节省大小。

  • 图片默认使用 RGB_565 而不是ARGB_888,内存占用比Picasso小一半,清晰度差,但是图片更小,也可以配置888

  • Picasso和Glide在磁盘缓存策略上有很大不同。Picasso缓存的是全尺寸的,而Glide缓存的是跟ImageView尺寸相同的。Glide的这种方式优点是加载显示快。而Picasso的方式则因为需要在显示之前重新调整大小而导致一些延迟

    比如加载的图片是1920*1080的大小,而在你的App中,显示该图片的ImageView大小只有1280*720,那么Glide就会很聪明的自动缓存1280*720大小的图片。

  • 图片缓存 -> 媒体缓存

    Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。甚至是 Video,所以更该当做一个媒体缓存,而Picasso不能。但是Glide 动画会消费太多的内存,因此谨慎使用

  • 默认使用HttpUrlConnection下载图片,可以配置为OkHttp或者Volley进行下载,也可以自定义下载方式。实际image-loader,Picasso都支持OkHttp,Volley

  • 默认使用手机内置存储进行磁盘缓存,可以配置为外部存储,可以配置缓存大小,图片池大小

  • 默认使用两个线程池来分别执行读取缓存和下载任务,都可以自定义

  • Glide 可以通过 signature 或不使用本地缓存支持 url 过期

  • 在页面不可见时停止网络请求,停止对图片的解析操作

  • 专注于流畅的滚动,当列表在滑动的时候,调用pauseRequests()取消请求,滑动停止时,调用resumeRequests()恢复请求。

  • Glide 功能强大,但代码量大、流转复杂。在较深掌握的情况下才推荐使用,免得出了问题难以下手解决

  • 使用Glide,你可以配置图片显示的动画,而Picasso只有一种动画:fading in

  • 可以使用thumbnail()产生一个你所加载图片的thumbnail

  • Glide还可以将任何的本地视频解码成一张静态图片

  • 将 ImageView 还原到真实大小时,Glide 加载的图片没有 Picasso 那么平滑

  • 包大小:1.3MB

自定义缓存大小

想要提高Glide的图片效果,可以创建一个新的 GlideModule 将 Bitmap 格式转换到 ARGB-8888。同时在 AndroidManifest.xml 中将 GlideModule 定义为 meta-data,如下:

  • 首先创建一个GlideModule子类实现GlideModule接口

    public class MyGlideModule implements GlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
         // Apply options to the builder here
    }
    @Override
    public void registerComponents(Context contxt, Glide glide) {
        // register ModuleLoaders here
    }
    }
    
  • 创建一个混淆文件, 针对于该GlideModule类的:

文件夹名: proguard.cfg

-keepnames class com.mypackage.MyGlideModule
# or more generally:
#-keep public class * implements com.bumptech.glide.module.GlideModule
  • 添加如下标签到AndroidManifest.xml文件中, 以便Glide可以找到你配置的GlideModule类

    <meta-data
           android:name="com.mypackage.MyGlideModule"
           android:value="GlideModule" />
    

修改了 Bitmap 格式后,Glide 将花费两倍于上次的内存,但是仍远远小于 Picasso 的内存开销,原因在于 Picasso 是加载了全尺寸的图片到内存,然后让 GPU 来实时重绘大小。而 Glide 加载的大小和 ImageView 的大小是一致的,当然,Picasso 也是可以指定加载图片大小的,但是问题在于你需要主动计算 ImageView 的大小,或者说你的 ImageView 大小是具体的值(而不是 wrap_content )

在加载图片这个问题上 Glide 完胜 Picasso,因为 Glide 可以自动计算出任意情况下的 ImageView 大小。

磁盘缓存Disk Cache

默认缓存在手机内置存储

builder.setDiskCache(new InternalCacheDiskCacheFactory(context,yourSizeInBytes));

缓存到外置的SD卡

builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, cacheDirectoty, yourSizeInBytes));

内存缓存Memory Cache

builder.setMemoryCache(new LruResourceCache(yourSizeInBytes));

Bitmap pool

builder.setBitmapPool(new LruBitmapPool(yourSizeInBytes));

Bitmap Format

builder.setDecodeFormat(DecodeFormat.ALWAYS_ARGB_8888);

Picasso 和 Glide 在磁盘缓存策略上有很大的不同。Picasso 缓存的是全尺寸的,而 Glide 缓存的是跟 ImageView 尺寸相同的。我们可以将 ImageView 调整成不同大小,但不管大小如何 Picasso 只缓存一个全尺寸的。Glide 则不同,它会为每种大小的 ImageView 缓存 一次。尽管一张图片已经缓存了一次,但是假如你要在另外一个地方再次以不同尺寸显示,需要重新下载,调整成新尺寸的大小,然后将这个尺寸的也缓存起来。具体说来就是:假如在第一个页面有一个 200×200 的 ImageView,在第二个页面有一个 100×100 的 ImageView,这两个 ImageView 本来是要显示同一张图片,却需要下载两次。不过,你可以通过代码改变这种行为,让Glide既缓存全尺寸又缓存其他尺寸,这样就使得下次在任何 ImageView 中加载图片的时候,全尺寸的图片将从缓存中取出,重新调整大小,然后缓存。
Glide 的这种方式优点是加载显示非常快。而 Picasso 的方式则因为需要在显示之前重新调整大小而导致一些延迟。不过 Glide 比 Picasso 需要更大的空间来缓存。


Fresco

Github & 官网

这个可以称为是Android平台上目前最为强大的图片加载库了,来源于地球人都知道的Facebook公司。

添加依赖

compile 'com.facebook.fresco:fresco:0.6.0+'

用法

  • application 中 初始化

    Fresco.initialize(context);

  • 布局文件中 加入命名空间

    \<LinearLayout

    xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:fresco="http://schemas.android.com/apk/res-auto"\>
    
  • 加入SimpleDraweeView

    \<com.facebook.drawee.view.SimpleDraweeView

    android:id="@+id/my_image_view" 
    android:layout_width="20dp" 
    android:layout_height="20dp" 
    fresco:placeholderImage="@drawable/my_drawable" />
    

    Uri uri = Uri.parse(“https://XXXX“);
    SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
    draweeView.setImageURI(uri);

特点

  • Fresco 中设计有一个叫做 image pipeline 的模块。它负责从网络,从本地文件系统,本地资源加载图片。为了最大限度节省空间和CPU时间,它含有3级缓存设计(2级内存,1级文件)。

  • Fresco 中设计有一个叫做 Drawees 模块,方便地显示loading图,当图片不再显示在屏幕上时,及时地释放内存和空间占用。

  • Fresco 支持 Android2.3(API level 9) 及其以上系统。

  • 内存管理(内存方面的表现更是无敌)

    解压后的图片,即Android中的Bitmap,占用大量的内存。大的内存占用势必引发更加频繁的GC。在5.0以下,GC将会显著地引发界面卡顿

在5.0以下系统,使用了Native缓存,Fresco将图片放到一个特别的内存区域。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。

为什么说是5.0以下,因为在5.0以后系统默认就是存储在Ashmem区了。

Android中的每个App的Java堆内存大小都是被严格控制的,每个对象都是使用Java的new在堆内存实例化,这是内存中相对安全的一块区域,内存有垃圾回收机制,App不在内存时,系统会自动回收内存。当内存进行垃圾回收机制时,还把Android应用完全终止了,也是使用App最常见的卡顿或假死的原因之一。

Ashmem Android 在操作Ashmem堆时,会把该堆中存有数据的内存区域从Ashmem对中抽取出来,而不是把它释放掉,这是一种弱内存释放模式; 被抽取出来的这部分内存只有当系统真正需要更多的内存那时候才会被释放,当Android把被抽取出来的这部分内存放回Ashmem堆,只是被抽取的内存空间没有被释放,之前的数据就被回复到相应的位置。

由于将图片放在Ashmem(匿名共享内存)中,大大降低了App的内存占用(因为Ashmem没有被统计到App的内存使用里),再加上各种优化,使得Fresco基本上告别了OOM。

Fresco 在低端机器上表现一样出色,你再也不用因图片内存占用而思前想后,但是源代码基于C/C++,阅读困难度提升。

  • 图片的渐进式呈现(类似WebView)

    渐进式的JPEG图片格式已经流行数年了,渐进式图片格式先呈现大致的图片轮廓,然后随着图片下载的继续,呈现逐渐清晰的图片,这对于移动设备,尤其是慢网络有极大的利好,可带来更好的用户体验。

Android 本身的图片库不支持此格式,但是Fresco支持。使用时,和往常一样,仅仅需要提供一个图片的URI即可,剩下的事情,Fresco会处理.

  • Gif图和WebP格式

    是的,支持加载Gif图(与Glide一样),支持WebP格式

  • 图像的呈现

    Fresco 的 Drawees 设计,带来一些有用的特性:

  • 自定义居中焦点(对人脸等图片显示非常有帮助)

    • 圆角图,当然圆圈也行。
    • 下载失败之后,点击重现下载
    • 自定义占位图,自定义overlay, 或者进度条
    • 指定用户按压时的overlay
  • 图像的加载

    Fresco 的 image pipeline 设计,允许用户在多方面控制图片的加载:

  • 为同一个图片指定不同的远程路径,或者使用已经存在本地缓存中的图片

    • 先显示一个低解析度的图片,等高清图下载完之后再显示高清图
    • 加载完成回调通知
    • 对于本地图,如有EXIF缩略图,在大图加载完成之前,可先显示缩略图
    • 缩放或者旋转图片
    • 处理已下载的图片
    • WebP 支持

      Fresco的图片直接显示为ARGB8888这种最高质量的级别,即使是在这种高质量的情况下依然保证了比其他库更少的内存占用,不得不佩服Facebook的实力。而且类似于进度监听、缓存策略等,也是应有尽有,总之作为一个图片加载库,Fresco在功能和性能方面已经趋于完美了。

  • 能够根据View的展示状态控制网络请求和图片解析的状态(在页面不可见时停止对图片的网络请求和解析操作,在页面可见时恢复操作)

  • 对外提供清除缓存的方法

  • 体积较大,集成后增大apk体积

  • 需要使用特定的view,需要xml支持

  • 包大小:4MB


总结

总体上来说,ImageLoader 的功能以及代理容易理解长度都一般。

Picasso与Glide有着近乎相同的 API 的使用风格,但 Glide 在缓存策略和加载 gif 方面略胜一筹。

Glide 和 Picasso 都是非常完美的库。Glide 加载图像以及磁盘缓存的方式都要优于 Picasso,速度更快,并且 Glide 更有利于减少 OutOfMemoryError 的发生,GIF 动画是 Glide 的杀手锏。不过Picasso 的图片质量更高。如果使用 Glide,建议将 Bitmap 格式换成 ARGB_8888、让 Glide 缓存同时缓存全尺寸和改变尺寸两种

Picasso 代码虽然只在一个包下,没有严格的包区分,但代码简单、逻辑清晰,一两个小时就能叫深入的了解完。

Glide 功能强大,但代码量大、流转复杂。在较深掌握的情况下才推荐使用,免得出了问题难以下手解决。

Picasso 所能实现的功能 Glide 都能做到,只是所需设置不同。两者的区别是 Picasso 比 Glide 体积小很多且图像质量比 Glide 高,但Glide 的速度比 Picasso 更快,Glide 的长处是处理大型的图片流,如 gif、video,如果要制作视频类应用,Glide 当为首选。

Fresco 可以说是综合了之前图片加载库的优点,其在5.0以下的内存优化非常好,但它的不足是体积太大,按体积进行比较:Fresco>Glide>Picasso,所以 Fresco 在图片较多的应用中更能凸显其价值,如果应用没有太多图片需求,不推荐使用 Fresco。

项目比较老,代码量较大,又没什么时间去大改,那么继续维持当前的选择是比较稳妥的办法。如果是新上马的项目,那么UIL由于不再维护、Picasso基本被Glide全方位超越,我推荐使用Glide或Fresco。如果你的App里,图片特别多,而且都是很大、质量很高的图片,而且你不太在乎App的体积(这可能吗?),那么Fresco就是很好的选择了,而Glide相比较Fresco要轻量一些,而且是Google官方推荐,所以在多数时候,会是开发者的首选。话说回来,如果你非常在意App的体积,不肯让App多增加多余的1KB,那么,也许自己实现一个图片加载库也是不错的选择哦,而且能很好的锻炼自己的能力。

参考资料
http://www.aichengxu.com/view/10139294
http://www.trinea.cn/android/android-image-cache-compare/
http://www.jianshu.com/p/ada9b90fa9e6
http://www.kaelli.com/15.html
http://blog.csdn.net/a910626/article/details/50688637
http://blog.qiji.tech/archives/6344
https://www.zhihu.com/question/40028112

QinPeng Zhu wechat
扫一扫,关注我的公众号获取更多资讯!
学习分享,感谢鼓励!