Android主流图片框架浅析

一、UIL——UniversalImageLoader

UIL可以算是老牌最火的图片加载库了,使用过这个框架的项目可以说多到教你做人,我第一次把第三方开源图片加载框架加入项目中的就是这个了,当时感觉瞬间逼格上涨,妈妈再也不用担心出现OOM和ListView图片错乱了。可惜的是该作者在项目中说明已经停止了对该项目的维护。这就意味着以后任何的 bug 都不会修复,任何的新特性都不会再继续开发,所以毫无疑问 UIL 不推荐在项目中使用了。

使用方法:

1、在Application全局变量中的进行配置ImageLoaderConfiguration,有选择性的进行配置,具体代码如下:

ImageLoaderConfiguration config =newImageLoaderConfiguration.Builder(context)
    .memoryCacheExtraOptions(480, 800)// default = device screen dimensions
    .discCacheExtraOptions(480, 800, CompressFormat.JPEG, 75)
    .taskExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
    .taskExecutorForCachedImages(AsyncTask.THREAD_POOL_EXECUTOR)
    .threadPoolSize(3)// default线程池数量
    .threadPriority(Thread.NORM_PRIORITY - 1)// default
    .tasksProcessingOrder(QueueProcessingType.FIFO)// default
    .denyCacheImageMultipleSizesInMemory()
    .memoryCache(newLruMemoryCache(2 * 1024 * 1024))//内存缓存
    .memoryCacheSize(2 * 1024 * 1024)
    .discCache(newUnlimitedDiscCache(cacheDir))// 磁盘缓存
    .discCacheSize(50 * 1024 * 1024)
    .discCacheFileCount(100)
    .discCacheFileNameGenerator(newHashCodeFileNameGenerator())// default
    .imageDownloader(newBaseImageDownloader(context))// default
    .imageDecoder(newBaseImageDecoder())// default
    .defaultDisplayImageOptions(DisplayImageOptions.createSimple())// default
    .enableLogging()
    .build();

2、针对每次加载任务进行配置DisplayImageOptions

DisplayImageOptions options =newDisplayImageOptions.Builder()
    .showStubImage(R.drawable.ic_stub)
    .showImageForEmptyUri(R.drawable.ic_empty)
    .showImageOnFail(R.drawable.ic_error)
    .resetViewBeforeLoading()
    .delayBeforeLoading(1000)
    .cacheInMemory()
    .cacheOnDisc()
    .preProcessor(...)
    .postProcessor(...)
    .extraForDownloader(...)
    .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2)// default
    .bitmapConfig(Bitmap.Config.ARGB_8888)// default
    .decodingOptions(...)
    .displayer(newSimpleBitmapDisplayer())// default
    .handler(newHandler())// default
    .build();

UIL支持的图片加载格式如下:

String imageUri ="http://site.com/image.png"; // from Web
String imageUri ="file:///mnt/sdcard/image.png"; // from SD card
String imageUri ="content://media/external/audio/albumart/13"; // from content provider
String imageUri ="assets://image.png"; // from assets
String imageUri ="drawable://"+ R.drawable.image; // from drawables (only images, non-9patch)

配置好后调用imageLoader.displayImage方法就OK了,妥妥的!

下面简单分析一下,UIL框架的加载原理:

  1. ImageLoader图片加载器,对外的主要 API,采取了单例模式,用于图片的加载和显示。
  2. MemoryCache图片内存换成。默认使用了 LRU 算法。 LRU: Least Recently Used 近期最少使用算法, 选用了基于链表结构的 LinkedHashMap 作为存储结构。假设情景:内存缓存设置的阈值只够存储两个 bitmap 对象,当 put 第三个 bitmap 对象时,将近期最少使用的 bitmap 对象移除。
  3. DiskCache图片磁盘缓存,默认使用LruDiskCache算法,在缓存满时删除最近最少使用的图片;缓存目录下名为journal的文件记录缓存的所有操作
  4. 图片加载流程
    1. 判断图片的内存缓存是否存在,若存在直接执行步骤 8;
    2. 判断图片的磁盘缓存是否存在,若存在直接执行步骤 5;
    3. ImageDownloader从网络上下载图片;
    4. 将图片缓存在磁盘上;
    5. ImageDecoder将图片 decode 成 bitmap 对象;
    6. BitmapProcessor根据DisplayImageOptions配置对图片进行预处理(Pre-process Bitmap);
    7. 将 bitmap 对象缓存到内存中;
    8. 根据DisplayImageOptions配置对图片进行后处理(Post-process Bitmap);
    9. 执行DisplayBitmapTask将图片显示在相应的控件上。

二、Picasso

Picasso是Square公司开源的一个Android平台上的图片加载框架,简单易用,一句话搞定项目中的图片加载,好用到令人发指。使用一句话:

Picasso.with(this).load("url").placeholder(R.mipmap.ic_default).into(imageView);

Picasso不仅实现了图片异步加载的功能,还解决了Android中加载图片时需要解决的一些常见问题:

  1. 在adapter中需要取消已经不在视野范围的ImageView图片资源的加载,否则会导致图片错位,Picasso已经解决了这个问题。
  2. 使用复杂的图片压缩转换来尽可能的减少内存消耗
  3. 自带内存和硬盘二级缓存功能

Picasso库的引入:

  1. 在线搜索picasso库,加入到gradle再同步即可引入picasso库
  2. 加入源码,加入后出现错误,把测试相关的内容删除,删除OKHttpDownloader,也可以导入okhttp包,源码中设置图片下载器的代码如下:
static Downloader createDefaultDownloader(Context context) {
  if (SDK_INT >= GINGERBREAD) {
      try {
        Class.forName("com.squareup.okhttp.OkHttpClient");
        return OkHttpLoaderCreator.create(context);
      } catch (ClassNotFoundException ignored) {
      }
  }
  return new UrlConnectionDownloader(context);
}

如果没有okhttp,把if语句注释,直接用默认的UrlConnectionDownloader进行图片下载

原理简要分析:

1、Picasso.with(Context):入手

public static Picasso with(Contextcontext){
      if(singleton==null){
              synchronized(Picasso.class){
                 if(singleton==null){
                    singleton=newBuilder(context).build();
                 }
              }
      }
    return singleton;
}

单列模式,保证多线程情况下,也只有一个实例。

/** Create the {@link Picasso} instance. 创建Picasso的实例 */
public Picassobuild(){
    Context context=this.context;
    if(downloader==null){
        downloader=Utils.createDefaultDownloader(context);
    }
    if(cache==null){
        cache=new LruCache(context);
    }
    if(service==null){
        service=new PicassoExecutorService();
    }
    if(transformer==null){
        transformer=RequestTransformer.IDENTITY;
    }
    Stats stats=newStats(cache);
    Dispatcher dispatcher=new Dispatcher(context,service,HANDLER,downloader,cache,stats);
    return new Picasso(context,dispatcher,cache,listener,transformer,
        requestHandlers,stats,indicatorsEnabled,loggingEnabled);
}

默认初始化了以下的参数:

Downloader
DownLoader就是下载用的工具类,在Picasso当中,如果OKHttp可以使用的话,就会默认使用OKHttp,如果无法使用的话,就会使用UrlConnectionDownloader(默认使用HttpURLConnection实现)。

Cache
默认实现为LruCache,就是使用LinkedHashMap实现的一个Cache类,注意的一个地方就是,在其他的地方,我们一般默认的是限制的capacity,但是这个地方我们是限制的总共使用的内存空间。因此LruCache在实现的时候,其实简单理解就是将LinkedHashMap封装,然后基于LinkedHashMap的方法实现Cache的方法,在Cache的set()方法的时候,会不断计算当前还可以使用的空间大小,要是超出范围,则删除之前保存的数据。

ExecutorService
默认的实现为PicassoExecutorService,该类也比较简单,其实就是ThreadPoolExecutor,在其功能的基础上继续封装,在其中有一个比较细心的功能就是,Picasso通过PicassoExecutorService设置线程数量,来调整在2G/3G/4G/WiFi不同网络情况下的不同表现。

RequestTransformer
ReqeustTransformer是一个接口,用来预处理Reqeust,可以用来将请求进行预先处理,比如改个域名啥的。

Stats
主要是一些统计信息,比如cache hit/miss,总共下载的文件大小,下载过的图片数量,转换的图片数量等等。

Dispatcher
Picasso当中,分发任务的线程,这是我们以后要重点研究的一个类,先标记一下,这个Dispatcher主要做了以下的事情:

启动了一个DispatcherThread线程初始化了一个用来处理消息的DispatcherHandler,注意,根据Dispatcher中默认配置,该Handler所有数据的处理是在DispatcherThread之上。初始化并注册了一个网络状态广播接收器。

2、图片加载流程:
1.初始化Picasso,实例化其唯一的对象。
2.根据传入的Url、File、resource Id,构建ReqeustCreator对象
3.根据ReqeustCreator构建Request对象,同时根据Reqeust属性,尝试从Cache中访问数据
4.Cache Hit,则通过回调,设置Target或者ImageView,完成该Reqeust
5.如果Cache Miss,那么则构建相应的Action,并提交到DispatcherThread当中。
6.Dispatcher中的Handler接收到相应的Message,调用dispatcher.performSubmit(action)进行处理。
7.创建BitmapHunter对象,并提交到PicassoExecutorService线程池
8.再次检查Memory Cache中已经有缓存,如果Hit,则读取缓存中的Bitmap
9.如果Cache miss,则交给Action对应的ReqeustHandler进行处理,比如网络请求,或者从File读取图片
10.返回结果之后,通知Dispatcher中的Handler处理结果。
11.DispatcherThread中将BitmapHunter的结果打包(batch),最快200ms打包一次。通知主线程HANDLER进行处理
12.主线程HANDLER接收打包的BitmapHunter,对最后的结果进行分发。

基本使用:

加载本地图片,并做压缩和旋转:

//加载本地图片
Picasso.with(this).load("file:///sdcard/Download/ddd.jpg").resize(100,100).rotate(180,0,0).into(mivPic);

加载网络图片,并设置不使用内存缓存中查找也不存储内存缓存,对于本地缓存,如果用okhttp,可以设这两者,如果是默认的downloader,只能设置NO_CACHE:

//加载网络图片
Picasso.with(this).
        load(mstrNetUrl).
        //networkPolicy(NetworkPolicy.NO_CACHE,NetworkPolicy.NO_STORE).
        memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE).
        into(mivPic);

加载资源图片,并自定义转换器:

public void getResPic(View view) {
    //加载资源图片
    Picasso.with(this).load(R.drawable.aaa).transform(new HalfTransformation()).into(mivPic);
}

自定义转换器对应的类

public class HalfTransformation implements Transformation{

    @Override
    public Bitmap transform(Bitmap source) {
        Matrix matrix = new Matrix();
        matrix.postScale(0.2f,0.2f);

        Bitmap newBitmap = Bitmap.createBitmap(source,0,0,source.getWidth(),source.getHeight(),matrix,true);
        source.recycle();//一定要回收原图
        return newBitmap;
    }

    @Override
    public String key() {
        return "HalfTransformation";
    }
}

设置占位图片:

Picasso.with(context)
    .load(url)
    .placeholder(R.drawable.user_placeholder)
    .error(R.drawable.user_placeholder_error)
.into(imageView);

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

注意:和Square的网络库一起能发挥最大作用,Picasso框架没有实现磁盘缓存,配合OkHttp进行实现。因为Picasso可以选择将网络请求的缓存部分交给了okhttp实现。

三、Glide

Glide 是 Google 一位员工的大作,他完全是基于 Picasso 的,沿袭了 Picasso 的简洁风格,但是在此做了大量优化与改进。

Glide 默认的 Bitmap 格式是 RGB_565 格式,而 Picasso 默认的是 ARGB_8888 格式,这个内存开销要小一半。

在磁盘缓存方面,Picasso 只会缓存原始尺寸的图片,而 Glide 缓存的是多种规格,也就意味着 Glide 会根据你 ImageView 的大小来缓存相应大小的图片尺寸,比如你 ImageView 大小是200*200,原图是 400*400 ,而使用 Glide 就会缓存 200*200 规格的图,而 Picasso 只会缓存 400*400 规格的。这个改进就会导致 Glide 比 Picasso 加载的速度要快,毕竟少了每次裁剪重新渲染的过程。

除此之外,还有很多其他配置选项的增加。

glide的用法跟picasso类似,不同点:

  1. 默认情况下,picasso加载整个图片到内存然后根据imageview大小调整,glide直接根据imageview大小加载调整后的图片,所以picasso图片质量好,内存消耗大,加载速度相对慢,glide图片质量相对差,内存消耗小,加载速度快
  2. picasso缓存整个图片,只要是同一张图片,都会直接从缓存读取,glide根据imageview大小缓存不同大小图片,即使是同一个图片,显示的imageview大小不同,也会从新缓存
  3. 最重要的一个特性是 Glide 支持加载 Gif 动态图,而 Picasso 不支持该特性

总体来说,Glide 是在 Picasso 基础之上进行的二次开发,各个方面做了不少改进,不过这也导致他的包比 Picasso 大不少,不过也就不到 500k,Picasso 是100多k,方法数也比 Picasso 多不少,不过毕竟级别还是蛮小的,影响不是很大。

四、Fresco

Fresco 是 Facebook 出品,他是新一代的图片加载库,最大的优势在于5.0以下(最低2.3)的bitmap加载。我们知道 Android 应用程序可用的内存有限,经常会因为图片加载导致 OOM,虽然我们有各种手段去优化,尽量减少出现 OOM 的可能性,但是永远没法避免,尤其某些低端手机 OOM 更是严重。而 Facebook 就另辟蹊径,既然没法在 Java 层处理,我们就在更底层的 Native 堆做手脚。于是 Fresco 将图片放到一个特别的内存区域叫 Ashmem 区,就是属于 Native 堆,图片将不再占用 App 的内存,在图片不显示的时候,占用的内存会自动被释放,Java 层对此无能为力,这里是属于 C++ 的地盘,这会使得APP更加流畅,减少因图片内存占用而引发的OOM。为什么说是5.0以下,因为在5.0以后系统默认就是存储在Ashmem区了。

四个库都使用了一遍,对比到Fresco确实强大,加载大图Fresco最屌,有的图Glide和Picasso加载不出来,换上Fresco妥妥的,不过Fresco比较庞大,推荐在主要都是图片的app中使用,一般的app使用Glide和Picasso就够了!Picasso所能实现的功能,Glide都能做,无非是所需的设置不同。但是Picasso体积比起Glide小太多如果项目中网络请求本身用的就是okhttp或者retrofit(本质还是okhttp),那么建议用Picasso,体积会小很多(Square全家桶的干活)。Glide的好处是大型的图片流,比如gif、Video,如果你们是做美拍、爱拍这种视频类应用,建议使用。Fresco在5.0以下的内存优化非常好,代价就是体积也非常的大,按体积算Fresco>Glide>Picasso,不过在使用起来也有些不便(小建议:他只能用内置的一个ImageView来实现这些功能,用起来比较麻烦,我们通常是根据Fresco自己改改,直接使用他的Bitmap层)

参考
1、那些我们用过的Android开源图片加载框架——文/水迹(简书作者)
2、picasso和glide
3、Android 三大图片加载框架比较
4、深入对比Glide 和 Picasso——好文推荐

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章