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閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容