Glide是一款由Bump Technologies开发的图片加载框架,使得我们可以在Android平台上以极度简单的方式加载和展示图片。
● 添加依赖:
dependencies {
compile 'com.github.bumptech.glide:glide:3.7.0'
}
● Glide中需要用到网络功能,因此你还得在AndroidManifest.xml中声明一下网络权限才行:
<uses-permission android:name="android.permission.INTERNET" />
● Glide加载各种图片(包括加载网络上的图片、加载手机本地的图片、加载应用资源中的图片等等)
//加载网络图片
Glide.with(this).load("http://*****").into(imageView);
// 加载本地图片
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);
① Glide.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的实例传进去就可以了
● 加载占位图placeholder():就是指在图片的加载过程比较慢时,我们先显示一张临时的图片,等图片加载出来了再替换成要加载的图片
Glide.with(this)
.load(url)
.placeholder(R.drawable.loading)//首先事先准备好了一张loading.jpg图片,用来作为占位图显示
.into(imageView);
● 异常占位图error():如果因为某些异常情况导致图片加载失败,比如说手机网络信号不好,这个时候就显示这张异常占位图
Glide.with(this)
.load(url)
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView);
这里串接了一个diskCacheStrategy()方法,并传入DiskCacheStrategy.NONE参数,这样就可以禁用掉Glide的缓存功能
● 指定为静态图:加入了一个asBitmap()方法,是只允许加载静态图片,不需要Glide去帮我们自动进行图片格式的判断了
Glide.with(this)
.load(url)
.asBitmap() //指定只显示静态图
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView);
由于调用了asBitmap()方法,现在GIF图就无法正常播放了,而是会在界面上显示第一帧的图片。
● 指定为静态图:加入了一个asGif()方法,是只允许加载动态图片,如果指定了只能加载动态图片,而传入的图片却是一张静图的话,那么结果自然就只有加载失败(即执行error(R.drawable.error))
Glide.with(this)
.load(url)
.asGif() //只加载动态图
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView);
实际上,使用Glide在绝大多数情况下我们都是不需要指定图片大小的。使用Glide,我们就完全不用担心图片内存浪费(比如说一张图片的尺寸是1000*1000像素,但是我们界面上的ImageView可能只有200*200像素,这个时候如果你不对图片进行任何压缩就直接读取到内存中,这就属于内存浪费了),甚至是内存溢出的问题。因为Glide从来都不会直接将图片的完整尺寸全部加载到内存中,而是用多少加载多少。Glide会自动判断ImageView的大小,然后只将这么大的图片像素加载到内存当中,帮助我们节省内存开支
不过,如果你真的有这样的需求,必须给图片指定一个固定的大小,Glide仍然是支持这个功能的
● 指定图片大小:使用override(width,height)方法指定了一个图片的尺寸,也就是说,Glide现在只会将图片加载成100*100像素的尺寸,而不会管你的ImageView的大小是多少了。
Glide.with(this)
.load(url)
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.override(100, 100) //指定图片大小
.into(imageView);
Glide又将它分成了两个模块,一个是内存缓存,一个是硬盘缓存。这两个缓存模块的作用各不相同:
内存缓存的主要作用是防止应用重复将图片数据读取到内存当中,而
硬盘缓存的主要作用是防止应用重复从网络或其他地方重复下载和读取数据。
Glide最为人性化的是,你甚至不需要编写任何额外的代码就能自动享受到这个极为便利的内存缓存功能,因为Glide默认就已经将它开启了。
● 关闭内存缓存:调用skipMemoryCache()方法并传入true,就表示禁用掉Glide的内存缓存功能
Glide.with(this)
.load(url)
.skipMemoryCache(true) //关闭内存缓存
.into(imageView);
● 关闭硬盘缓存:调用diskCacheStrategy()方法并传入DiskCacheStrategy.NONE,就可以禁用掉Glide的硬盘缓存功能了。
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.NONE) //关闭硬盘缓存
.into(imageView);
diskCacheStrategy()方法它可以接收四种参数:
当我们使用Glide去加载一张图片的时候,Glide默认并不会将原始图片展示出来,而是会对图片进行压缩和转换(我们会在后面学习这方面的内容)。总之就是经过种种一系列操作之后得到的图片,就叫转换过后的图片。而Glide默认情况下在硬盘缓存的就是转换过后的图片,我们通过调用diskCacheStrategy()方法则可以改变这一默认行为。
如果希望提前对图片进行一个预加载,等真正需要加载图片的时候就直接从缓存中读取,可以不用再等待慢长的网络加载时间了
preload()方法有两个方法重载
①preload()方法一个不带参数,表示将会加载图片的原始尺寸,
②preload(width,height)方法另一个可以通过参数指定加载图片的宽和高。
预加载:preload()方法的用法也非常简单,直接使用它来替换into()方法即可,如下所示:
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.preload();//不带参数,默认图片原始尺寸
我们如果使用了preload()方法,最好要将diskCacheStrategy的缓存策略指定成DiskCacheStrategy.SOURCE。因为preload()方法默认是预加载的原始图片大小,而into()方法则默认会根据ImageView控件的大小来动态决定加载图片的大小。因此,如果不将diskCacheStrategy的缓存策略指定成DiskCacheStrategy.SOURCE的话,很容易会造成我们在预加载完成之后再使用into()方法加载图片,却仍然还是要从网络上去请求图片这种现象。
调用了预加载之后,我们以后想再去加载这张图片就会非常快了,因为Glide会直接从缓存当中去读取图片并显示出来,代码如下所示:
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(imageView);
想要去访问图片的缓存文件该怎么办呢?这就需要用到downloadOnly()方法了。和preload()方法类似,downloadOnly()方法也是可以替换into()方法的。
downloadOnly()方法是定义在DrawableTypeRequest类当中的,它有两个方法重载,一个接收图片的宽度和高度,另一个接收一个泛型对象,如下所示:
① downloadOnly(int width, int height):用于在子线程中下载图片的
② downloadOnly(Y target):用于在主线程中下载图片的。
当调用了downloadOnly(int width, int height)方法后会立即返回一个FutureTarget对象,然后Glide会在后台开始下载图片文件。接下来我们调用FutureTarget的get()方法就可以去获取下载好的图片文件了,如果此时图片还没有下载完,那么get()方法就会阻塞住,一直等到图片下载完成才会有值返回。
public void downloadImage(View view) {
new Thread(new Runnable() {
@Override
public void run() {
try {
String url = "address";
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(int width, int height)方法必须要用在子线程当中,因此这里的第一步就是new了一个Thread。在子线程当中,我们先获取了一个Application Context,这个时候不能再用Activity作为Context了,因为会有Activity销毁了但子线程还没执行完这种可能出现。
接下来就是Glide的基本用法,只不过将into()方法替换成了downloadOnly()方法。downloadOnly()方法会返回一个FutureTarget对象,这个时候其实Glide已经开始在后台下载图片了,我们随时都可以调用FutureTarget的get()方法来获取下载的图片文件,只不过如果图片还没下载好线程会暂时阻塞住,等下载完成了才会把图片的File对象返回。
最后,我们使用runOnUiThread()切回到主线程,然后使用Toast将下载好的图片文件路径显示出来。
如果之后我们可以使用如下代码去加载这张图片(同一个URL),图片就会立即显示出来,而不用再去网络上请求了,因为之前已经下载到本地了,会优先读取:
public void loadImage(View view) {
String url = "address";
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(imageView);
}
需要注意的是,这里必须将硬盘缓存策略指定成DiskCacheStrategy.SOURCE或者DiskCacheStrategy.ALL,否则Glide将无法使用我们刚才下载好的图片缓存文件。
downloadOnly(int width, int height)方法必须使用在子线程当中,最主要还是因为它在内部帮我们自动创建了一个RequestFutureTarget,是这个RequestFutureTarget要求必须在子线程当中执行。而downloadOnly(Y target)方法则要求我们传入一个自己创建的Target,因此就不受RequestFutureTarget的限制了。
但是downloadOnly(Y target)方法的用法也会相对更复杂一些,因为我们又要自己创建一个Target了,而且这次必须直接去实现最顶层的Target接口,比之前的SimpleTarget和ViewTarget都要复杂不少。
那么下面我们就来实现一个最简单的DownloadImageTarget吧,注意Target接口的泛型必须指定成File对象,这是downloadOnly(Y target)方法要求的,代码如下所示:
public class DownloadImageTarget implements Target<File> {
private static final String TAG = "DownloadImageTarget";
@Override
public void onStart() {
}
@Override
public void onStop() {
}
@Override
public void onDestroy() {
}
@Override
public void onLoadStarted(Drawable placeholder) {
}
@Override
public void onLoadFailed(Exception e, Drawable errorDrawable) {
}
@Override
public void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) {
Log.d(TAG, resource.getPath());
}
@Override
public void onLoadCleared(Drawable placeholder) {
}
@Override
public void getSize(SizeReadyCallback cb) {
cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
}
@Override
public void setRequest(Request request) {
}
@Override
public Request getRequest() {
return null;
}
}
其中只有两个方法是必须实现的,一个是getSize()方法,一个是onResourceReady()方法。
Glide在开始加载图片之前会先计算图片的大小,然后回调到onSizeReady()方法当中,之后才会开始执行图片加载。而这里,计算图片大小的任务就交给我们了。只不过这是一个最简单的Target实现,在getSize()方法中就直接回调了Target.SIZE_ORIGINAL,表示图片的原始尺寸。
然后onResourceReady()方法我们就非常熟悉了,图片下载完成之后就会回调到这里,在这个方法中只是打印了一下下载的图片文件的路径。
这样一个最简单的DownloadImageTarget就定义好了,使用它也非常的简单,我们不用再考虑什么线程的问题了,而是直接把它的实例传入downloadOnly(Y target)方法中即可,如下所示:
public void downloadImage(View view) {
String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
Glide.with(this)
.load(url)
.downloadOnly(new DownloadImageTarget());
}
其实listener()方法的作用非常普遍,它可以用来监听Glide加载图片的状态。
举个例子,比如说我们刚才使用了preload()方法来对图片进行预加载,但是怎样确定预加载有没有完成呢?还有如果Glide加载图片失败了,该怎样调试错误的原因呢?答案都在listener()方法当中。
listener()方法的基本用法吧,不同于刚才几个方法都是要替换into()方法的,listener()是结合into()方法一起使用的,当然也可以结合preload()方法一起使用。最基本的用法如下所示:
public void loadImage(View view) {
String url = "address";
Glide.with(this)
.load(url)
.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) {
return false;
}
})
.into(imageView);
}
在into()方法之前串接了一个listener()方法,然后实现了一个RequestListener的实例。其中RequestListener需要实现两个方法,一个onResourceReady()方法,一个onException()方法。
① 当图片加载完成的时候就会回调onResourceReady()方法
② 当图片加载失败的时候就会回调onException()方法,onException()方法中会将失败的Exception参数传进来,这样我们就可以定位具体失败的原因了。
onResourceReady()方法和onException()方法都有一个布尔值的返回值,
返回false就表示这个事件没有被处理,还会继续向下传递,
返回true就表示这个事件已经被处理掉了,从而不会再继续向下传递。举个简单点的例子,如果我们在RequestListener的onResourceReady()方法中返回了true,那么就不会再回调Target的onResourceReady()方法了。
虽说Glide的图片变换功能框架已经很强大了,使得我们可以轻松地自定义图片变换效果,但是如果每一种图片变换都要我们自己去写还是蛮吃力的。事实上,确实也没有必要完全靠自己去实现各种各样的图片变换效果,因为大多数的图片变换都是比较通用的,各个项目会用到的效果都差不多,我们每一个都自己去重新实现无异于重复造轮子。
也正是因此,网上出现了很多Glide的图片变换开源库,其中做的最出色的应该要数glide-transformations这个库了。它实现了很多通用的图片变换效果,如裁剪变换、颜色变换、模糊变换等等,使得我们可以非常轻松地进行各种各样的图片变换。
glide-transformations的项目主页地址是 https://github.com/wasabeef/glide-transformations 。
repositories {
jcenter()
}
dependencies {
implementation 'jp.wasabeef:glide-transformations:4.x.x'
// If you want to use the GPU Filters
implementation 'jp.co.cyberagent.android:gpuimage:2.x.x'
}
Glide.with(this).load(R.drawable.demo)
.apply(RequestOptions.bitmapTransform(BlurTransformation(25, 3)))
.into(imageView)
//普通矩形
Glide.with(mContext).load(R.drawable.img2)
.apply(RequestOptions.bitmapTransform(new CropSquareTransformation()))
.into(ivCropSquare);
//长方形带圆角
Glide.with(mContext).load(R.drawable.img2)
.apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(10, 3)))
.into(ivRoundCorner);
//圆形
Glide.with(mContext).load(R.drawable.img2)
.apply(RequestOptions.bitmapTransform(new CircleCrop()))
.into(ivCircle);
//模糊效果
Glide.with(mContext).load(R.drawable.img2)
.apply(RequestOptions.bitmapTransform(new BlurTransformation()))
.into(ivBlur);
组合效果(可以把前面的效果组合一起)
MultiTransformation multi = new MultiTransformation(
new BlurTransformation(25),
new RoundedCornersTransformation(50, 0, RoundedCornersTransformation.CornerType.BOTTOM));
Glide.with(mContext).load(R.drawable.img2)
.apply(RequestOptions.bitmapTransform(multi))
.into(ivCropGroup);
比如:
ToonFilterTransformation
SepiaFilterTransformation
ContrastFilterTransformation
InvertFilterTransformation
PixelationFilterTransformation
SketchFilterTransformation
SwirlFilterTransformation
BrightnessFilterTransformation
KuwaharaFilterTransformation
VignetteFilterTransformation
效果(分类):
Crop
CropTransformation
CropCircleTransformation
CropCircleWithBorderTransformation
CropSquareTransformation
RoundedCornersTransformation
Color
ColorFilterTransformation
GrayscaleTransformation
Blur
BlurTransformation
Mask
MaskTransformation
大多数情况下,想要实现的图片加载效果只需要一行代码就能解决了。但是Glide过于简洁的API也造成了一个问题,就是如果想要更改Glide的某些默认配置项应该怎么操作呢?很难想象如何将更改Glide配置项的操作串联到一行经典的Glide图片加载语句中当中吧?没错,这个时候就需要用到自定义模块功能了。
自定义模块功能可以将更改Glide配置,替换Glide组件等操作独立出来,使得能轻松地对Glide的各种配置进行自定义,并且又和Glide的图片加载逻辑没有任何交集,这也是一种低耦合编程方式的体现。
自定义模块的基本用法:
public class MyGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
//用来更改Glide和配置
}
@Override
public void registerComponents(Context context, Glide glide) {
//替换Glide组件的
}
}
重写了applyOptions()和registerComponents()方法,这两个方法分别就是用来更改Glide和配置以及替换Glide组件的,只需要在这两个方法中加入具体的逻辑,就能实现更改Glide配置或者替换Glide组件的功能了。
<manifest>
...
<application>
<meta-data
android:name="com.example.glidetest.MyGlideModule"
android:value="GlideModule" />
...
</application>
</manifest>
在<application>标签中加入一个meta-data配置项,其中android:name指定成我们自定义的MyGlideModule的完整路径,android:value必须指定成GlideModule,这个是固定值。
更改Glide配置:
如果想要更改Glide的默认配置,其实只需要在applyOptions()方法中提前将Glide的配置项进行初始化就可以了,Glide常用配置项:
用于配置Glide的内存缓存策略,默认配置是LruResourceCache。
用于配置Glide的Bitmap缓存池,默认配置是LruBitmapPool。
用于配置Glide的硬盘缓存策略,默认配置是InternalCacheDiskCacheFactory。
用于配置Glide读取缓存中图片的异步执行器,默认配置是FifoPriorityThreadPoolExecutor,也就是先入先出原则。
用于配置Glide读取非缓存中图片的异步执行器,默认配置也是FifoPriorityThreadPoolExecutor。
用于配置Glide加载图片的解码模式,默认配置是RGB_565。
例子1:
public class MyGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDiskCache(new ExternalCacheDiskCacheFactory(context));
}
@Override
public void registerComponents(Context context, Glide glide) {
}
}
现在所有Glide加载的图片都会缓存到SD卡上了。
例子2:
public class MyGlideModule implements GlideModule {
public static final int DISK_CACHE_SIZE = 500 * 1024 * 1024;
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, DISK_CACHE_SIZE));
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
}
@Override
public void registerComponents(Context context, Glide glide) {
}
}
Glide加载图片的默认格式是RGB_565,而Picasso加载图片的默认格式是ARGB_8888。ARGB_8888格式的图片效果会更加细腻,但是内存开销会比较大。而RGB_565格式的图片则更加节省内存,但是图片效果上会差一些。
我们只需要在MyGlideModule中更改一下默认配置即可,通过这样配置之后,使用Glide加载的所有图片都将会使用ARGB_8888的格式,虽然图片质量变好了,但同时内存开销也会明显增大。
替换Glide组件:
替换Glide组件功能需要在自定义模块的registerComponents()方法中加入具体的替换逻辑。相比于更改Glide配置,替换Glide组件这个功能的难度就明显大了不少。Glide中的组件非常繁多,也非常复杂,但其实大多数情况下并不需要我们去做什么替换。不过,有一个组件却有着比较大的替换需求,那就是Glide的HTTP通讯组件。
默认情况下,Glide使用的是基于原生HttpURLConnection进行订制的HTTP通讯组件,但是更喜欢使用OkHttp,因此将Glide中的HTTP通讯组件修改成OkHttp的这个需求比较常见。
首先来看一下Glide中目前有哪些组件吧,在Glide类的构造方法当中,:
public class Glide {
Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context, DecodeFormat decodeFormat) {
...
register(File.class, ParcelFileDescriptor.class, new FileDescriptorFileLoader.Factory());
register(File.class, InputStream.class, new StreamFileLoader.Factory());
register(int.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
register(int.class, InputStream.class, new StreamResourceLoader.Factory());
register(Integer.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
register(Integer.class, InputStream.class, new StreamResourceLoader.Factory());
register(String.class, ParcelFileDescriptor.class, new FileDescriptorStringLoader.Factory());
register(String.class, InputStream.class, new StreamStringLoader.Factory());
register(Uri.class, ParcelFileDescriptor.class, new FileDescriptorUriLoader.Factory());
register(Uri.class, InputStream.class, new StreamUriLoader.Factory());
register(URL.class, InputStream.class, new StreamUrlLoader.Factory());
register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
register(byte[].class, InputStream.class, new StreamByteArrayLoader.Factory());
...
}
}
调用register()方法的方式来注册一个组件,register()方法中传入的参数表示Glide支持使用哪种参数类型来加载图片,以及如何去处理这种类型的图片加载。举个例子:
register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
这句代码就表示,可以使用Glide.with(context).load(new GlideUrl("url...")).into(imageView)的方式来加载图片,而HttpUrlGlideUrlLoader.Factory则是要负责处理具体的网络通讯逻辑。如果想要将Glide的HTTP通讯组件替换成OkHttp的话,那么只需要在自定义模块当中重新注册一个GlideUrl类型的组件就行了。
通过public void registerComponents(Context context, Glide glide)替换组件有点麻烦,Glide官方给我们提供了非常简便的HTTP组件替换方式。并且除了支持OkHttp3之外,还支持OkHttp2和Volley。
dependencies {
compile 'com.squareup.okhttp3:okhttp:3.9.0'
compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0@aar'
}
dependencies {
compile 'com.github.bumptech.glide:okhttp-integration:1.5.0@aar'
compile 'com.squareup.okhttp:okhttp:2.7.5'
}
dependencies {
compile 'com.github.bumptech.glide:volley-integration:1.5.0@aar'
compile 'com.mcxiaoke.volley:library:1.0.19'
}