帶你暢游Glide

glide.jpg

目前市場上的圖片異步請求框架有很多,比如Volley,Fresco,Glide,Picasso等,之前項目中用的一直都似乎Volley的ImageLoader,但是使用起來并不方便,再加上最近面試都會問到圖片異步加載框架,Glide,Picasso等,所以決定學習一下新的圖片異步加載框架,這邊我選擇Glide,首先是因為Glide使用起來真的很方便,一行代碼就能解決(當然,Picasso使用起來跟Glide幾乎沒什么差異,也很好用,這邊推薦一篇博客,主要講解Glide,Picasso的差異,然后就是Glide是Google官方推薦的,運用在很多Google的開源項目中,沒理由不學了。

一、Glide基本使用

  1. 基本配置

    我這邊使用的是3.7.0的版本,同時可以下載Glide的源碼,生成對應的jar進行導入,這兩種方案都是可行的。

    compile 'com.github.bumptech.glide:glide:3.7.0'
    
  2. 基本使用

    Glide的使用是一樣簡單的,通常一行代碼就可以解決我們日常的開發需求,Glide使用的也是目前非常流行的鏈式調用,使用起來特別舒暢,再想想Volley的ImageLoader,感覺整個世界又美好了好多,哈哈,廢話多了。Glide有個牛逼的特性就是他能根據Activity,Fragment的生命周期來判斷是請求加載圖片還是取消或暫停。這個特性根據with中傳入的值而定。

    Glide.with(Context/Activity/Fragment).load(url/resId/...).into(ImageView);
    
  3. 基本拓展

    基礎上述的基本使用外,Glide理所當然也能做一下個性化的配置,例如設置占位符,設置錯誤圖等等,這些都是基本的。所有的設置只需要添加到load與into中間就行。當然,可以配置的選項不僅僅這些,可以自己去探索看看。

    Glide.with(context)
     .load("url")
     .placeholder(R.mipmap.placeholder)  //設置占位符
     .error(R.mipmap.error)              //設置圖片加載失敗后的提示
     .override(200, 200)                 //設置需要加載圖片的大小
     .crossFade()                        //圖片顯示動畫,逐漸顯示
     .diskCacheStrategy(DiskCacheStrategy.NONE)  //緩存策略,DiskCacheStrategy.NONE為不緩存
     .into(mImageView);
    
  4. 硬盤緩存策略

    Glide的硬盤緩存通過diskCacheStrategy方法設定,它有四種硬盤緩存方案,主要是基于原圖與當前使用的尺寸的圖片進行區分,Glide加載圖片的適合會根據我們override傳入的數據加載對應大小的圖片,再特定情況下將該尺寸圖片緩存在硬盤,如果沒有設置override,他會根據ImageView的大小來計算需要加載多大的圖片:

    1. DiskCacheStrategy.ALL 緩存所有,包括原始圖,以及當前緩存尺寸的圖。
    2. DiskCacheStrategy.NONE 不做任何緩存,每次都從服務器下載。
    3. DiskCacheStrategy.SOURCE 只緩存原始圖。
    4. DiskCacheStrategy.RESULT 默認選項,只緩存當前需要顯示的尺寸的圖,不緩存原始圖。
  5. 內存緩存策略

    Glide的內存緩存是默認開啟的,它內部是使用一個LruCache算法與弱引用機制相結合的方式實現內存緩存的,

    private final MemoryCache cache;
    private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
    

activeResources緩存的是正在被使用的圖片,cache中緩存的是網絡請求后緩存下來的圖片,如果從緩存中取數據,先從cache中取,取到圖片的畫,將該圖片從cache中刪除,再放入activeResources中,然后返回該圖片,如果沒取到,就從activeResources中取,取到就返回,取不到就從硬盤,或者網絡獲取,通過cache,activeResources結合的方式進行內存緩存,有效的避免了正在使用的圖片被LruCache算法回收的問題。

雖然Glide默認開啟了內存緩存,當然我們也有方法關閉內存緩存,通過如下方法可以實現:

Glide.with(context)
  .load(url)
  .skipMemoryCache(true)     //關閉內存緩存
  .into(mImageView);

?

二、圖片預加載-preload

? 再某些特定的情境下,我們肯定會遇到圖片提前下載到本地,然后需要使用的適合可以很快加載出來,這個適合就要使用到Glide的預加載了。對于這個功能,我們可以假想一下,對于目前的認知,我們會怎么去實現?下面這行代碼能都實現?純粹一個Glide圖片加載請求,不給他顯示的ImageView,當然這么些肯定是會有問題的,哈哈。

Glide.with(Context/Activity/Fragment).load(url/resId/...).into((ImageView)null);

? 帶著這個疑問,我們來看一下Glide給我們提供的preload()方法。這個方法就是提供給我們預加載圖片使用的,發起一個圖片加載的請求,這個請求結果會進行硬盤,內存緩存等操作,但是不對結果做任何操作。下面我們來看看它的源碼。

在看源碼前,我們需要了解一個事情,into方法中并不是只能傳遞一個ImageView,into()方法真正應該傳入的是一個Target對象,into(ImageView)內部其實就是將ImageView封裝到了一個XXXImageViewTarget,而Target是一個接口,定義了圖片加載的相關方法。

public interface Target<R> extends LifecycleListener {
    ...
    //圖片加載啟動前回調
    void onLoadStarted(Drawable placeholder);
    //圖片加載失敗回調
    void onLoadFailed(Exception e, Drawable errorDrawable);
    //圖片加載成功回調
    void onResourceReady(R resource, GlideAnimation<? super R> glideAnimation);
    ...
}

了解了這一知識點后,我們就來看一下preload的源碼

public Target<TranscodeType> preload() {
  return preload(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
}

public Target<TranscodeType> preload(int width, int height) {
  final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(width, height);
  return into(target);
}

不難發現,preload其實就是創建了一個PreloadTarget,然后作為參數傳遞到into方法中,下面再追蹤一下PreoadTarget的源碼,重點看一下onResourceReady方法的重寫。

public final class PreloadTarget<Z> extends SimpleTarget<Z> {

    /**
     * Returns a PreloadTarget.
     *
     * @param width The width in pixels of the desired resource.
     * @param height The height in pixels of the desired resource.
     * @param <Z> The type of the desired resource.
     */
    public static <Z> PreloadTarget<Z> obtain(int width, int height) {
        return new PreloadTarget<Z>(width, height);
    }

    private PreloadTarget(int width, int height) {
        super(width, height);
    }

    @Override
    public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {
        Glide.clear(this);
    }
}

PreloadTarget的源碼其實異常簡單,只有簡單幾行,onResourceReady方法中僅僅只是調用了Glide.clear(),該方法也僅僅是對一下資源的回收罷了,因此,Glide提供的preload方法與我們預想的差不多,就是在Glide請求成功后不做任何操作。

三、圖片下載-downloadOnly

有了上面的知識做鋪墊,我們不難發現,Glide能做的不僅僅是將圖片顯示到ImageView等控件中,我們可以在拿到圖片資源后,做我們想做的一切,使用很簡單,只需要實現Target就行,同時Glide也實現了一些Target供我們使用,比如SimpleTarget,顧名思義,就是使用起來很簡單的target,只需要實現onResourceReady方法就行,比如,我們可以以這種方式實現圖片下載功能。

Glide.with(this)
.load("http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg")
.into(new SimpleTarget<GlideDrawable>() {
  @Override
  public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
  //resource及是下載的圖片資源,我們只需要將圖片資源寫入文件,即可實現文件的下載
  }
});

當然,Glide這么強大的庫也給我們提供了下載文件的簡單使用,一共兩個,一個需要在子線程中執行,一個在主線程中執行。

  • downloadOnly(width, height)

    該方法需要在子線程中執行,因為內部會有一個阻塞線程的耗時操作。該方法會返回一個Target,下載的圖片信息就可以通過返回的Target獲取到。Target.SIZE_ORIGINAL是告訴Glide下載圖片原始尺寸。

    new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
                    final Context context = getApplicationContext();
                    FutureTarget<File> target = Glide.with(context)
                                                     .load(url)
                                                     .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
                    final File imageFile = target.get();
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(context, imageFile.getPath(), Toast.LENGTH_LONG).show();
                        }
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    
  • downloadOnly(Target)

    該方法與preload類似,只是這邊需要我們自己去實現這個Target

    Glide.with(this)
      .load("http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg")
      .downloadOnly(new Target<File>() {
        ...
        @Override
        public void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) {
          Log.e(TAG, "圖片存儲路徑 :" + resource.getAbsolutePath());
        }
      ...
      });
    

四、Glide中的Transform

transform,顧名思義,就是對圖片進行變換用的,我們經常會在使用Glide的過程中使用centerCrop()和fitCenter()方法,Glide會根據目標ImageView的scaleType,來決定是調用centerCrop()還是fitCenter(),由于ImageView默認的ScaleType是FIT_CENTER,所以Glide默認會為Glide添加fitCenter的轉換,當然,如果我們不需要transform的時候,我們可以調用dontTransform()來取消轉換。

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.
        }
    }

下面我們來看看dontTransform, centerCrop, fitCenter三者的具體效果,(圖片為一張200x200的網絡圖片)

transform對比.png

dontTransform():以ScaleType=FIT_CENTER的模式顯示圖片

fitCenter():拉伸圖片,橫向平鋪,截取橫向中間段,參考Glide源碼中的com.bumptech.glide.load.resource.bitmap.FitCenter

centerCrop():拉伸圖片,縱向平鋪,截取縱向中間段,參考com.bumptech.glide.load.resource.bitmap.CenterCrop

五、Transform實現圓角圖片

有了上面關于transform的知識,我們知道我們可以在transform中對圖形做任何我們想做的操作,舉例一個非常常見的需求,就是圓角圖片或者原型頭像,以及帶邊框等等,我們完全可以使用transform來實現,下面直接貼出源碼,可以直接使用。

public class RoundTransform extends BitmapTransformation{

    private static final int DEFAULT_BORDER_COLOR = Color.WHITE;
    private static final int DEFAULT_BORDER_WIDTH = 10;
    private static final int DEFAULT_BORDER_RADIUS = 0;

    //類型:圓角
    public static final int TYPE_ROUND = 0;
    //類型:圓形
    public static final int TYPE_CIRCLE = 1;

    private int mType = TYPE_ROUND;
    private int mBorderColor = DEFAULT_BORDER_COLOR;
    private int mBorderWidth = DEFAULT_BORDER_WIDTH;
    private int mBorderRadius = DEFAULT_BORDER_RADIUS;

    //繪制邊線的畫筆
    private Paint mBorderPaint;

    public RoundTransform(Context context) {
        super(context);
        init();
    }

    public RoundTransform(Context context, int type, int borderWidth, int borderColor){
        super(context);
        this.mType = type;
        this.mBorderWidth = borderWidth;
        this.mBorderColor = borderColor;
        init();
    }

    public RoundTransform(Context context, int type, int borderWidth, int borderColor, int borderRadius){
        super(context);
        this.mType = type;
        this.mBorderWidth = borderWidth;
        this.mBorderColor = borderColor;
        this.mBorderRadius = borderRadius;
        init();
    }

    private void init(){
        if(mBorderWidth > 0) {
            mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mBorderPaint.setDither(true);
            mBorderPaint.setColor(mBorderColor);
            mBorderPaint.setStyle(Paint.Style.STROKE);
            mBorderPaint.setStrokeWidth(mBorderWidth);
        }
    }

    @Override
    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        switch (mType){
            case TYPE_CIRCLE:
                return transform4Circle(pool, toTransform);
            case TYPE_ROUND:
                return transform4Round(pool, toTransform);
        }
        return null;
    }

    /**
     * 圓形變換
     * @param pool
     * @param toTransform
     * @return
     */
    private Bitmap transform4Circle(BitmapPool pool, Bitmap toTransform) {
        if(toTransform == null){
            return null;
        }
        int squareWidth = Math.min(toTransform.getWidth(), toTransform.getHeight());
        int startX = (toTransform.getWidth() - squareWidth) / 2;
        int startY = (toTransform.getHeight() - squareWidth) / 2;
        //截取中間方塊
        Bitmap squareBitmap = Bitmap.createBitmap(toTransform, startX, startY, squareWidth, squareWidth);
        Bitmap result = pool.get(squareWidth, squareWidth, Bitmap.Config.ARGB_8888);
        if(result == null){
            result = Bitmap.createBitmap(squareWidth, squareWidth, Bitmap.Config.ARGB_8888);
        }
        Canvas canvas = new Canvas(result);
        Paint shaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        shaderPaint.setDither(true);
        shaderPaint.setShader(new BitmapShader(squareBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
        canvas.drawCircle(squareWidth / 2, squareWidth / 2, squareWidth / 2, shaderPaint);
        //繪制邊框
        if(mBorderPaint != null){
            canvas.drawCircle(squareWidth / 2, squareWidth / 2, squareWidth / 2 - mBorderWidth / 2, mBorderPaint);
        }
        if(result != null){
            pool.put(result);
        }
        squareBitmap.recycle();
        return result;
    }

    /**
     * 圓角變換
     * @param pool
     * @param toTransform
     * @return
     */
    private Bitmap transform4Round(BitmapPool pool, Bitmap toTransform) {
        if(toTransform == null){
            return null;
        }
        // border 要居中即要壓在圖片上
        final int border = (int) (mBorderWidth / 2);
        final int width = (int) (toTransform.getWidth() - mBorderWidth);
        final int height = (int) (toTransform.getHeight() - mBorderWidth);

        Bitmap result = pool.get(width, height, Bitmap.Config.ARGB_8888);
        if (result == null) {
            result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        }
        Canvas canvas = new Canvas(result);
        Paint paint = new Paint();
        paint.setShader(new BitmapShader(toTransform, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
        paint.setAntiAlias(true);
        RectF rectF = new RectF(border, border, width - border, height - border);
        canvas.drawRoundRect(rectF, mBorderRadius, mBorderRadius, paint);

        if (mBorderPaint != null) {
            canvas.drawRoundRect(rectF, mBorderRadius, mBorderRadius, mBorderPaint);
        }
        if(result != null){
            pool.put(result);
        }
        return result;
    }

    @Override
    public String getId() {
        return getClass().getName();
    }
}

如果需要更多的變換需求,可以參考一下https://github.com/wasabeef/glide-transformations,他幫我們實現了很多轉換,比如高斯模糊之類的。導入進來直接用即可,很方便。

六、自定義模塊

Glide使用如此簡單,只需要一行代碼,那我們肯定會問,難道它沒用提供給我們配置設置的方法嘛?我想設置圖片解碼格式(默認使用RGB_565),我想設置緩存方案,我想設置緩存路徑,我想...等下,難道Glide這么牛逼的框架沒想到這些嘛?怎么可能,他能做的遠比我們想的多。

Glide提供了自定義模塊來提供我們設置配置的接口,我們需要實現GlideModule

public class MyGlideModule implements GlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {}

    @Override
    public void registerComponents(Context context, Glide glide) {}
}

然后在AndroidManifest.xml中配置

<application>
  ...
  <meta-data
            android:name="com.yunzhou.libcommon.glide.CustomGlideModule"
            android:value="GlideModule" />
  ...
</application>

這邊需要注意的一點是,自定義模塊并不是只能有一個,可以多個,我們可以看下源碼,他是通過反射機制實現的,獲取的是value=‘GlideModule’的一個List<GlideModule>

Context applicationContext = context.getApplicationContext();
List<GlideModule> modules = new ManifestParser(applicationContext).parse();


public List<GlideModule> parse() {
        List<GlideModule> modules = new ArrayList<GlideModule>();
        try {
            ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
                    context.getPackageName(), PackageManager.GET_META_DATA);
            if (appInfo.metaData != null) {
                for (String key : appInfo.metaData.keySet()) {
                    if (GLIDE_MODULE_VALUE.equals(appInfo.metaData.get(key))) {
                        modules.add(parseModule(key));
                    }
                }
            }
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException("Unable to find metadata to parse GlideModules", e);
        }

        return modules;
    }

下面說一下如何使用GlideModule實現自定義的配置,主要還是兩個方法applyOptions和registerComponents

  • applyOptions

    applyOptions主要做的是修改初始化配置,GlideBuilder提供了6項配置的修改

    • setDiskCache

      配置硬盤緩存策略,默認使用InternalCacheDiskCacheFactory

    • setDecodeFormat

      配置圖片加載解碼模式,默認為RGB_565

    • setBitmapPool

      配置圖片緩存池,默認為LruBitmapPool

    • setDiskCacheService

      配置讀取硬盤緩存的異步執行器,默認是FifoPriorityThreadPoolExecutor

    • setMemoryCache

      配置內存緩存策略,默認是LruResourceCache

    • setResizeService

      配置讀取非緩存圖片的異步執行器,默認是FifoPriorityThreadPoolExecutor

      ?

      這里面我們講一下硬盤緩存策略,它默認使用的是InternalCacheDiskCacheFactory,閱讀源碼可以發現它是存儲在內部存儲區域的getCacheDir() + "/image_manager_disk_cache", 代碼很簡單,僅僅兩個構造函數而已,能夠供我們配置的也就是緩存的目錄的文件名,以及緩存大小,并不能自定義緩存路徑;

      public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory {
      
          public InternalCacheDiskCacheFactory(Context context) {
              this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE);
          }
      
          public InternalCacheDiskCacheFactory(Context context, int diskCacheSize) {
              this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, diskCacheSize);
          }
      
          public InternalCacheDiskCacheFactory(final Context context, final String diskCacheName, int diskCacheSize) {
              super(new CacheDirectoryGetter() {
                  @Override
                  public File getCacheDirectory() {
                      File cacheDirectory = context.getCacheDir();
                      if (cacheDirectory == null) {
                          return null;
                      }
                      if (diskCacheName != null) {
                          return new File(cacheDirectory, diskCacheName);
                      }
                      return cacheDirectory;
                  }
              }, diskCacheSize);
          }
      }
      

      如果我們想使用外部緩存怎么辦?

      Glide為我們提供了InternalCacheDiskCacheFactory來為我們實現內部緩存,那它同時也通過了ExternalCacheDiskCacheFactory來實現外部緩存,我們通過如下代碼就可以實現使用外部存儲區域

      builder.setDiskCache(new ExternalCacheDiskCacheFactory(context));
      

      但是,它與內部緩存一樣,我們并不能夠修改外部存儲路徑。

      如何修改存儲路徑?

      Glide并沒有提供我們直接修改存儲邏輯的方法,但這也不帶邊不能修改,這邊提供兩種方案

      1.修改InternalCacheDiskCacheFactory/ExternalCacheDiskCacheFactory源碼,設置成我們需要的路徑

      2.新建類繼承DiskLruCacheFactory,讓這個新建的類提供自定義存儲路徑的接口,具體的核心邏輯可參考InternalCacheDiskCacheFactory/ExternalCacheDiskCacheFactory的實現,聰明的你一定可以解決的!

  • registerComponents

    registerComponents中主要是替換Glide中的一些組件,比如大家都知道的,Glide可以修改Http網絡請求的引擎,Glide默認使用的是HttpUrlConnection,我們可以修改它使用Volley,或者當下比較火的OkHttp等等。這邊我們就講一下使用OkHttp進行網絡請求的實現,使用Volley也是一樣的,比較簡單,關鍵還是Glide封裝的比較牛逼。

    修改網絡請求有兩種方式,第一種是使用Glide為我們實現好的模塊,我們導入就行,比如OkHttp

    dependencies {
        compile 'com.github.bumptech.glide:glide:3.7.0'
        compile 'com.squareup.okhttp3:okhttp:3.9.0'
        compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0@aar'
    }
    

    第二種方法,就需要手動擼代碼了,主要是重新實現ModelLoader和DataFetcher兩個接口,其中DataFetcher的實現是真正進行網絡請求的,下面看下源碼,不難理解

    public class OKHttpFetcher implements DataFetcher<InputStream> {
    
        private final OkHttpClient mOkHttpClient;
        private final GlideUrl mUrl;
        private InputStream mStream;
        private ResponseBody mResponseBody;
        private volatile boolean isCancelled;
    
        public OKHttpFetcher(OkHttpClient client, GlideUrl url){
            this.mOkHttpClient = client;
            this.mUrl = url;
        }
    
        @Override
        public InputStream loadData(Priority priority) throws Exception {
            Request.Builder requestBuilder = new Request.Builder()
                    .url(mUrl.toStringUrl());
            for(Map.Entry<String, String> headerEntry : mUrl.getHeaders().entrySet()){
                String key = headerEntry.getKey();
                requestBuilder.addHeader(key, headerEntry.getValue());
            }
            requestBuilder.addHeader("httpEngine", "OkHttp");
            Request request = requestBuilder.build();
            if(isCancelled){
                return null;
            }
            Response response = mOkHttpClient.newCall(request).execute();
            mResponseBody = response.body();
            if(!response.isSuccessful() || mResponseBody == null){
                throw new IOException("Request failed with code: " + response.code());
            }
            mStream = ContentLengthInputStream.obtain(mResponseBody.byteStream(),
                    mResponseBody.contentLength());
            return mStream;
        }
    
        @Override
        public void cleanup() {
            try {
                if(mStream != null){
                    mStream.close();
                }
                if(mResponseBody != null){
                    mResponseBody.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    
        @Override
        public String getId() {
            return mUrl.getCacheKey();
        }
    
        @Override
        public void cancel() {
            isCancelled = true;
        }
    }
    
    public class OKHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
        private OkHttpClient okHttpClient;
    
        public OKHttpGlideUrlLoader(OkHttpClient client){
            this.okHttpClient = client;
        }
    
        @Override
        public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
            return new OKHttpFetcher(okHttpClient, model);
        }
    
        public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream>{
    
            private OkHttpClient client;
    
            public Factory(){}
    
            private synchronized OkHttpClient getOkHttpClient(){
                if(client == null){
                    client = new OkHttpClient();
                }
                return client;
            }
    
            @Override
            public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
                return new OKHttpGlideUrlLoader(getOkHttpClient());
            }
    
            @Override
            public void teardown() {
    
            }
        }
    }
    

    準備工作就緒后,在registerComponents中實現注冊就可以了

    //設置網絡引擎為OKHttp
    glide.register(GlideUrl.class, InputStream.class, new OKHttpGlideUrlLoader.Factory());
    

七、使用Glide時遇到的問題

1.使用七牛云上的圖片,可能出于安全考慮,圖片路徑后面需要添加token,導致每次都會觸發網絡請求

這個問題的原因跟Glide的緩存key有關,Glide調用load的時候會將傳入的url封裝成一個GlideUrl對象,GlideUrl中的getCacheKey()方法返回的內容是緩存key的組成部分,因此圖片路徑后面添加token,同一張圖片每一次請求token都不一樣,會導致key每次都不一樣,所以Glide每次都會去下載,因為它在緩存中找不到對應key的內容。

?解決方案就是重寫GlideUrl的getCacheKey(),在這個方法中將圖片路徑后面的token過濾掉,然后調用load方法的時候傳入的不再是單獨的字符串,而是我們重寫的GlideUrl對象。

2.在RecycleView的Item中加載圖片,ImageView大小設置的wrap_content,來回滑動過程中會出現同一張圖片忽大忽 小的問題。

這個問題出現的原因有兩個,一個是RecycleView的View重用機制,另一個是Glide加載圖片時根據ImageView的大小加載適當的圖片。這兩者共同作用導致了一種情況的發生。說真的我也沒有特別好的解決方案,本身RecycleView中每一項圖片大小都不能確定,來去很大,這種需求就比較雞肋了,可以找交互懟一波,如果真的要實現的話就是我們加載圖片的時候 每次都計算圖片大小,然后用override設定圖片需要顯示圖片的區域大小。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容

  • 一、簡介 在泰國舉行的谷歌開發者論壇上,谷歌為我們介紹了一個名叫Glide的圖片加載庫,作者是bumptech。這...
    天天大保建閱讀 7,538評論 2 28
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,809評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,836評論 18 139
  • Glide的使用 Glide是google開發用于Android加載媒體的類庫,包括圖片,gif,video,已經...
    敖大膽閱讀 7,819評論 2 17
  • 2017年1月10日,奧巴馬揮手告別了八年之久的總統生涯。與前屆總統幾分鐘的告別演講不同,他用長達一小時的演講呼吁...
    Seraphyphy閱讀 551評論 2 10