前言
Glide是Android中使用非常频繁的图片加载框架,在平时开发中使用非常频繁。现在Android的图片加载框架也是非常成熟了,其中Glide应该是非常流行的一个了。
Glide基本用法及介绍
添加版本依赖库
1 | dependencies { |
相比于Glide 3,这里要多添加一个compiler的库,这个库是用于生成Generated API的。另外记得添加网络权限:
1 | <uses-permission android:name="android.permission.INTERNET" /> |
加载图片
添加完了依赖库后我们就可以使用了,在布局当中加入一个Button和一个ImageView,点击Button然后加载图片到ImageView中。onClick方法的代码如下:
1 | public void onClick(View v) { |
Glide.with(this).load(url).into(imageView)就是Glide加载图片的核心代码。
占位图
会发现点击”加载图片”按钮之后,要稍微等一会图片才会显示出来,因为从网络上下载图片本来需要时间,不过这样的用户体验就不太好了,我们可以用占位图的方式优化体验,就是在图片加载完成之前先用一张本地图片占住这个位置,不至于屏幕是空白的。先准备好一张loading.jpg图片,放在资源文件夹中。修改onClick方法中加载图片的代码。
1 | RequestOptions options = new RequestOptions() |
这里我们先创建了一个RequestOptions对象,然后调用它的placeholder()方法来指定占位图,再将占位图片的资源id传入到这个方法中。最后,在Glide的三步走之间加入一个apply()方法,来应用我们刚才创建的RequestOptions对象。
不过如果你现在重新运行一下代码并点击”加载图片”很可能是根本看不到占位图效果的。因为Glide有非常强大的缓存机制,我们刚才加载图片的时候Glide自动就已经将它缓存下来了,下次加载的时候将会直接从缓存中读取,不会再去网络下载了,因而加载的速度非常快,所以占位图可能根本来不及显示。
因此这里我们还需要稍微做一点修改,来让占位图能有机会显示出来,修改代码如下所示:
1 | RequestOptions options = new RequestOptions() |
增加了diskCacheStrategy(DiskCacheStrategy.NONE);这一行代码,diskCacheStrategy方法传入了DiskCacheStrategy.NONE参数,这样就可以禁用掉Glide的缓存功能。
当再次点击”加载图片”按钮之后会立即显示一张占位图,然后等真正的图片加载完成之后会将占位图替换掉。
除了这种加载占位图之外,还有一种异常占位图。异常占位图就是指,如果因为某些异常情况导致图片加载失败,比如说手机网络信号不好,这个时候就显示这张异常占位图。准备一张error.jpg图片,然后修改Glide加载部分的代码,如下所示:
1 | RequestOptions options = new RequestOptions() |
如上代码所示,这里又串接了一个error()方法就可以指定异常占位图了。
其实看到这里,如果你熟悉Glide 3的话,相信你已经掌握Glide 4的变化规律了。在Glide 3当中,像placeholder()、error()、diskCacheStrategy()等等一系列的API,都是直接串联在Glide三步走方法中使用的。
而Glide 4(这里用的是Glide 4.9)中引入了一个RequestOptions对象,将这一系列的API都移动到了RequestOptions当中。这样做的好处是可以使我们摆脱冗长的Glide加载语句,而且还能进行自己的API封装,因为RequestOptions是可以作为参数传入到方法中的。
比如你就可以写出这样的Glide加载工具类:
1 | public class GlideUtil { |
这样就会很方便,可以让Glide的加载语句不至于变得冗长。
指定加载的图片大小
实际上,使用Glide在大多数情况下我们都是不需要指定图片大小的,因为Glide会自动根据ImageView的大小来决定图片的大小,以此保证图片不会占用过多的内存从而引发OOM。
不过,如果你真的有这样的需求,必须给图片指定一个固定的大小,Glide仍然是支持这个功能的。修改Glide加载部分的代码,如下所示:
1 | RequestOptions options = new RequestOptions() |
仍然非常简单,这里使用override()方法指定了一个图片的尺寸。也就是说,Glide现在只会将图片加载成200*100像素的尺寸,而不会管你的ImageView的大小是多少了。
如果你想加载一张图片的原始尺寸的话,可以使用Target.SIZE_ORIGINAL关键字,如下所示:
1 | RequestOptions options = new RequestOptions() |
这样的话,Glide就不会再去自动压缩图片,而是会去加载图片的原始尺寸。当然,这种写法也会面临着更高的OOM风险。
缓存机制
Glide的缓存设计可以说是非常先进的,考虑的场景也很周全。在缓存这一功能上,Glide又将它分成了两个模块,一个是内存缓存,一个是硬盘缓存。
这两个缓存模块的作用各不相同,内存缓存的主要作用是防止应用重复将图片数据读取到内存当中,而硬盘缓存的主要作用是防止应用重复从网络或其他地方重复下载和读取数据。
内存缓存
默认情况下,Glide自动就是开启内存缓存的。也就是说,当我们使用Glide加载了一张图片之后,这张图片就会被缓存到内存当中,只要在它还没从内存中被清除之前,下次使用Glide再加载这张图片都会直接从内存当中读取,而不用重新从网络或硬盘上读取了,这样无疑就可以大幅度提升图片的加载效率。比方说你在一个RecyclerView当中反复上下滑动,RecyclerView中只要是Glide加载过的图片都可以直接从内存当中迅速读取并展示出来,从而大大提升了用户体验。
而Glide最为人性化的是,你甚至不需要编写任何额外的代码就能自动享受到这个极为便利的内存缓存功能,因为Glide默认就已经将它开启了。
那么既然已经默认开启了这个功能,还有什么可讲的用法呢?只有一点,如果你有什么特殊的原因需要禁用内存缓存功能,Glide对此提供了接口,禁用内存缓存:
1 | RequestOptions options = new RequestOptions() |
可以看到,只需要调用skipMemoryCache()方法并传入true,就表示禁用掉Glide的内存缓存功能。
硬盘缓存
刚刚学习占位图功能的时候,我们就使用过硬盘缓存的功能了。当时为了禁止Glide对图片进行硬盘缓存而使用了如下代码:
1 | RequestOptions options = new RequestOptions() |
调用diskCacheStrategy()方法并传入DiskCacheStrategy.NONE,就可以禁用掉Glide的硬盘缓存功能了。
这个diskCacheStrategy()方法基本上就是Glide硬盘缓存功能的一切,它可以接收五种参数:
- DiskCacheStrategy.NONE: 表示不缓存任何内容。
- DiskCacheStrategy.DATA: 表示只缓存原始图片。
- DiskCacheStrategy.RESOURCE: 表示只缓存转换过后的图片。
- DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
- DiskCacheStrategy.AUTOMATIC: 表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)。
其中,DiskCacheStrategy.DATA对应Glide 3中的DiskCacheStrategy.SOURCE,DiskCacheStrategy.RESOURCE对应Glide 3中的DiskCacheStrategy.RESULT。而DiskCacheStrategy.AUTOMATIC是Glide 4中新增的一种缓存策略,并且在不指定diskCacheStrategy的情况下默认使用就是的这种缓存策略。
上面五种参数的解释本身并没有什么难理解的地方,但是关于转换过后的图片这个概念大家可能需要了解一下。就是当我们使用Glide去加载一张图片的时候,Glide默认并不会将原始图片展示出来,而是会对图片进行压缩和转换(我们会在稍后学习这方面的内容)。总之就是经过种种一系列操作之后得到的图片,就叫转换过后的图片。
关于Glide 4硬盘缓存的内容就讲到这里。想要了解更多Glide缓存方面的知识,可以参考 Android图片加载框架最全解析(三),深入探究Glide的缓存机制 这篇文章。
指定加载格式
我们都知道,Glide其中一个非常亮眼的功能就是可以加载GIF图片,而同样作为非常出色的图片加载框架的Picasso是不支持这个功能的。
而且使用Glide加载GIF图并不需要编写什么额外的代码,Glide内部会自动判断图片格式。比如我们将加载图片的URL地址改成一张GIF图,如下所示:
1 | Glide.with(this) |
也就是说,不管我们传入的是一张普通图片,还是一张GIF图片,Glide都会自动进行判断,并且可以正确地把它解析并展示出来。
但是如果我想指定加载格式该怎么办呢?就比如说,我希望加载的这张图必须是一张静态图片,我不需要Glide自动帮我判断它到底是静图还是GIF图。
只需要再串接一个asBitmap()方法就可以了,如下所示:
1 | Glide.with(this) |
asBitmap()方法,这个方法的意思就是说这里只允许加载静态图片,不需要Glide去帮我们自动进行图片格式的判断了。如果你传入的还是一张GIF图的话,Glide会展示这张GIF图的第一帧,而不会去播放它。
注意:在Glide 3中的语法是先load()再asBitmap()的,而在Glide 4中是先asBitmap()再load()的,写错了顺序就会报错。
那么类似地,既然我们能强制指定加载静态图片,就也能强制指定加载动态图片,对应的方法是asGif()。而Glide 4中又新增了asFile()方法和asDrawable()方法,分别用于强制指定文件格式的加载和Drawable格式的加载。
回调与监听
preload()方法
Glide加载图片虽说非常智能,它会自动判断该图片是否已经有缓存了,如果有的话就直接从缓存中读取,没有的话再从网络去下载。但是如果我希望提前对图片进行一个预加载,等真正需要加载图片的时候就直接从缓存中读取,不想再等待慢长的网络加载时间了,这该怎么办呢?
不用担心,Glide专门给我们提供了预加载的接口,也就是preload()方法,我们只需要直接使用就可以了。
preload()方法有两个方法重载,一个不带参数,表示将会加载图片的原始尺寸,另一个可以通过参数指定加载图片的宽和高。
preload()方法的用法也非常简单,直接使用它来替换into()方法即可,如下所示:
1 | Glide.with(this) |
注:替换into()方法
调用了预加载之后,我们以后想再去加载这张图片就会非常快了,因为Glide会直接从缓存当中去读取图片并显示出来,代码如下所示:
1 | Glide.with(this) |
submit()方法
一直以来,我们使用Glide都是为了将图片显示到界面上。虽然我们知道Glide会在图片的加载过程中对图片进行缓存,但是缓存文件到底是存在哪里的,以及如何去直接访问这些缓存文件?我们都还不知道。
其实Glide将图片加载接口设计成这样也是希望我们使用起来更加的方便,不用过多去考虑底层的实现细节。但如果我现在就是想要去访问图片的缓存文件该怎么办呢?这就需要用到submit()方法了。
submit()方法其实就是对应的Glide 3中的downloadOnly()方法,和preload()方法类似,submit()方法也是可以替换into()方法的,不过submit()方法的用法明显要比preload()方法复杂不少。这个方法只会下载图片,而不会对图片进行加载。当图片下载完成之后,我们可以得到图片的存储路径,以便后续进行操作。
那么首先我们还是先来看下基本用法。submit()方法有两个方法重载:
1 | submit() |
其中submit()方法是用于下载原始尺寸的图片,而submit(int width, int height)则可以指定下载图片的尺寸。
这里就以submit()方法来举例。当调用了submit()方法后会立即返回一个FutureTarget对象,然后Glide会在后台开始下载图片文件。接下来我们调用FutureTarget的get()方法就可以去获取下载好的图片文件了,如果此时图片还没有下载完,那么get()方法就会阻塞住,一直等到图片下载完成才会有值返回。
下面我们通过一个例子来演示一下吧,代码如下所示:
1 | public void downloadImage() { |
这段代码稍微有一点点长,我带着大家解读一下。首先,submit()方法必须要用在子线程当中,因为刚才说了FutureTarget的get()方法是会阻塞线程的,因此这里的第一步就是new了一个Thread。在子线程当中,我们先获取了一个Application Context,这个时候不能再用Activity作为Context了,因为会有Activity销毁了但子线程还没执行完这种可能出现。
接下来就是Glide的基本用法,只不过将into()方法替换成了submit()方法,并且还使用了一个asFile()方法来指定加载格式。submit()方法会返回一个FutureTarget对象,这个时候其实Glide已经开始在后台下载图片了,我们随时都可以调用FutureTarget的get()方法来获取下载的图片文件,只不过如果图片还没下载好线程会暂时阻塞住,等下载完成了才会把图片的File对象返回。
最后,我们使用runOnUiThread()切回到主线程,然后使用Toast将下载好的图片文件路径显示出来。这样我们就能清晰地看出来图片完整的缓存路径是什么了。
listener()方法
其实listener()方法的作用非常普遍,它可以用来监听Glide加载图片的状态。举个例子,比如说我们刚才使用了preload()方法来对图片进行预加载,但是我怎样确定预加载有没有完成呢?还有如果Glide加载图片失败了,我该怎样调试错误的原因呢?答案都在listener()方法当中。
下面来看下listener()方法的基本用法吧,不同于刚才几个方法都是要替换into()方法的,listener()是结合into()方法一起使用的,当然也可以结合preload()方法一起使用。最基本的用法如下所示:
1 | Glide.with(this) |
这里我们在into()方法之前串接了一个listener()方法,然后实现了一个RequestListener的实例。其中RequestListener需要实现两个方法,一个onResourceReady()方法,一个onLoadFailed()方法。从方法名上就可以看出来了,当图片加载完成的时候就会回调onResourceReady()方法,而当图片加载失败的时候就会回调onLoadFailed()方法,onLoadFailed()方法中会将失败的GlideException参数传进来,这样我们就可以定位具体失败的原因了。
没错,listener()方法就是这么简单。不过还有一点需要处理,onResourceReady()方法和onLoadFailed()方法都有一个布尔值的返回值,返回false就表示这个事件没有被处理,还会继续向下传递,返回true就表示这个事件已经被处理掉了,从而不会再继续向下传递。举个简单点的例子,如果我们在RequestListener的onResourceReady()方法中返回了true,那么就不会再回调Target的onResourceReady()方法了。
图片变换
图片变换的意思就是说,Glide从加载了原始图片到最终展示给用户之前,又进行了一些变换处理,从而能够实现一些更加丰富的图片效果,如图片圆角化、圆形化、模糊化等等。
添加图片变换的用法非常简单,我们只需要在RequestOptions中串接transforms()方法,并将想要执行的图片变换操作作为参数传入transforms()方法即可,如下所示:
1 | RequestOptions options = new RequestOptions() |
至于具体要进行什么样的图片变换操作,这个通常都是需要我们自己来写的。不过Glide已经内置了几种图片变换操作,我们可以直接拿来使用,比如CenterCrop、FitCenter、CircleCrop等。
但所有的内置图片变换操作其实都不需要使用transform()方法,Glide为了方便我们使用直接提供了现成的API:
1 | RequestOptions options = new RequestOptions() |
当然,这些内置的图片变换API其实也只是对transform()方法进行了一层封装而已,它们背后的源码仍然还是借助transform()方法来实现的。
这里我们就选择其中一种内置的图片变换操作来演示一下吧,circleCrop()方法是用来对图片进行圆形化裁剪的,我们动手试一下,代码如下所示:
1 | String url = "http://guolin.tech/book.png"; |
这会对图片进行圆形化裁剪。
当然,除了使用内置的图片变换操作之外,我们完全可以自定义自己的图片变换操作。理论上,在对图片进行变换这个步骤中我们可以进行任何的操作,你想对图片怎么样都可以。包括圆角化、圆形化、黑白化、模糊化等等,甚至你将原图片完全替换成另外一张图都是可以的。
不过由于这部分内容相对于Glide 3没有任何的变化,因此就不再重复进行讲解了。想学习自定义图片变换操作的朋友们可以参考这篇文章 Android图片加载框架最全解析(五),Glide强大的图片变换功能 。
关于图片变换,最后我们再来看一个非常优秀的开源库,glide-transformations。它实现了很多通用的图片变换效果,如裁剪变换、颜色变换、模糊变换等等,使得我们可以非常轻松地进行各种各样的图片变换。
glide-transformations的项目主页地址是 地址 。
下面我们就来体验一下这个库的强大功能吧。首先需要将这个库引入到我们的项目当中,在app/build.gradle文件当中添加如下依赖:
1 | dependencies { |
我们可以对图片进行单个变换处理,也可以将多种图片变换叠加在一起使用。比如我想同时对图片进行模糊化和黑白化处理,就可以这么写:
1 | String url = "http://guolin.tech/book.png"; |
可以看到,同时执行多种图片变换的时候,只需要将它们都传入到transforms()方法中即可。
自定义模块
自定义模块属于Glide中的高级功能,同时也是难度比较高的一部分内容。
这里我不可能在这一篇文章中将自定义模块的内容全讲一遍,限于篇幅的限制我只能讲一讲Glide 4中变化的这部分内容。关于Glide自定义模块的全部内容,请大家去参考 Android图片加载框架最全解析(六),探究Glide的自定义模块功能 这篇文章。
首先定义一个我们自己的模块类,并让它继承自AppGlideModule,如下所示:
1 | @GlideModule |
可以看到,在MyAppGlideModule类当中,我们重写了applyOptions()和registerComponents()方法,这两个方法分别就是用来更改Glide配置以及替换Glide组件的。
注意在MyAppGlideModule类在上面,我们加入了一个@GlideModule的注解,这是Gilde 4和Glide 3最大的一个不同之处。在Glide 3中,我们定义了自定义模块之后,还必须在AndroidManifest.xml文件中去注册它才能生效,而在Glide 4中是不需要的,因为@GlideModule这个注解已经能够让Glide识别到这个自定义模块了。
这样的话,我们就将Glide自定义模块的功能完成了。后面只需要在applyOptions()和registerComponents()这两个方法中加入具体的逻辑,就能实现更改Glide配置或者替换Glide组件的功能了。详情还是请参考 Android图片加载框架最全解析(六),探究Glide的自定义模块功能 这篇文章,这里就不再展开讨论了。