Picasso,Glide,Fresco以及UIL使用经验的整理

这么久以来虽然经常用到一些图库,但是自己从来没有真正整理过我们使用过的这些东西有什么不同点,我们为什么要选择这个图库作为项目的加载图片的框架(由于本人资历尚浅,经验也不丰富,所以从没有试过自己做一个图片加载库),今天突发奇想,也算是让自己思路更清晰一点,来写一些关于这些图库的异同之处.
作为一个应用丰富的App,图片加载是必不可少的,甚至对于图片的质量和数量都是要求很高的,然而对于图片的加载是一个耗时操作,在UI线程为了避免ANR异常不能做耗时操作,是一个Android开发人员的基本常识,所以异步加载图片是必然的,这就让我们面临了一个怎么选择图片加载框架的问题?市面上比较成熟的图库有: Universal-Image-Loader(UIL)、Picasso、Glide、Fresco。其实任何一个图片加载框架都可以当做一个普通的下载文件流程,一般都包含这么几个步骤:初始化配置->构造请求->执行请求->处理请求结果。

1.Universal-Image-Loader(UIL)

这是一个很早就出现的图片加载框架了,作者是nostra13,UIL使用很方便,而且自带多种缓存策略,如最大尺寸先删除、时间最久删除 等,使用它,基本上不需要考虑太多的问题。另外,UIL还支持图片下载进度的监听,如果你有特殊需求,则可以在 图片开始下载前、刚开始下载等各个时间段来做一些额外的事情,非常方便。而且UIL可以在View滚动的过程中暂停图片的加载,有利于提升界面的流畅度。但由于作者在两年前宣布不再维护这个项目了,也就是说这个项目将不再更新,所以如果你将开发一个新项目,本人不推荐你使用此框架,因为市面上还有其它跟它一样强大甚至更好的图库可以使用,但如果你现在的项目是一个老牌项目,那也没必要着急更换它,因为它还是很强大的,没有到跟不上时代的地步,到了真需要更换的时候再换也来得及

2.Picasso

这是一个来自于开源界名气很大的Square公司开发的,总体来看,它属于轻量级别的图片加载库,但它也有着一些自己的特色。比如,很特别的拥有统计功能,可以知道使用了多少内存、缓存命中如何,另外它本身没有什么缓存策略,而是依赖所用的网络库的缓存策略——其实就是依赖了OkHttp。Picasso使用起来也是比较简单的,不过对于新项目来说,也不是很推荐,原因就在于,Glide比它更优秀,而且使用起来几乎 是一样的……

缓存路径:

这里讲一下关于Picasso缓存路径:

data/data/your package name/cache/picasso-cache/(默认路径)

有时候根据需求,我们需要更改其缓存路径,这里我们分析一下啊,Picasso 底层其实是使用OkHttp去下载图片,同时在设置Picasso的时候,有一个.downloader(Downloader downloader)方法,我们可以传递进去一个OkHttpDownloader( OkHttpClient client).

Picasso picasso = new Picasso.Builder(Context)
                .downloader(new OkHttpDownloader(client))
                .build();

看到这里我们应该想到,如果给OkHttpClient设置Cache是不是就可以改变缓存路径呢?只需要给OkHttpClient设置.cache(new Cache(file, maxSize))就可以实现修改缓存路径了。代码就是:

File file = new File("缓存文件所在路径");
        if (!file.exists()) {
            file.mkdirs();
        }
        long maxSize = Runtime.getRuntime().maxMemory() / 8;   //设置图片缓存大小为运行时缓存的八分之一
        OkHttpClient client = new OkHttpClient.Builder()
                .cache(new Cache(file, maxSize))
                .build();
        Picasso picasso = new Picasso.Builder(this)
                .downloader(new OkHttpDownloader(client))
                .build();

这里需要注意的就是:当把OkHttp升级到OkHttp3时,给downloader设置OkHttpDownloader()并不支持OkHttp3.如果想使用OkHttp3,需要使用 OkHttp3Downloader来替代OkHttpDownloader,OkHttp3Downloader库是jakewharton专为为了能使用OkHttp3作为下载器而写的,使用姿势也很简单:在Module dependencies添加依赖:

compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'

然后上面的代码改成:

    Picasso picasso = new Picasso.Builder(this)
                .downloader(new OkHttp3Downloader(client))    //注意此处替换为 OkHttp3Downloader
                .build();
关于Picasso实例化对象(两种方式)
  • Picasso.with(context)
    此方法提供默认方式,生成单例的Picasso对象.
  • new Picasso.Builder(context).build()
    此方式提供自定义线程池、缓存、下载器等方法.
关于Picasso源码简单分析
  • Picasso.with(context),在源码中我们很容易就可以看出这是在构造一个单例的对象,而且是通过new Builder(context).build()建造者模式构建.
    1.通过Builder(context)往下看就会发现 this.context = context.getApplicationContext();得到的是全局的上下文,这是为了让Picasso下载器同步应用的生命周期的,然后我们的重点就可以放在build()上了.
    2.在build()方法里面我们会发现有六个方法:
(源代码)
    (第一个方法)  if (downloader == null) {
        downloader = Utils.createDefaultDownloader(context);  //创建一个默认的下载器.
            1.其中Downloader是一个用于从网络上加载图片的接口,需要实现load和shutdown方法。load用于加载图片,shutdown用于关闭一些操作.
            2.Picasso的线程池是经过优化过的,可以根据当前设备网络状况设置其ThreadCount。
在网络良好的条件下,线程池持有较多线程,保证下载速度够快。在网络较差的条件下(2G网络等),线程池减少持有线程,保证带宽不会被多个连接阻塞。
      }

    (第二个方法)  if (cache == null) {
        cache = new LruCache(context);  //初始化缓存,创建内存缓存
      }

     (第三个方法)  if (service == null) {
        service = new PicassoExecutorService();  //初始化线程池
           1.默认启动了3个核心线程,采用了PriorityBlockingQueue优先级阻塞队列,也就是说Picasso支持优先级调度.(对网络状态进行线程的优化)
      }

     (第四个方法)  if (transformer == null) {
        transformer = RequestTransformer.IDENTITY;  // 初始化转换器,请求的前置处理,在请求发出去之前执行,类似于拦截器
          1.默认RequestTransformer.IDENTITY表示不作处理.
      }

     (第五个方法)  Stats stats = new Stats(cache);  //状态控制类,统计一些状态信息,用来发送各种消息,例如查找图片缓存的命中率,下载是否完成等

    (第六个方法)   Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);   //最后创建调度器,用来分发任务
1.从build方法中可以看出,大多数参数直接传进了这个类的构造方法中,可见这个类不容小觑。
       Dispatcher主要是来调度任务的,比如提交任务,取消任务,暂停加载,恢复加载,重试,加载完成,监听网络等等。
       同样,里面也用了一个HandlerThread和Handler来分发任务。通过一系列的dispatchXXX,由Handler发送消息,Handler接收消息后,通过performXXX来调度任务。
  • 关于Picasso中任务调度器Dispatcher的简单分析:
    翻看源码我们会看到其Dispatcher类的构造方法有六个参数,这里主要分析其中两个,一个ExecutorService,另一个就是Handler,
    第一,我们首先讲一下Handler
public DispatcherHandler(Looper looper, Dispatcher dispatcher) {
      super(looper);
      this.dispatcher = dispatcher;
    }

    @Override public void handleMessage(final Message msg) {
      switch (msg.what) {
        case REQUEST_SUBMIT: {      //提交请求
          Action action = (Action) msg.obj;
          dispatcher.performSubmit(action);
          break;
        }
        case REQUEST_CANCEL: {        //取消请求
          Action action = (Action) msg.obj;
          dispatcher.performCancel(action);
          break;
        }
        case TAG_PAUSE: {          //暂停请求
          Object tag = msg.obj;
          dispatcher.performPauseTag(tag);
          break;
        }
        case TAG_RESUME: {      //恢复请求
          Object tag = msg.obj;
          dispatcher.performResumeTag(tag);
          break;
        }
        case HUNTER_COMPLETE: {         //捕获完成
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performComplete(hunter);
          break;
        }
        case HUNTER_RETRY: {          //重试
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performRetry(hunter);
          break;
        }
        case HUNTER_DECODE_FAILED: {         //解码失败
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performError(hunter, false);
          break;
        }
    }
  }

第二,关于ExecutorService

  if (service instanceof PicassoExecutorService) {
      ((PicassoExecutorService) service).adjustThreadCount(info);
    }
//在adjustThreadCount(info)方法里面就是下面这段代码,很明可以看出,这段代码是根据网络状态信息info来决定线程池个数的,默认是3条线程
 if (info == null || !info.isConnectedOrConnecting()) {
      setThreadCount(DEFAULT_THREAD_COUNT);
      return;
    }
    switch (info.getType()) {
      case ConnectivityManager.TYPE_WIFI:   //wife状态下
      case ConnectivityManager.TYPE_WIMAX:  //802·16无线城域网,类似于wife
      case ConnectivityManager.TYPE_ETHERNET:  //以太网数据连接
        setThreadCount(4);
        break;
      case ConnectivityManager.TYPE_MOBILE:
        switch (info.getSubtype()) {
          case TelephonyManager.NETWORK_TYPE_LTE:        // 4G状态下
          case TelephonyManager.NETWORK_TYPE_HSPAP:
          case TelephonyManager.NETWORK_TYPE_EHRPD:
            setThreadCount(3);
            break;
          case TelephonyManager.NETWORK_TYPE_UMTS:       // 3G状态下
          case TelephonyManager.NETWORK_TYPE_CDMA:
          case TelephonyManager.NETWORK_TYPE_EVDO_0:
          case TelephonyManager.NETWORK_TYPE_EVDO_A:
          case TelephonyManager.NETWORK_TYPE_EVDO_B:
            setThreadCount(2);
            break;
          case TelephonyManager.NETWORK_TYPE_GPRS:      // 2G状态下
          case TelephonyManager.NETWORK_TYPE_EDGE:
            setThreadCount(1);
            break;
          default:
            setThreadCount(DEFAULT_THREAD_COUNT);  
        }
        break;
      default:
        setThreadCount(DEFAULT_THREAD_COUNT);    //默认状态下是3条
  • 接下来我们讲一下Picasso中的load("image src url")方法,源码中load()方法有四个
(源代码如下)
//通过uri参数获得RequestCreator对象
public RequestCreator load(Uri uri) {
    return new RequestCreator(this, uri, 0);
  }
//通过请求路径path,获取其uri参数获得RequestCreator对象
 public RequestCreator load(String path) {
    if (path == null) {
      return new RequestCreator(this, null, 0);
    }
    if (path.trim().length() == 0) {
      throw new IllegalArgumentException("Path must not be empty.");
    }
    return load(Uri.parse(path));
  }
//通过文件file获得其uri参数,从而获取RequestCreator对象
public RequestCreator load(File file) {
    if (file == null) {
      return new RequestCreator(this, null, 0);
    }
    return load(Uri.fromFile(file));
  }
//通过指定的请求id来获取RequestCreator对象
 public RequestCreator load(int resourceId) {
    if (resourceId == 0) {
      throw new IllegalArgumentException("Resource ID must not be zero.");
    }
    return new RequestCreator(this, null, resourceId);
  }
(从上面的四个方法可以看出,其实又可以分为两类:uri和resourceId。uri又分为file和net。)

从上面的代码可以看出load()方法最终都是需要得到RequestCreator对象,那RequestCreator又有什么作用呢?
RequestCreator是用来配置加载参数的。RequestCreator有两个功能

  1. 配置加载参数。
    包括placeHolder与error图片,加载图片的大小、旋转、居中等属性。
  2. 执行加载。
    通过调用into(object)方法进行加载。
  • 说完load()方法,接下来得说说into()方法了
    into()可以说是Picasso中比较复杂的方法,方法有五个,方法体也比较长,代码我这里就不贴了,大家可以自行翻看,这个方法在RequestCreator里面
    通过源码分析,其逻辑还是比较清晰的,这里总结一下:
  1. into会检查当前是否是在主线程上执行。
 long started = System.nanoTime();
    checkMain();
  1. 如果我们没有提供一个图片资源并且有设置placeholder,那么就会把我们设置的placeholder显示出来,并中断执行。(下面的代码)
   Drawable drawable =
        placeholderResId != 0 ? picasso.context.getResources().getDrawable(placeholderResId)
            : placeholderDrawable;

    if (!data.hasImage()) {
      picasso.cancelRequest(target);
      target.onPrepareLoad(drawable);
      return;
    }
  1. defered属性我们一般情况下不需要关注,只有当我们调用了RequestCreator的fit方法时defered才为true,但我们几乎不会这样做。
if (deferred) {
      throw new IllegalStateException("Fit cannot be used with get.");
    }
  1. 接下来就是创建了一个Request对象,我们在前面做得一些设置都会被封装到这个Request对象里面。
Request finalData = createRequest(started);
   String key = createKey(finalData, new StringBuilder());
  1. 检查我们要显示的图片是否可以直接在缓存中获取,如果有就直接显示出来好了。
  if (!skipMemoryCache) {
      Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
      if (bitmap != null) {
        picasso.cancelRequest(target);
        target.onBitmapLoaded(bitmap, MEMORY);
        return;
      }
    }
  1. 缓存没命中,那就只能费点事把源图片down下来了。这个过程是异步的,并且通过一个Action来完成请求前后的衔接工作。
 Action action =
        new TargetAction(picasso, target, request, skipMemoryCache, errorResId, errorDrawable,
            requestKey);
    picasso.enqueueAndSubmit(action);    //异步提交请求action

3.Glide

Google官方推荐图库,在许多Android的原生应用中都采用了Glide来加载图片。其实Glide与Picasso的使用姿势是惊人的相似的

 Picasso.with(context).load("image src url").into(ImageView);
 Glide.with(context).load("image src url").into(ImageView);(当然这只是它们常用的)

从某种程度上说,Glide可以看作是Picasso的增强版,所以它有着自己独特的优势,Glide不仅支持常见的jpg和png格式,还能显示gif动画,甚至是视频,或者说它已经不仅仅是一个普通的图片加载库了,而是一个多媒体库。另外一个优势是,Glide在内存方面的表现相当出色,首先它的图片默认格式是RGB565,要比Picasso默认的ARGB8888节省更多内存,而且它缓存的不是原始图片,而是缓存了图片的实际大小——比如加载的图片是 19201080的大小,而在你的App中,显示该图片的ImageView控件大小只有1280720,那么Glide就会很聪明的自动缓存 1280*720大小的图片。

关于Glide的源码简单解析

在Glide中我们经常用的一种姿势就是:

 Glide.with(context).load("image src url").into(ImageView);

那么这里解析也是从这段代码开始

1.关于with(context)方法

在源码中这个方法是静态的,重载方法有五个,

   //第一个方法传入一个上下文,根据上下文的所属生命周期来确定需要获取对象的生命周期
   public static RequestManager with(Context context) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(context);
    }
  //第二个方法传入一个activity
    public static RequestManager with(Activity activity) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(activity);
    }
  //第三个方法传入一个FragmentActivity
   public static RequestManager with(FragmentActivity activity) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(activity);
    }
   //第四个方法传入一个app.Fragment
  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static RequestManager with(android.app.Fragment fragment) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(fragment);
    }
   //第五个方法传入一个V4兼容包下的Fragment
   public static RequestManager with(Fragment fragment) {
        RequestManagerRetriever retriever = RequestManagerRetriever.get();
        return retriever.get(fragment);
    }

这几个方法不长,逻辑也很清晰,都是通过一个RequestManagerRetriever的静态get()方法得到一个RequestManagerRetriever对象,其实这个静态get()方法就是一个单例的具体实现,然后再调用RequestManagerRetriever的实例get()方法,去获取RequestManager对象。这里需要注意的是实例get()方法中传入的参数类型,不同的参数类型对应不同的生命周期,下面代码就是具体的实现
(源代码)

private RequestManager getApplicationManager(Context context) {
        // Either an application context or we're on a background thread.
        if (applicationManager == null) {
            synchronized (this) {
                if (applicationManager == null) {
                    // Normally pause/resume is taken care of by the fragment we add to the fragment or activity.
                    // However, in this case since the manager attached to the application will not receive lifecycle
                    // events, we must force the manager to start resumed using ApplicationLifecycle.
                    applicationManager = new RequestManager(context.getApplicationContext(),
                            new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
                }
            }
        }
        return applicationManager;
    }
    //第一种,传入全局的上下文,根据不同类型的上下文执行不同的方法
    public RequestManager get(Context context) {
        if (context == null) {
            throw new IllegalArgumentException("You cannot start a load on a null Context");
        } else if (Util.isOnMainThread() && !(context instanceof Application)) {
            if (context instanceof FragmentActivity) {
                return get((FragmentActivity) context);
            } else if (context instanceof Activity) {
                return get((Activity) context);
            } else if (context instanceof ContextWrapper) {
                return get(((ContextWrapper) context).getBaseContext());
            }
        }

        return getApplicationManager(context);
    }
    //第二种,传入一个FragmentActivity,根据情况的不同调用的方法也不同,这里需要注意一下if (Util.isOnBackgroundThread())这种情形表示在子线程执行Glide加载图片,最终会执行最上面那个方法
   public RequestManager get(FragmentActivity activity) {
        if (Util.isOnBackgroundThread()) {
            return get(activity.getApplicationContext());
        } else {
            assertNotDestroyed(activity);
            FragmentManager fm = activity.getSupportFragmentManager();  
            return supportFragmentGet(activity, fm);
        }
    }
     //第三种,传入一个Fragment,也是根据情况的不同调用的方法也不同,注意 if (Util.isOnBackgroundThread())这种情形表示在子线程执行Glide加载图片,最终也会执行最上面的方法
    public RequestManager get(Fragment fragment) {
        if (fragment.getActivity() == null) {
            throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
        }
        if (Util.isOnBackgroundThread()) {
            return get(fragment.getActivity().getApplicationContext());
        } else {
            FragmentManager fm = fragment.getChildFragmentManager();
            return supportFragmentGet(fragment.getActivity(), fm);
        }
    }

  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {
        RequestManagerFragment current = getRequestManagerFragment(fm); //获取隐藏的app包下的Fragment
        RequestManager requestManager = current.getRequestManager();
        if (requestManager == null) {
            requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
            current.setRequestManager(requestManager);
        }
        return requestManager;
    }

    RequestManager supportFragmentGet(Context context, FragmentManager fm) {
        SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm); //获取隐藏的V4包下的Fragment
        RequestManager requestManager = current.getRequestManager();
        if (requestManager == null) {
            requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());
            current.setRequestManager(requestManager);
        }
        return requestManager;
    }

其实通过上面的代码看下来,逻辑上还是比较清晰的,RequestManagerRetriever类中看似有很多个get()方法的重载,什么Context参数,Activity参数,Fragment参数等等,实际上只有两种情况而已,即传入Application类型的参数,和传入非Application类型的参数。

  1. 先看传入Application参数的情况。如果在Glide.with()方法中传入的是一个Application对象,那么这里就会调用带有Context参数的get()方法重载,然后会调用getApplicationManager()方法来获取一个RequestManager对象。Application对象的生命周期即应用程序的生命周期,因此Glide并不需要做什么特殊的处理,它自动就是和应用程序的生命周期是同步的,如果应用程序关闭的话,Glide的加载也会同时终止。
  2. 再看传入非Application参数的情况。不管你在Glide.with()方法中传入的是Activity、FragmentActivity、v4包下的Fragment、还是app包下的Fragment,最终的流程都是一样的,那就是会向当前的Activity当中添加一个隐藏的Fragment。具体添加的逻辑是在上述代码都有注释说明,分别对应的app包和v4包下的两种Fragment的情况。那么这里为什么要添加一个隐藏的Fragment呢?因为Glide需要知道加载的生命周期。比如说:如果你在某个Activity上正在加载着一张图片,结果图片还没加载出来,Activity就被用户关掉了,但是如果图片请求还在继续,当请求的数据回来之后没有界面可以进行渲染,这就会造成内存泄漏,所以这种情况下肯定是需要取消Glide的网络的请求的。可是Glide并没有办法知道Activity的生命周期,于是Glide就使用了添加隐藏Fragment的技巧,因为Fragment的生命周期和Activity是同步的,如果Activity被销毁了,Fragment是可以监听到的,这样Glide就可以捕获这个事件并停止网络请求了。这里需要注意的一点就是:如果我们是在非主线程当中使用的Glide,那么不管你是传入的Activity还是Fragment,都会被强制当成Application来处理
    总体来说,第一个with()方法其实就是为了得到一个RequestManager对象而已,然后Glide会根据我们传入with()方法的参数来确定图片加载的生命周期,接下来我们就分析一下load("image src url")这个方法
2.关于load("image src url")方法

通过上面的with(context)方法返回的都是RequestManager对象,那么load()方法肯定在RequestManager这个类里面,我们知道Glide是支持图片URL字符串、图片本地路径等等加载形式的,load重载的方法有很多,我们常用的一般都是load(String string),关于URL字符串加载形式,下面我们分析一下这种情形:
(源代码:)

    /**
     * Returns a request builder to load the given {@link java.lang.String}.
     * signature.
     *
     * @see #fromString()
     * @see #load(Object)
     *
     * @param string A file path, or a uri or url handled by {@link com.bumptech.glide.load.model.UriLoader}.
     */
    public DrawableTypeRequest<String> load(String string) {
        return (DrawableTypeRequest<String>) fromString().load(string);
    }

    / * @see #from(Class)
     * @see #load(String)
     */
    public DrawableTypeRequest<String> fromString() {
        return loadGeneric(String.class);
    }

    private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass){
        ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);
        ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader =
                Glide.buildFileDescriptorModelLoader(modelClass, context);
        if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
            throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for"
                    + " which there is a registered ModelLoader, if you are using a custom model, you must first call"
                    + " Glide#register with a ModelLoaderFactory for your custom model class");
        }
   //传入StreamStringLoader对象,获取DrawableTypeRequest对象
        return optionsApplier.apply(
                new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
                        glide, requestTracker, lifecycle, optionsApplier));
    }

RequestManager类的代码是非常多的,关于load(String string)简化之后比较重要的方法就只剩下上述代码中的这三个方法。

  1. 先来看load()方法,这个方法中的逻辑是非常简单的,只有一行代码,就是先调用了fromString()方法再调用load()方法,然后把传入的图片URL地址传进去。而fromString()方法也极为简单,就是调用了loadGeneric()方法,并且指定参数为String.class.

  2. 执行loadGeneric()方法时,分别调用了Glide.buildStreamModelLoader()方法和Glide.buildFileDescriptorModelLoader()方法来获得ModelLoader对象。ModelLoader对象是用于加载图片的,而我们给load()方法传入不同类型的参数,这里也会得到不同的ModelLoader对象。由于我们刚才传入的参数是String.class,因此最终得到的是StreamStringLoader对象,它是实现了ModelLoader接口的。

  3. 最后可以看到,loadGeneric()方法是要返回一个DrawableTypeRequest对象的,因此在loadGeneric()方法的最后又new了一个DrawableTypeRequest对象,然后把刚才获得的ModelLoader对象(StreamStringLoader对象),还有其他的一些参数传进去。
    这里就可以看到如果得到一个DrawableTypeRequest对象,那这里面肯定是有所作为的(源码如下:)

/**
     * Attempts to always load the resource as a {@link android.graphics.Bitmap}, even if it could actually be animated.
     *
     * @return A new request builder for loading a {@link android.graphics.Bitmap}
     */
    public BitmapTypeRequest<ModelType> asBitmap() {
        return optionsApplier.apply(new BitmapTypeRequest<ModelType>(this, streamModelLoader,
                fileDescriptorModelLoader, optionsApplier));
    }

 public GifTypeRequest<ModelType> asGif() {
        return optionsApplier.apply(new GifTypeRequest<ModelType>(this, streamModelLoader, optionsApplier));
    }

这里就可以看到我们经常使用的 asBitmap() 和 asGif(),这两个方法分别是用于强制指定加载静态图片动态图片,而从源码中可以看出,它们分别又创建了一个BitmapTypeRequest和GifTypeRequest,如果没有进行强制指定的话,那默认就是使用DrawableTypeRequest

上面的fromString()方法会返回一个DrawableTypeRequest对象,接下来会调用这个对象的load()方法,把图片的URL地址传进去。通过对DrawableTypeRequest类的查看,没有找到load()方法,所以在DrawableTypeRequest的父类DrawableRequestBuilder中可以看到load()方法.

 @Override
    public DrawableRequestBuilder<ModelType> load(ModelType model) {
        super.load(model);
        return this;
    }

 /**
     * {@inheritDoc}
     *
     * <p>
     *     Note - If no transformation is set for this load, a default transformation will be applied based on the
     *     value returned from {@link android.widget.ImageView#getScaleType()}. To avoid this default transformation,
     *     use {@link #dontTransform()}.
     * </p>
     *
     * @param view {@inheritDoc}
     * @return {@inheritDoc}
     */
    @Override
    public Target<GlideDrawable> into(ImageView view) {
        return super.into(view);
    }

DrawableRequestBuilder中有很多个方法,这些方法其实就是Glide绝大多数的API了。通过源码,我们可以看到load()和into()这两个方法了,所以我们总结一下:最终load()方法返回的其实就是一个DrawableTypeRequest对象。那么接下来分析into()方法中的逻辑。

3.关于into()方法的分析

从上面的代码我们可以看到DrawableRequestBuilder类里面的into()方法只有

return super.into(view); 

这说明into()真正的实现逻辑是在DrawableRequestBuilder的父类GenericRequestBuilder,所以into()方法需要在这个类进行分析

    /**
     * Sets the {@link ImageView} the resource will be loaded into, cancels any existing loads into the view, and frees
     * any resources Glide may have previously loaded into the view so they may be reused.
     *
     * @see Glide#clear(android.view.View)
     *
     * @param view The view to cancel previous loads for and load the new resource into.
     * @return The {@link com.bumptech.glide.request.target.Target} used to wrap the given {@link ImageView}.
     */
    public Target<TranscodeType> into(ImageView view) {
        Util.assertMainThread();
        if (view == null) {
            throw new IllegalArgumentException("You must pass in a non null View");
        }

        if (!isTransformationSet && view.getScaleType() != null) {
            switch (view.getScaleType()) {
                case CENTER_CROP:
                    applyCenterCrop();
                    break;
                case FIT_CENTER:
                case FIT_START:
                case FIT_END:
                    applyFitCenter();
                    break;
                //$CASES-OMITTED$
                default:
                    // Do nothing.
            }
        }

        return into(glide.buildImageViewTarget(view, transcodeClass));
    }

在上面代码中into(ImageView view)方法里面最后一行代码先是调用glide.buildImageViewTarget()方法,这个方法会构建出一个Target对象,Target对象则是用来最终展示图片用的,如果我们跟进去的话会看到如下代码:

<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
        return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
    }

这里可以看到会通过imageViewTargetFactory调用buildTarget(imageView, transcodedClass)这个方法,如果继续查看源码,会发现在buildTarget()方法中会根据传入的class参数来构建不同的Target对象。这个class参数其实基本上只有两种情况,如果你在使用Glide加载图片的时候调用了asBitmap()方法,那么这里就会构建出BitmapImageViewTarget对象,否则的话构建的都是GlideDrawableImageViewTarget对象。

这里glide.buildImageViewTarget(view, transcodeClass),我们得到一个GlideDrawableImageViewTarget对象,然后回到

    return into(glide.buildImageViewTarget(view, transcodeClass));

我们可以看到into(GlideDrawableImageViewTarget对象)的源码

 /**
     * Set the target the resource will be loaded into.
     *
     * @see Glide#clear(com.bumptech.glide.request.target.Target)
     *
     * @param target The target to load the resource into.
     * @return The given target.
     */
    public <Y extends Target<TranscodeType>> Y into(Y target) {
        Util.assertMainThread();
        if (target == null) {
            throw new IllegalArgumentException("You must pass in a non null Target");
        }
        if (!isModelSet) {
            throw new IllegalArgumentException("You must first set a model (try #load())");
        }

        Request previous = target.getRequest();

        if (previous != null) {
            previous.clear();
            requestTracker.removeRequest(previous);
            previous.recycle();
        }

        Request request = buildRequest(target); //调用buildRequest()方法构建出了一个Request对象
        target.setRequest(request);
        lifecycle.addListener(target);
        requestTracker.runRequest(request);  //执行Request对象

        return target;
    }

上面代码构建出来的Request对象是用来发出加载图片请求的,它是Glide中非常关键的一个组件.这就是我们经常用的with(),load(),into()最表面的意思,里面还有很多很多内容没有写出来,我也是参考郭霖大神的文章以及自己的拙见才写这么点东西的,也算是做了一次笔记吧!

关于Glide优化

  1. 配置使用Volley和OkHttp来加载图片
    Volley和OkHttp是项目中使用最广泛的两个网络库,也是两个相对来说速度比较快的,Glide默认使用的是HttpUrlConnection的方式请求网络,其效率是比较低的,可以使用Volley或者OkHttp(和项目使用的网络请求库一致)作为Glide的网络请求方式.

Gradle配置

//使用volley
dependencies {
    compile 'com.github.bumptech.glide:volley-integration:1.4.0@aar'
    //compile 'com.mcxiaoke.volley:library:1.0.8'
}
//使用okhttp
dependencies {
    compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar'
    //compile 'com.squareup.okhttp:okhttp:2.2.0'
}

当在Library库中使用aar的时候,Library中的GlideModule会自动合并 到主项目中mainfest文件中,当使用jar包导入时,需要手动去合并Library合并GlideModule或者使用自己配置的GlideModule。

Maven配置

//使用volley
<dependency>
    <groupId>com.github.bumptech.glide</groupId>
    <artifactId>volley-integration</artifactId>
    <version>1.4.0</version>
    <type>aar</type>
</dependency>
<dependency>
    <groupId>com.mcxiaoke.volley</groupId>
    <artifactId>library</artifactId>
    <version>1.0.8</version>
    <type>aar</type>
</dependency>

//使用okhttp
<dependency>
    <groupId>com.github.bumptech.glide</groupId>
    <artifactId>okhttp-integration</artifactId>
    <version>1.4.0</version>
    <type>aar</type>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp</groupId>
    <artifactId>okhttp</artifactId>
    <version>2.2.0</version>
    <type>jar</type>
</dependency>

jar的形式引入

如果通过Maven,Ant 或者其它系统工具来构建的话,是不支持manifest 文件合并的,你必须手动在AndroidManifest.xml添加GlideModule metadata 属性。

//使用volley
<meta-data
    android:name="com.bumptech.glide.integration.volley.VolleyGlideModule"
    android:value="GlideModule" />
//使用okhttp
<meta-data
    android:name="com.bumptech.glide.integration.okhttp.OkHttpGlideModule"
    android:value="GlideModule" />

//添加混淆配置
-keep class com.bumptech.glide.integration.volley.VolleyGlideModule
-keep class com.bumptech.glide.integration.okhttp.OkHttpGlideModule

其实如果我们想使用OkHttp3作为Glide网络请求方式,可以自行查看相关文档.

4.Fresco

这个号称是Android平台上目前最为强大的图片加载库,由Facebook公司开发。与Glide一样,Fresco也是支持gif动画显示,而且在内存方面的表现更加优秀。由于将图片放在Ashmem(匿名共享内存)中,大大降低了App的内存占用(因为Ashmem没有被统计到App的内存使用里),再加上各种级别优化,使得Fresco基本上告别了OOM,而且Fresco的图片直接显示为ARGB8888这种最高质量级别,即使是在这种高质量的情况下依然保证了比其他库更少的内存占用,这就是Fresco最吸引人的地方。而且类似于进度监听、缓存策略等,也是非常完善的,总之作为一个图片加载库,Fresco在功能和性能方面已经趋于完美了。

Picasso,Glide,Fresco的区别

  • Picasso :和Square的网络库一起能发挥最大作用,因为Picasso可以选择将网络请求的缓存部分交给了okhttp实现。使用4.0+系统上的HTTP缓存来代替磁盘缓存.
    Picasso 底层是使用OkHttp去下载图片,所以Picasso底层网络协议为Http.

  • Glide:模仿了Picasso的API,而且在它的基础上加了很多的扩展(比如gif等支持),Glide默认的Bitmap格式是RGB_565,比Picasso默认的ARGB_8888格式的内存开销要小一半;Picasso缓存的是全尺寸的(只缓存一种),而Glide缓存的是跟ImageView尺寸相同的(即5656和128128是两个缓存) 。
    Glide 底层默认使用的是HttpUrlConnection的方式请求网络,所以Glide的底层网络协议也为Http.

  • Fresco:最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区,这个区域没有被统计到App的内存使用里)。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减少因图片内存占用而引发的OOM。为什么说是5.0以下,因为在5.0以后系统默认就是存储在Ashmem区了。
    Image pipeline 默认使用HttpURLConnection。应用可以根据自己需求使用不同的网络库。Fresco的Image Pipeline负责图片的获取和管理。图片可以来自远程服务器,本地文件,或者Content Provider,本地资源。压缩后的文件缓存在本地存储中,Bitmap数据缓存在内存中.

    功能性 Fresco > Glide > Picasso
    包大小 Fresco > Glide > Picasso

主要功能:

共有的功能:根据content生命周期进行图片加载或暂停和恢复,缓存图片到本地。
加载图片格式及大小:

  • Picasso:下载全尺寸图片,load全尺寸图片到imageview上,图片使用ARGB-8888格式。
  • Glide:包含Picasso功能,默认下载不同图片至本地,load 对应imageview尺寸的图片,图片使用ARGB-565格式。
    可加载gif、缩略图、视频静态图片、转换字节数组、显示动画。
  • Fresco:结合Picasso、Glide优点,更适用于加载大量图片。另支持渐进式显示图片、WebP格式图片。

对图片转换

  1. Picasso: picasso-transformations,:结合picasso,支持将图片转换为其他形状后显示。
  2. Glide: glide-transformations:结合glide,支持将图片转换为其他形状后显示。
  3. Fresco: android-gpuimage:支持将图片变幻为各种滤镜效果。

总结:

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

该如何选择图片加载库?

如果你手中的项目比较老旧,而且代码量较大,你又没什么时间去大改,那么继续维持当前的选择是比较稳妥的办法。如果是新上马的项目,那么UIL由于不再维护、Picasso基本被Glide全方位超越,我推荐使用Glide或Fresco。如果你的App里,图片特别多,而且都是很大、质量很高的图 片,而且你不太在乎App的体积(可能性不大),那么Fresco就是很好的选择了,而Glide相比较Fresco,Glide要轻量一些,而且是Google官方推荐,所以在多数时候,会是开发者的首选。话说回来,如果你对这些图库都不满意,那可以自己写一个,如果可以的话!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容