带你畅游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设定图片需要显示图片的区域大小。

推荐阅读更多精彩内容

  • 一、简介 在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫Glide的图片加载库,作者是bumptech。这...
    天天大保建阅读 3,580评论 2 24
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 124,500评论 16 534
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 70,495评论 12 116
  • Glide的使用 Glide是google开发用于Android加载媒体的类库,包括图片,gif,video,已经...
    敖小强阅读 6,184评论 2 17
  • 2017年1月10日,奥巴马挥手告别了八年之久的总统生涯。与前届总统几分钟的告别演讲不同,他用长达一小时的演讲呼吁...
    Seraphyphy阅读 313评论 2 9