转载请注明出处:http://blog.csdn.net/li0978/article/details/53415118

前言

前段时间写过一篇图片缓存框架Picasso的用法,对于Picasso有些同学也比较熟悉,采用Lru最近最少缓存策略,并且自带内存和硬盘缓存机制,在图片加载尤其是多图加载着实为大伙省了不少力,在此之前同样也相识有Afinal、Xutil、UniversalImageLoader等优秀的开源框架,今天再总结一个图片加载缓存框架 — Glide,以助自己后边的项目构建舔砖加瓦吧。

Glide简介

Glide是一个快速高效的开源媒体和图片加载框架,他把媒体解码、内存和磁盘二级缓存还有一些资源缓存池封装成一个个简单的接口,使用很方便,并且Glide也是google推荐使用的图片加载框架。Glide支持下载、解码、展示视频快照和图片资源以及GIF动画,Glide支持插件扩展并使用于任何网络网络引擎,默认情况下采用的是HttpUrlconnection网络加载形式,当然也可以采用Google的volley框架和Square的OkHttp来取代。
Glide官方说明:https://github.com/bumptech/glide

Glide特点

  • 使用简单
  • 可配置度高,自适应程度高
  • 支持常见图片格式 Jpg png gif webp
  • 支持多种数据源 网络、本地、资源、Assets 等
  • 高效缓存策略 支持Memory和Disk图片缓存 默认Bitmap格式采用RGB_565内存使用至少减少一半
  • 生命周期集成 根据Activity/Fragment生命周期自动管理请求
  • 高效处理Bitmap 使用Bitmap Pool使Bitmap复用,主动调用recycle回收需要回收的Bitmap,减小系统回收压力

Glide和Picasso对比

Glide和Picasso在使用上非常相似,之前也总结过Picasso,发现在某些地方甚至可以完全模仿Picasso写Glide,不过二者在核心上还是有一定区别的:

  1. Picasso接收的上下文是Context,而Glide传入的上下文可以有Context、Activity、Fragment。Activity和Fragment有生命周期,因此在某个生命周期阶段图片加载也响应收到控制,更灵活。另外在某些情况下也避免了对象未进行引用而造成的内存泄漏问题。
  2. Glide默认的图片格式RGB565而Picasso支持的图片格式ARGB8888,尽管前者没有后者图像更清晰(相差不大),但是在内存开销上却比前者少了一半,加载更快
  3. Glide默认对图片缓存仅仅是展示控件的大小,如果在另外一个不同大小控件上加载相同的图片需要再次下载。Picasso缓存的图片默认是原图,可对原图进行随处展示。
  4. Glide支持媒体解码,支持GIF动画加载,Picasso不能。
    Glide和Picasso区别不止以上这些,以上只是些典型的区别。对于Glide的使用,下边一一道来。

Glide的基础用法

1.glide项目引用
对于Glide这么强大的开源框架,又是google推荐的早已加入到jcenter()仓库中了,所以我们使用的时候只需要在gradle中引用一下仓库的包即可:

compile 'com.github.bumptech.glide:glide:3.7.0'

2.绑定生命周期,让Glide加载图片过程根据生命周期管理。
上边也提到Glide可以根据多种形式绑定上下文,尤其是针对Activity的引用,可用于在生命周期内对图片加载进行控制。

 Glide.with(Context context);// 绑定Context
 Glide.with(Activity activity);// 绑定Activity
 Glide.with(FragmentActivity activity);// 绑定FragmentActivity
 Glide.with(Fragment fragment);// 绑定Fragment

3.简单加载(这里也可以加载本地和asset,可参考Picasso的简单用法)。

Glide.with(this).load(imageUrl).into(imageView);

4.设置加载前和加载失败时的图片

Glide.with(this).load(imageUrl).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).into(imageView);

5.设置下载优先级

Glide.with(this).load(imageUrl).priority(Priority.NORMAL).into(imageView);

6.设置内存缓存(是否进行内存缓存)

Glide.with(this).load(imageUrl).skipMemoryCache(true).into(imageView);

7.设置磁盘缓存

Glide.with(this).load(imageUrl).diskCacheStrategy(DiskCacheStrategy.ALL).into(imageView);

磁盘缓存策略说明:

  • ALL:缓存源资源和转换后的资源
  • NONE:不作任何磁盘缓存
  • SOURCE:缓存源资源
  • RESULT:缓存转换后的资源

8.设置加载动画
api中默认也存在动画,这里可以自己设置,并且也支持属性动画

Glide.with(this).load(imageUrl).animate(R.anim.item_alpha_in).into(imageView);

设置淡入动画

Glide.with(this).load(imageUrl)
.crossFade(1000)        //设置淡入动画,并且淡入过度时间为1秒
.override(80,80)         //最终呈现的像素值80*80
.into(imageView3);

9.设置缩略图

Glide.with(this).load(imageUrl).thumbnail(0.1f).into(imageView); //显示的图片大小为原图的1/10

10.设置加载尺寸(像素)

Glide.with(this).load(imageUrl).override(800, 800).into(imageView);

11.设置图片适配和转换
Api提供了centerCrop()、fitCenter()两种适配方式,前者效果是将图片按照最小边充满,最大边裁剪适配,后者效果是将图片按照最大边充满最小边居中空缺适配。

Glide.with(this).load(imageUrl).centerCrop().into(imageView);

也可以自定义Transformation来设置自己的形状,如设置圆角图形,圆角半径单位是dp:

public class GlideRoundTransform extends BitmapTransformation {
        private float radius = 0f;
        public GlideRoundTransform(Context context) {
            this(context, 4);
        }

        public GlideRoundTransform(Context context, int dp) {
            super(context);
            this.radius = Resources.getSystem().getDisplayMetrics().density * dp;
        }

        @Override
        protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
            return roundCrop(pool, toTransform);
        }

        private Bitmap roundCrop(BitmapPool pool, Bitmap source) {
            if (source == null) return null;

            Bitmap result = pool.get(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
            if (result == null) {
                result = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
            }
            Canvas canvas = new Canvas(result);
            Paint paint = new Paint();
            paint.setShader(new BitmapShader(source, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
            paint.setAntiAlias(true);
            RectF rectF = new RectF(0f, 0f, source.getWidth(), source.getHeight());
            canvas.drawRoundRect(rectF, radius, radius, paint);
            return result;
        }

        @Override
        public String getId() {
            return getClass().getName() + Math.round(radius);
        }
    }

设置圆角图片:

Glide.with(this).load(imageUrl).transform(new
GlideRoundTransform(this,100)).into(imageView);

也可以自定义圆形图片:

public class GlideCircleTransform extends BitmapTransformation {

    public GlideCircleTransform(Context context) {
        super(context);
    }

    @Override
    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        return circleCrop(pool, toTransform);
    }

    private static Bitmap circleCrop(BitmapPool pool, Bitmap source) {
        if (source == null) return null;
        //获取最小边长
        int size = Math.min(source.getWidth(), source.getHeight());
        //获取圆形图片的宽度和高度
        int x = (source.getWidth() - size) / 2;
        int y = (source.getHeight() - size) / 2;

        // TODO this could be acquired from the pool too
        Bitmap squared = Bitmap.createBitmap(source, x, y, size, size);

        Bitmap result = pool.get(size, size, Bitmap.Config.ARGB_8888);
        if (result == null) {
            result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        }

        Canvas canvas = new Canvas(result);
        Paint paint = new Paint();
        paint.setShader(new BitmapShader(squared, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
        paint.setAntiAlias(true);
        float r = size / 2f;   //得到圆形半径
        canvas.drawCircle(r, r, r, paint);
        return result;
    }

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

设置圆形图片:

Glide.with(this).load(imageUrl).transform(new GlideCircleTransform(this)).into(imageView);

12.设置要下载的内容
有些时候我们不想直接将加载的图片显示到控件上,或者我们想下载这张图片,又或者我们暂时不想在此处展示这张图片,可以这样处理:

Glide.with(this).load(imageUrl).centerCrop().into(new SimpleTarget<GlideDrawable>() {
            @Override
            public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
                //这里可根据resource自行处理(下载...)
            }
        });

13.设置监听请求接口

Glide.with(this).load(imageUrl).listener(new RequestListener<String, GlideDrawable>() {
            @Override
            public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
                return false;
            }

            @Override
            public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
                //imageView.setImageDrawable(resource);
                return false;
            }
        }).into(imageView);

14.设置GIF加载方式

Glide.with(this).load(imageUrl).asBitmap().into(imageView);//显示gif静态图片
Glide.with(this).load(imageUrl).asGif().into(imageView);//显示gif动态图片

15.缓存动态清理

Glide.get(this).clearDiskCache(); //清理磁盘缓存
Glide.get(this).clearMemory(); //清理内存缓存

Glide的高级用法

Glide内部有一个GlideModule,是用来全局配置Glide的,可进行设置缓存路径,缓存空间,图片格式,自定义cache指示等操作。

1.GlideModule添加
自定义一个GlideModule :

public class MyGlideModule implements GlideModule {
    @Override public void applyOptions(Context context, GlideBuilder builder) {
        // Apply options to the builder here.
    }

    @Override public void registerComponents(Context context, Glide glide) {
        // register ModelLoaders here.
    }
}

AndroidManifest.xml注册:

<manifest ...>
    <!-- ... permissions -->
    <application ...>
        <meta-data
            android:name="com.mypackage.MyGlideModule"
            android:value="GlideModule" />
        <!-- ... activities and other components -->
    </application>
</manifest>

混淆处理:

-keepnames class com.mypackage.MyGlideModule
# or more generally:
#-keep public class * implements com.bumptech.glide.module.GlideModule

多个GlideModule冲突问题
一般情况下我们一个项目可以有多个library项目,这样就可能有多个GlideModule的存在,但是多个GlideModule存在却会出现冲突,为了避免这种情况发生,一般我们尽量只设置一个GlideModule,当然也可在配置清单中忽略某个GlideMoudle:

<meta-data android:name=”com.mypackage.MyGlideModule” tools:node=”remove” />

2.GlideModule相关配置
设置Glide内存缓存大小:

int maxMemory = (int) Runtime.getRuntime().maxMemory();//获取系统分配给应用的总内存大小
int memoryCacheSize = maxMemory / 8;//设置图片内存缓存占用八分之一
//设置内存缓存大小
builder.setMemoryCache(new LruResourceCache(memoryCacheSize));

有些时候我们也需要获取一下默认的内存缓存大小:

MemorySizeCalculator calculator = new MemorySizeCalculator(context);  
int defaultMemoryCacheSize = calculator.getMemoryCacheSize();  
int defaultBitmapPoolSize = calculator.getBitmapPoolSize(); 

设置Glide磁盘缓存大小和磁盘缓存存放位置:

File cacheDir = context.getExternalCacheDir();//指定的是数据的缓存地址
int diskCacheSize = 1024 * 1024 * 30;//最多可以缓存多少字节的数据
//设置磁盘缓存大小和位置
builder.setDiskCache(new DiskLruCacheFactory(cacheDir.getPath(), "glide", diskCacheSize));

也可以:

//存放在data/data/xxxx/cache/
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, "glide", diskCacheSize));
//存放在外置文件浏览器
builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, "glide", diskCacheSize));

设置图片解码格式:
Glide默认的图片解码格式是RGB_565相比RGB_8888占内存更小,但是却损失了一部分图片质量,需求根据自己定。

//设置图片解码格式
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
//设置BitmapPool内存缓存大小
builder.setBitmapPool(new LruBitmapPool(memoryCacheSize));

3.使用ModelLoader自定义数据源:
有些时候我们需要根据不同的情况加载不同格式的图片,可采用工厂模式来进行选取。

定义处理URL接口

public interface IDataModel {
    String buildDataModelUrl(int width, int height);
}

实现处理URL接口
JpgDataModel:

public class JpgDataModel implements IDataModel {
    private String dataModelUrl;

    public JpgDataModel(String dataModelUrl) {
        this.dataModelUrl = dataModelUrl;
    }

    @Override
    public String buildDataModelUrl(int width, int height) {
        //http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg?imageView2/1/w/200/h/200/format/jpg
        return String.format("%s?imageView2/1/w/%d/h/%d/format/jpg", dataModelUrl, width, height);
    }
}

WebpDataModel:

public class WebpDataModel implements IDataModel {
    private String dataModelUrl;

    public WebpDataModel(String dataModelUrl) {
        this.dataModelUrl = dataModelUrl;
    }

    @Override
    public String buildDataModelUrl(int width, int height) {
        //http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg?imageView2/1/w/200/h/200/format/webp
        return String.format("%s?imageView2/1/w/%d/h/%d/format/webp", dataModelUrl, width, height);
    }
}

设置图片加工工厂

public class MyDataLoader extends BaseGlideUrlLoader<IDataModel> {
    public MyDataLoader(Context context) {
        super(context);
    }

    public MyDataLoader(ModelLoader<GlideUrl, InputStream> urlLoader) {
        super(urlLoader, null);
    }

    @Override
    protected String getUrl(IDataModel model, int width, int height) {
        return model.buildDataModelUrl(width, height);
    }

    /**
     */
    public static class Factory implements ModelLoaderFactory<IDataModel, InputStream> {

        @Override
        public ModelLoader<IDataModel, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new MyDataLoader(factories.buildModelLoader(GlideUrl.class, InputStream.class));
        }

        @Override
        public void teardown() {
        }
    }
}

根据不同的要求采用不同的策略加载图片

//加载jpg图片
Glide.with(this).using(new MyDataLoader(this)).load(new JpgDataModel(imageUrl)).into(imageView);
//加载webp图片
Glide.with(this).using(new MyDataLoader(this)).load(new WebpDataModel(imageUrl)).into(imageView);

这样每次加载都要.using(),我们也可以不用.using(),方法就是将MyDataLoader的工厂注册到GlideModel中:

public class MyGlideModule implements GlideModule {
    ...
    @Override
    public void registerComponents(Context context, Glide glide) {
        glide.register(IDataModel.class, InputStream.class, 
            new MyUrlLoader.Factory());
    }
}

调用:

 //加载jpg图片
 Glide.with(this).load(new JpgDataModel(imageUrl)).into(imageView);
 //加载webp图片
 Glide.with(this).load(new WebpDataModel(imageUrl)).into(imageView);

以上是个人对Glide的相关总结,Glide功能不止这些,甚或是还可以自定义图片缓存TAG来实现对图片软删除等操作,这些功能在开发中也是微乎其微了,当然Glide开发团队也是想的周到,以实现程序的健壮性。Glide和Picasso有很多相似之处,了解Picasso的同学可以根据Picasso的相关Api很容易上手Glide,返过来也如此,总之后续继续深入吧。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐