前言
现在Android上的图片加载框架非常成熟,从最早的老牌图片加载框架UniversalImageLoader,到后来Google推出的Volley,再到后来的新兴军Glide和Picasso,当然还有Facebook的Fresco。每一个都非常稳定,功能也都十分强大。但是它们的使用场景基本都是重合的,也就是说我们基本只需要选择其中一个来进行学习和使用就足够了。
在这几个框架当中,从易用性上来讲,Glide和Picasso应该都是完胜其他框架的,这两个框架都实在是太简单好用了,大多数情况下加载图片都是一行代码就能解决的,而UniversalImageLoader和Fresco则在这方面略逊一些。
关于Glide和Picasso的对比呢,首先这两个框架的用法非常相似,但其实它们各有特色。Picasso比Glide更加简洁和轻量,Glide比Picasso功能更为丰富。之前已经有人对这两个框架进行过全方面的对比,大家如果想了解更多的话可以去参考一下 这篇文章 。
关于Glide这个框架本人用的还是比较多的,并且作为Google官方推荐的图片加载框架,我们很有必要对Glide的使用做一个全方位的了解以应对项目中的各种需求。
目前Glide框架4.0及以上版本已经发布,相比于Glide 3 而言,Glide4并不能算是有什么突破性的升级,而更多的是一些API工整方面的优化,相比于Glide3的API,Glide4进行了更加科学合理的调整,使得易读性,易写性,可扩展性等方面都有了不错的提升,但如果你已经对Glide3非常熟悉的话,并不是就必须要切换到Glide4上面来,因为Glide4能实现的功能Glide3也能实现,而且Glide4在性能方面也并没有什么提升,但是对于刚接触Glide的朋友而言,直接上手Glide4是最佳选择,推荐博客:带你全面了解Glide 4的用法
添加Glide支持
推荐使用Glide 3.7版本,该版本比较成熟和稳定
1.app/build.gradle文件当中添加如下依赖:
dependencies {
compile 'com.github.bumptech.glide:glide:3.7.0'
}
2.如果使用Glide加载网络图片,别忘记添加网络权限:
<uses-permission android:name="android.permission.INTERNET" />
开始使用Glide
1.一句代码即可完成图片加载
String url = "http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg";
// 一句代码即可完成网络图片的加载
Glide.with(this).load(url).into(imageView);
with() | 用于创建一个加载图片的实例。with()方法可以接收Context、Activity或者Fragment类型的参数。也就是说我们选择的范围非常广,不管是在Activity还是Fragment中调用with()方法,都可以直接传this。那如果调用的地方既不在Activity中也不在Fragment中呢?也没关系,我们可以获取当前应用程序的ApplicationContext,传入到with()方法当中。注意with()方法中传入的实例会决定Glide加载图片的生命周期,如果传入的是Activity或者Fragment的实例,那么当这个Activity或Fragment被销毁的时候,图片加载也会停止。如果传入的是ApplicationContext,那么只有当应用程序被杀掉的时候,图片加载才会停止。 |
---|---|
load() | 用于指定待加载的图片资源。Glide支持加载各种各样的图片资源,包括网络图片、本地图片、应用资源、二进制流、Uri对象等。 |
into() | 这个方法就很简单了,我们希望让图片显示在哪个ImageView上,把这个ImageView的实例传进去就可以了。当然,into()方法不仅仅是只能接收ImageView类型的参数,还支持很多更丰富的用法,不过那个属于高级技巧,我们会在后面的文章当中学习 |
load()方法的其他重载方法的使用:
// 加载本地图片
File file = new File(getExternalCacheDir() + "/image.jpg");
Glide.with(this).load(file).into(imageView);
// 加载应用资源
int resource = R.drawable.image;
Glide.with(this).load(resource).into(imageView);
// 加载二进制流
byte[] image = getImageBytes();
Glide.with(this).load(image).into(imageView);
// 加载Uri对象
Uri imageUri = getImageUri();
Glide.with(this).load(imageUri).into(imageView);
2.占位图 & 异常占位图
占位图 就是指在图片的加载过程中,我们先显示一张临时的图片,等图片加载出来了再替换成要加载的图片。
Glide.with(this)
.load(url)
.placeholder(R.drawable.loading)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView);
因为Glide框架自身的缓存机制,第一次加载之后再次加载直接读取缓存,看不到占位图的效果,因此我们添加diskCacheStrategy(DiskCacheStrategy.NONE)方法来禁用Glide的缓存,关于Glide缓存方面的内容我们将会在后面的文章进行详细的讲解。
运行效果如下:
异常占位图 就是指如果因为某些异常情况导致图片加载失败,比如说图片地址错误,手机网络信号不好,这个时候就显示这张异常占位图:
Glide.with(this)
.load(url)
.placeholder(R.drawable.loading) // 占位图
.error(R.drawable.error) // 异常占位图
.diskCacheStrategy(DiskCacheStrategy.NONE) // 禁用磁盘缓存
.into(imageView);
如果此时我们修改图片url为不存在的url地址,或者直接将网络断开,运行效果如图:
3.指定图片格式
Glide另外一个强大的功能,那就是Glide是支持加载GIF图片的。这一点确实非常牛逼,因为相比之下Jake Warton曾经明确表示过,Picasso是不会支持加载GIF图片的。
而使用Glide加载GIF图并不需要编写什么额外的代码,Glide内部会自动判断图片格式。比如这是一张GIF图片的URL地址:
http://p1.pstatp.com/large/166200019850062839d3
我们只需要将刚才那段加载图片代码中的URL地址替换成上面的地址就可以了,现在重新运行一下代码,效果如下图所示:
也就是说,不管我们传入的是一张普通图片,还是一张GIF图片,Glide都会自动进行判断,并且可以正确地把它解析并展示出来。
但是如果我想指定图片的格式该怎么办呢?就比如说,我希望加载的这张图必须是一张静态图片,我不需要Glide自动帮我判断它到底是静图还是GIF图。
想实现这个功能仍然非常简单,我们只需要再串接一个新的方法就可以了,如下所示:
Glide.with(this)
.load(url)
.asBitmap() // 指定只加载静态图,如果url是一张gif图,只会显示第一帧
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView);
可以看到,这里在load()方法的后面加入了一个asBitmap()方法,这个方法的意思就是说这里只允许加载静态图片,不需要Glide去帮我们自动进行图片格式的判断了。
现在重新运行一下程序,效果如下图所示:
由于调用了asBitmap()方法,现在GIF图就无法正常播放了,而是会在界面上显示第一帧的图片。
那么类似地,既然我们能强制指定加载静态图片,就也能强制指定加载动态图片。比如说我们想要实现必须加载动态图片的功能,就可以这样写:
Glide.with(this)
.load(url)
.asGif() // 指定只加载gif图,如果给的url指向的是一张静态图,那么只能是加载错误
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView);
如果指定了只能加载动态图片,而传入的图片却是一张静图的话,那么结果自然就只有加载失败,效果如下图:
4.指定图片大小
实际上,使用Glide在绝大多数情况下我们都是不需要指定图片大小的。
在学习本节内容之前,你可能还需要先了解一个概念,就是我们平时在加载图片的时候很容易会造成内存浪费。什么叫内存浪费呢?比如说一张图片的尺寸是1000x1000像素,但是我们界面上的ImageView可能只有200x200像素,这个时候如果你不对图片进行任何压缩就直接读取到内存中,这就属于内存浪费了,因为程序中根本就用不到这么高像素的图片。
而使用Glide,我们就完全不用担心图片内存浪费,甚至是内存溢出的问题。因为Glide从来都不会直接将图片的完整尺寸全部加载到内存中,而是用多少加载多少。Glide会自动判断ImageView的大小,然后只将这么大的图片像素加载到内存当中,帮助我们节省内存开支。
不过,如果你真的有这样的需求,必须给图片指定一个固定的大小,Glide仍然是支持这个功能的。修改Glide加载部分的代码,如下所示:
Glide.with(this)
.load(url)
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.override(100, 100)
.into(imageView);
这里使用override()方法指定了一个图片的尺寸,也就是说,Glide现在只会将图片加载成100*100像素的尺寸,而不会管你的ImageView的大小是多少了。
5.关于Glide的缓存
Glide的缓存设计可以说是非常先进的,考虑的场景也很周全。在缓存这一功能上,Glide又将它分成了两个模块,一个是内存缓存,一个是硬盘缓存。
这两个缓存模块的作用各不相同,内存缓存的主要作用是防止应用重复将图片数据读取到内存当中,而硬盘缓存的主要作用是防止应用重复从网络或其他地方重复下载和读取数据。
默认情况下,Glide的两种缓存都是开启的。
内存缓存
默认情况下,Glide自动就是开启内存缓存的。也就是说,当我们使用Glide加载了一张图片之后,这张图片就会被缓存到内存当中,只要在它还没从内存中被清除之前,下次使用Glide再加载这张图片都会直接从内存当中读取,而不用重新从网络或硬盘上读取了,这样无疑就可以大幅度提升图片的加载效率。比方说你在一个RecyclerView当中反复上下滑动,RecyclerView中只要是Glide加载过的图片都可以直接从内存当中迅速读取并展示出来,从而大大提升了用户体验,如果你有什么特殊的原因需要禁用内存缓存功能,Glide对此提供了接口:
Glide.with(this)
.load(url)
.skipMemoryCache(true) // 禁用内存缓存
.into(imageView);
可以看到,只需要调用skipMemoryCache()方法并传入true,就表示禁用掉Glide的内存缓存功能。
磁盘缓存
在前面为了禁止Glide对图片进行硬盘缓存而使用了如下代码:
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView);
调用diskCacheStrategy()方法并传入DiskCacheStrategy.NONE,就可以禁用掉Glide的硬盘缓存功能了。
这个diskCacheStrategy()方法基本上就是Glide硬盘缓存功能的一切,它可以接收四种参数:
DiskCacheStrategy.NONE: 表示不缓存任何内容。
DiskCacheStrategy.SOURCE: 表示只缓存原始图片。
DiskCacheStrategy.RESULT: 表示只缓存转换过后的图片(默认选项)。
DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
上面四种参数的解释本身并没有什么难理解的地方,但是有一个概念大家需要了解,就是当我们使用Glide去加载一张图片的时候,Glide默认并不会将原始图片展示出来,而是会对图片进行压缩和转换(我们会在后面学习这方面的内容)。总之就是经过种种一系列操作之后得到的图片,就叫转换过后的图片。而Glide默认情况下在硬盘缓存的就是转换过后的图片,我们通过调用diskCacheStrategy()方法则可以改变这一默认行为。
关于缓存的项目案例:
比如之前有一位群里的朋友就跟我说过,他们项目的图片资源都是存放在七牛云上面的,而七牛云为了对图片资源进行保护,会在图片url地址的基础之上再加上一个token参数。也就是说,一张图片的url地址可能会是如下格式:
http://url.com/image.jpg?token=d9caa6e02c990b0a
而使用Glide加载这张图片的话,也就会使用这个url地址来组成缓存Key。
但是接下来问题就来了,token作为一个验证身份的参数并不是一成不变的,很有可能时时刻刻都在变化。而如果token变了,那么图片的url也就跟着变了,图片url变了,缓存Key也就跟着变了。结果就造成了,明明是同一张图片,就因为token不断在改变,导致Glide的缓存功能完全失效了。
这其实是个挺棘手的问题,而且我相信绝对不仅仅是七牛云这一个个例,大家在使用Glide的时候很有可能都会遇到这个问题。
那么该如何解决这个问题呢?方案如下:
1.创建一个MyGlideUrl继承自GlideUrl,代码如下所示:
public class MyGlideUrl extends GlideUrl {
private String mUrl;
public MyGlideUrl(String url) {
super(url);
mUrl = url;
}
@Override
public String getCacheKey() {
return mUrl.replace(findTokenParam(), "");
}
private String findTokenParam() {
String tokenParam = "";
int tokenKeyIndex = mUrl.indexOf("?token=") >= 0 ? mUrl.indexOf("?token=") : mUrl.indexOf("&token=");
if (tokenKeyIndex != -1) {
int nextAndIndex = mUrl.indexOf("&", tokenKeyIndex + 1);
if (nextAndIndex != -1) {
tokenParam = mUrl.substring(tokenKeyIndex + 1, nextAndIndex + 1);
} else {
tokenParam = mUrl.substring(tokenKeyIndex);
}
}
return tokenParam;
}
}
这里我们重写了getCacheKey()方法,在里面加入了一段逻辑用于将图片url地址中token参数的这一部分移除掉。这样getCacheKey()方法得到的就是一个没有token参数的url地址,从而不管token怎么变化,最终Glide的缓存Key都是固定不变的了。
当然,定义好了MyGlideUrl,我们还得使用它才行,将加载图片的代码改成如下方式即可:
Glide.with(this)
.load(new MyGlideUrl(url))
.into(imageView);
也就是说,我们需要在load()方法中传入这个自定义的MyGlideUrl对象,而不能再像之前那样直接传入url字符串了。不然的话Glide在内部还是会使用原始的GlideUrl类,而不是我们自定义的MyGlideUrl类。
这样我们就将这个棘手的缓存问题给解决掉了。
源码分析参见郭霖大神的系列博客完整版:https://blog.csdn.net/guolin_blog/article/details/53939176