Android开发图片缓存框架Glide的总结
前段时间写过一篇图片缓存框架Picasso的用法,对于Picasso有些同学也比较熟悉,采用Lru最近最少缓存策略,并且自带内存和硬盘缓存机制,在图片加载尤其是多图加载着实为大伙省了不少力,在此之前同样也相识有Afinal、Xutil、UniversalImageLoader等优秀的开源框架,今天再总结一个图片加载缓存框架 — Glide,以助自己后边的项目构建舔砖加瓦吧。
转载请注明出处: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,不过二者在核心上还是有一定区别的:
- Picasso接收的上下文是Context,而Glide传入的上下文可以有Context、Activity、Fragment。Activity和Fragment有生命周期,因此在某个生命周期阶段图片加载也响应收到控制,更灵活。另外在某些情况下也避免了对象未进行引用而造成的内存泄漏问题。
- Glide默认的图片格式RGB565而Picasso支持的图片格式ARGB8888,尽管前者没有后者图像更清晰(相差不大),但是在内存开销上却比前者少了一半,加载更快
- Glide默认对图片缓存仅仅是展示控件的大小,如果在另外一个不同大小控件上加载相同的图片需要再次下载。Picasso缓存的图片默认是原图,可对原图进行随处展示。
- 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,返过来也如此,总之后续继续深入吧。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)