Glide图片加载框架的使用简介
.
1. 在app/build.gradle文件当中添加如下依赖:
dependencies {
//图片加载框架之Glide
implementation 'com.github.bumptech.glide:glide:3.7.0'
}
2. 在AndroidManifest.xml中声明一下网络权限才行:
<uses-permission android:name="android.permission.INTERNET" />
3. 开始使用Glide加载图片
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
private String url = "https://konachan.net/sample/35daf8e3177fe2fee4da571ea2e25edf/Konachan.com%20-%20319531%20sample.jpg";//加载网络图片
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.image_view);
}
public void loadImage(View view) {
//使用Glide加载图片
Glide.with(this)//绑定图片加载的生命周期
.load(url)//设置要加载图片的地址
.into(imageView);设置要加载到的ImageView
}
}
with()方法的介绍
作用: 用于创建一个加载图片的实例;with()方法可以接收Context、Activity或者Fragment类型的参数
注意:
with()方法中传入的实例会决定Glide加载图片的生命周期,如果传入的是Activity或者Fragment的实例,那么当这个Activity或Fragment被销毁的时候,图片加载也会停止。如果传入的是ApplicationContext,那么只有当应用程序被杀掉的时候,图片加载才会停止
load()方法的介绍
作用: 用于指定待加载的图片资源
注意:
Glide支持加载各种各样的图片资源,包括网络图片、本地图片、应用资源、二进制流、Uri对象等等
load()方法也有很多个方法重载有如下:
//加载网络图片
String url = "https://konachan.net/sample/35daf8e3177fe2fee4da571ea2e25edf/Konachan.com%20-%20319531%20sample.jpg";
Glide.with(this).load(url).into(imageView);
//加载网络Gif图片
String gifUrl = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606494098848&di=4e086e68e595a1e7526c4e7aa9b18060&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fforum%2Fw%3D580%2Fsign%3D270f696bdbf9d72a17641015e42b282a%2Fc9c65824ab18972be9440d9ee4cd7b899f510ab3.jpg";
Glide.with(this).load(gifUrl).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);
into()方法的介绍
作用: 我们希望让图片显示在哪个ImageView上,把这个ImageView的实例传进去就可以了
注意:
into()方法不仅仅是只能接收ImageView类型的参数,还支持很多更丰富的用法
.
.
Glide提供的Api的介绍
占位图
Glide.with(this)
.load(url)
.placeholder(R.drawable.loading)//占位图
.into(imageView);
使用placeholder()方法,然后将占位图片的资源id传入到这个方法中即可
Glide.with(this)
.load(url)
.placeholder(R.drawable.loading)//占位图
.error(R.drawable.error)//加载失败的占位图
.into(imageView);
使用error()方法,然后将占位图片的资源id传入到这个方法中即可
.
指定图片格式
- asBitmap() :只加载静态图片
Glide.with(this)
.load(url)
.asBitmap()//只允许加载静态图片
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.into(imageView);
- asGif() : 只允许加载Gif图片
Glide.with(this)
.load(url)
.asGif()//只允许加载Gif图片
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.into(imageView);
.
指定图片大小
实际上,使用Glide在绝大多数情况下我们都是不需要指定图片大小的。
而使用Glide,我们就完全不用担心图片内存浪费,甚至是内存溢出的问题。因为Glide从来都不会直接将图片的完整尺寸全部加载到内存中,而是用多少加载多少。Glide会自动判断ImageView的大小,然后只将这么大的图片像素加载到内存当中,帮助我们节省内存开支。
如果你真的有这样的需求,必须给图片指定一个固定的大小,Glide仍然是支持这个功能的
Glide.with(this)
.load(url)
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.override(300, 200)//指定图片大小
.into(imageView);
.
.
Glide缓存简介
Glide的缓存设计可以说是非常先进的,考虑的场景也很周全。在缓存这一功能上,Glide又将它分成了两个模块,一个是内存缓存,一个是硬盘缓存。
这两个缓存模块的作用各不相同,内存缓存的主要作用是防止应用重复将图片数据读取到内存当中,而硬盘缓存的主要作用是防止应用重复从网络或其他地方重复下载和读取数据。
缓存Key
既然是缓存功能,就必然会有用于进行缓存的Key。那么Glide的缓存Key是怎么生成的呢?我不得不说,Glide的缓存Key生成规则非常繁琐,决定缓存Key的参数竟然有10个之多。
我们先来看一下Glide缓存Key的生成逻辑
public class Engine implements EngineJobListener,MemoryCache.ResourceRemovedListener,EngineResource.ResourceListener {
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb)
{
Util.assertMainThread();
long startTime = LogTime.getLogTime();
//调用了fetcher.getId()方法获得了一个id字符串,这个字符串也就是我们要加载的图片的唯一标识。
//比如说如果是一张网络上的图片的话,那么这个id就是这张图片的url地址
final String id = fetcher.getId();
//生成缓存Key
//EngineKey也就是Glide中的缓存Key
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
...
}
...
}
注:
可见,决定缓存Key的条件非常多,即使你用override()方法改变了一下图片的width或者height,也会生成一个完全不同的缓存Key。
EngineKey类的源码大家有兴趣可以自己去看一下,其实主要就是重写了equals()和hashCode()方法,保证只有传入EngineKey的所有参数都相同的情况下才认为是同一个EngineKey对象
内存缓存
首先你要知道,默认情况下,Glide自动就是开启内存缓存的。也就是说,当我们使用Glide加载了一张图片之后,这张图片就会被缓存到内存当中,只要在它还没从内存中被清除之前,下次 使用Glide再加载这张图片都会直接从内存当中读取,而不用重新从网络或硬盘上读取了,这样无疑就可以大幅度提升图片的加载效率。比方说你在一个RecyclerView当中反复上下滑动,RecyclerView中只要是Glide加载过的图片都可以直接从内存当中迅速读取并展示出来,从而大大提升了用户体验。
如果你有什么特殊的原因需要禁用内存缓存功能,Glide对此提供了接口:
Glide.with(this)
.load(url)
.skipMemoryCache(true)//禁用掉Glide的内存缓存功能
.into(imageView);
硬盘缓存
禁止Glide对图片进行硬盘缓存而使用了如下代码:
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.NONE)//禁用掉Glide的缓存功能
.into(imageView);
注: 调用diskCacheStrategy()方法并传入DiskCacheStrategy.NONE,就可以禁用掉Glide的硬盘缓存功能了。
这个diskCacheStrategy()方法基本上就是Glide硬盘缓存功能的一切,它可以接收四种参数:
- DiskCacheStrategy.NONE: 表示不缓存任何内容。
- DiskCacheStrategy.SOURCE: 表示只缓存原始图片。
- DiskCacheStrategy.RESULT: 表示只缓存转换过后的图片(默认选项)。
- DiskCacheStrategy.ALL: 表示既缓存原始图片,也缓存转换过后的图片。
高级技巧(处理特殊的url地址)
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(), "");
}
//对url进行处理,去除 ? 和后面的内容
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;
}
}
2. 在Glide中使用
Glide.with(this)
.load(new MyGlideUrl(url))//使用处理后的url
.into(imageView);
注:
我们需要在load()方法中传入这个自定义的MyGlideUrl对象,而不能再像之前那样直接传入url字符串了。不然的话Glide在内部还是会使用原始的GlideUrl类,而不是我们自定义的MyGlideUrl类。
.
.
Glide的回调与监听
1. into()方法
我们都知道into()方法中是可以传入ImageView的。那么into()方法还可以传入别的参数吗?我可以让Glide加载出来的图片不显示到ImageView上吗?答案是肯定的,这就需要用到自定义Target功能。
into()方法还有一个接收Target参数的重载。即使我们传入的参数是ImageView,Glide也会在内部自动构建一个Target对象。而如果我们能够掌握自定义Target技术的话,就可以更加随心所欲地控制Glide的回调了
Glide中Target的继承结构图吧,如下所示:
可以看到,Target的继承结构还是相当复杂的,实现Target接口的子类非常多。不过你不用被这么多的子类所吓到,这些大多数都是Glide已经实现好的具备完整功能的Target子类,如果我们要进行自定义的话,通常只需要在两种Target的基础上去自定义就可以了,一种是SimpleTarget,一种是ViewTarget
.
SimpleTarget的用法示例:
.
SimpleTarget<GlideDrawable> simpleTarget = new SimpleTarget<GlideDrawable>() {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) {
//可以对图片的对象进行相关的处理
imageView.setImageDrawable(resource);
}
};
//在Glide中使用
String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
Glide.with(this)
.load(url)
.into(simpleTarget);
SimpleTarget中的泛型并不一定只能是GlideDrawable,还能直接拿到这张图的Bitmap对象
SimpleTarget<Bitmap> simpleTarget = new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {
//可以对图片的对象进行相关的处理
imageView.setImageBitmap(resource);
}
};
String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
Glide.with(this)
.load(url)
.asBitmap()
.into(simpleTarget);
注:
在onResourceReady()方法中,我们就可以获取到Glide加载出来的图片对象 了,也就是方法参数中传过来的GlideDrawable对象。有了这个对象之后你可以使用它进行任意的逻辑操作,这里我只是简单地把它显示到了ImageView上
ViewTarget的功能更加广泛,它可以作用在任意的View上
.
1. 创建自定义View
public class MyLayout extends LinearLayout {
//创建ViewTarget对象并设置类型
private ViewTarget<MyLayout, GlideDrawable> viewTarget;
public MyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
viewTarget = new ViewTarget<MyLayout, GlideDrawable>(this) {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) {
MyLayout myLayout = getView();//获取自定义View的对象
myLayout.setImageAsBackground(resource);//为自定义View设置布局
}
};
}
//获取ViewTarget的引用
public ViewTarget<MyLayout, GlideDrawable> getTarget() {
return viewTarget;
}
public void setImageAsBackground(GlideDrawable resource) {
this.setBackground(resource);
}
}
2. 在XML中使用自定义View
3. 在Activity中设置Glide
public class MainActivity extends AppCompatActivity {
MyLayout myLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myLayout = (MyLayout) findViewById(R.id.background);
String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
Glide.with(this)
.load(url)
.into(myLayout.getTarget());//通过myLayout获取ViewTarget对象
}
}
.
2. preload()方法
Glide加载图片虽说非常智能,它会自动判断该图片是否已经有缓存了,如果有的话就直接从缓存中读取,没有的话再从网络去下载。
Glide专门给我们提供了预加载的接口,也就是preload()方法,我们只需要直接使用就可以了。
注: preload()方法有两个方法重载,一个不带参数,表示将会加载图片的原始尺寸,另一个可以通过参数指定加载图片的宽和高。
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.preload();
调用了预加载之后,我们以后想再去加载这张图片就会非常快了,因为Glide会直接从缓存当中去读取图片并显示出来,代码如下所示:
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(imageView);
注 : 这里我们仍然需要使用diskCacheStrategy()方法将硬盘缓存策略指定成DiskCacheStrategy.SOURCE,以保证Glide一定会去读取刚才预加载的图片缓存。
.
3. downloadOnly()方法
作用: 表示只会下载图片,而不会对图片进行加载。当图片下载完成之后,我们可以得到图片的存储路径,以便后续进行操作
它有两个方法重载 downloadOnly(int width, int height)和downloadOnly(Y target),一个接收图片的宽度和高度,另一个接收一个泛型对象
如下所示:
- downloadOnly(int width, int height)
downloadOnly(int width, int height)是用于在子线程中下载图片的
new Thread(new Runnable() {
@Override
public void run() {
try {
String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
//获取了一个Application Context
final Context context = getApplicationContext();
FutureTarget<File> target = Glide.with(context)
.load(url)
.downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
//调用FutureTarget的get()方法来获取下载的图片文件
//注意:如果图片还没下载好线程会暂时阻塞住,等下载完成了才会把图片的File对象返回
final File imageFile = target.get();
//切回到主线程
//runOnUiThread()的介绍: https://www.jianshu.com/p/22a2ff3a51af
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(context, imageFile.getPath(), Toast.LENGTH_LONG).show();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
之后我们可以使用如下代码去加载这张图片,图片就会立即显示出来,而不用再去网络上请求了:
String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(imageView);
注意: 这里必须将硬盘缓存策略指定成DiskCacheStrategy.SOURCE或者DiskCacheStrategy.ALL,否则Glide将无法使用我们刚才下载好的图片缓存文件
- downloadOnly(Y target)
downloadOnly(Y target)是用于在主线程中下载图片的
注 :要自己创建一个Target了,而且这次必须直接去实现最顶层的Target接口;并且Target接口的泛型必须指定成File对象
public class DownloadImageTarget implements Target<File> {
private static final String TAG = "图片的地址:";
@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 onLoadCleared(Drawable placeholder) {
}
@Override
public void setRequest(Request request) {
}
@Override
public Request getRequest() {
return null;
}
/*---------------------其中只有两个方法是必须实现的,一个是getSize()方法,一个是onResourceReady()方法-----------------------*/
@Override
public void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) {
//Glide在开始加载图片之前会先计算图片的大小,然后回调到onSizeReady()方法当中,之后才会开始执行图片加载
//下载完成后返回的文件大小(File resource)
Log.d(TAG, resource.getPath());//打印文件的路径
}
@Override
//计算图片大小
public void getSize(SizeReadyCallback cb) {
//直接回调了Target.SIZE_ORIGINAL,表示图片的原始尺寸
cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
}
}
在主线程中使用
String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
Glide.with(this)
.load(url)
.downloadOnly(new DownloadImageTarget());
.
3. listener()方法
作用: 用来监听Glide加载图片的状态
用法如下所示:
String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
Glide.with(this)
.load(url)
.listener(new RequestListener<String, GlideDrawable>() {
@Override
//图片加载失败的回调
public boolean onException(Exception e, String model, Target<GlideDrawable> target,
boolean isFirstResource) {
/*onException()方法中会将失败的Exception参数传进来,这样我们就可以定位具体失败的原因*/
return false;
}
@Override
//图片加载完成的回调
public boolean onResourceReady(GlideDrawable resource, String model,
Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
return false;
}
})
.into(imageView);
注意:
- 返回false就表示这个事件没有被处理,还会继续向下传递给Target的onResourceReady()方法
- 返回true就表示这个事件已经被处理掉了,从而不会再继续向下传递给Target的onResourceReady()方法,图片就不会在ImageView中显示
.
.
自定义模块的基本用法
目的: 可以将更改Glide配置,替换Glide组件等操作独立出来,使得我们能轻松地对Glide的各种配置进行自定义,并且又和Glide的图片加载逻辑没有任何交集,这也是一种低耦合编程方式的体现
自定义模块的使用步骤:
1. 首先需要定义一个我们自己的模块类,并让它实现GlideModule接口,如下所示:
public class MyGlideModule implements GlideModule {
@Override
//用来更改Glide和配置
public void applyOptions(Context context, GlideBuilder builder) {
}
@Override
//替换Glide组件
public void registerComponents(Context context, Glide glide) {
}
}
2. 在AndroidManifest.xml文件当中加入如下配置:
<manifest>
...
<application>
<meta-data
android:name="com.example.glidetest.MyGlideModule"
android:value="GlideModule" />
...
</application>
</manifest>
注:
在标签中加入一个meta-data配置项,其中android:name指定成我们自定义的MyGlideModule的完整路径,android:value必须指定成GlideModule,这个是固定值
3. Glide会在初始化的时候自动调用ManifestParser的parse()方法去解析AndroidManifest.xml文件中的配置
实际上就是将AndroidManifest中所有值为GlideModule的meta-data配置读取出来,并将相应的自定义模块实例化。由于你可以自定义任意多个模块,因此这里我们将会得到一个GlideModule的List集合
使用目的:
- 更改Glide配置
如果想要更改Glide的默认配置,其实只需要在applyOptions()方法中提前将Glide的配置项进行初始化就可以了
Glide有哪些配置项呢?这里我给大家做了一个列举:
- setMemoryCache(): 用于配置Glide的内存缓存策略,默认配置是LruResourceCache。
- setBitmapPool(): 用于配置Glide的Bitmap缓存池,默认配置是LruBitmapPool。
- setDiskCache(): 用于配置Glide的硬盘缓存策略,默认配置是InternalCacheDiskCacheFactory。
- setDiskCacheService(): 用于配置Glide读取缓存中图片的异步执行器,默认配置是FifoPriorityThreadPoolExecutor,也就是先入先出原则。
- setResizeService(): 用于配置Glide读取非缓存中图片的异步执行器,默认配置也是FifoPriorityThreadPoolExecutor。
- setDecodeFormat(): 用于配置Glide加载图片的解码模式,默认配置是RGB_565。
使用场景:
1. 把Glide加载的图片都会缓存到SD卡上
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) {
}
}
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));
}
@Override
public void registerComponents(Context context, Glide glide) {
}
}
注:
InternalCacheDiskCacheFactory和ExternalCacheDiskCacheFactory的默认硬盘缓存大小都是250M。也就是说,如果你的应用缓存的图片总大小超出了250M,那么Glide就会按照DiskLruCache算法的原则来清理缓存的图片
3. 修改Glide的默认加载的图片格式为ARGB_8888的图片格式
public class MyGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
}
@Override
public void registerComponents(Context context, Glide glide) {
}
}
注:
Glide加载图片的默认格式是RGB_565;ARGB_8888格式的图片效果会更加细腻,但是内存开销会比较大。而RGB_565格式的图片则更加节省内存,但是图片效果上会差一些
- 替换Glide组件
替换Glide组件功能需要在自定义模块的registerComponents()方法中加入具体的替换逻辑。
Glide中目前有哪些组件?如下所示:
- 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());
使用场景:
1. 将Glide的HTTP通讯组件替换成OkHttp
- 引入OkHttp的库
dependencies {
implementation 'com.squareup.okhttp3:okhttp:3.9.0'
}
- 创建一个OkHttpFetcher类
public class OkHttpFetcher implements DataFetcher<InputStream> {
private final OkHttpClient client;
private final GlideUrl url;
private InputStream stream;
private ResponseBody responseBody;
private volatile boolean isCancelled;
public OkHttpFetcher(OkHttpClient client, GlideUrl url) {
this.client = client;
this.url = url;
}
@Override
public InputStream loadData(Priority priority) throws Exception {
Request.Builder requestBuilder = new Request.Builder()
.url(url.toStringUrl());
for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
requestBuilder.addHeader("httplib", "OkHttp");
Request request = requestBuilder.build();
if (isCancelled) {
return null;
}
Response response = client.newCall(request).execute();
responseBody = response.body();
if (!response.isSuccessful() || responseBody == null) {
throw new IOException("Request failed with code: " + response.code());
}
stream = ContentLengthInputStream.obtain(responseBody.byteStream(),
responseBody.contentLength());
return stream;
}
@Override
public void cleanup() {
try {
if (stream != null) {
stream.close();
}
if (responseBody != null) {
responseBody.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String getId() {
return url.getCacheKey();
}
@Override
public void cancel() {
isCancelled = true;
}
}
- 创建OkHttpGlideUrlLoader类
public class OkHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
private OkHttpClient okHttpClient;
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private OkHttpClient client;
public Factory() {
}
public Factory(OkHttpClient client) {
this.client = client;
}
private synchronized OkHttpClient getOkHttpClient() {
if (client == null) {
client = new OkHttpClient();
}
return client;
}
@Override
public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
return new OkHttpGlideUrlLoader(getOkHttpClient());
}
@Override
public void teardown() {
}
}
public OkHttpGlideUrlLoader(OkHttpClient client) {
this.okHttpClient = client;
}
@Override
public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
return new OkHttpFetcher(okHttpClient, model);
}
}
- 最后在registerComponents()方法中替换默认的网络请求组件
public class MyGlideModule implements GlideModule {
@Override
//用来更改Glide和配置
public void applyOptions(Context context, GlideBuilder builder) {
}
@Override
//替换Glide组件
public void registerComponents(Context context, Glide glide) {
glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory());
}
}
.
使用OkHttp拦截器实现下载进度监听
我们只要向OkHttp中添加一个自定义的拦截器,就可以在拦截器中捕获到整个HTTP的通讯过程,然后加入一些自己的逻辑来计算下载进度,这样就可以实现下载进度监听的功能了
OkHttp拦截器使用步骤:
1. 创建ProgressInterceptor类实现Interceptor接口
//创建一个没有任何逻辑的空拦截器
public class ProgressInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
return response;
}
}
2. 启用这个拦截器,修改MyGlideModule中的代码
public class MyGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
@Override
public void registerComponents(Context context, Glide glide) {
/*------------使用自定义的拦截器-----------------------*/
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new ProgressInterceptor()); //添加拦截器
OkHttpClient okHttpClient = builder.build(); //创建Okhttp客户端
//OkHttpGlideUrlLoader和OkHttpFetcher注册到Glide当中,将原来的HTTP通讯组件给替换掉
//使用Okhttp3替换Glide默认的网络请求
glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory(okHttpClient));//向Okhttp工厂中添加客户端
}
}
3. 创建ProgressListener接口,用于作为进度监听回调的工具
public interface ProgressListener {
//定义获取进度的抽象方法
void onProgress(int progress);
}
4. 在ProgressInterceptor中加入注册下载监听和取消注册下载监听的方法
//Okhttp的拦截器
public class ProgressInterceptor implements Interceptor {
static final Map<String, ProgressListener> LISTENER_MAP = new HashMap<>();
//注册下载监听
public static void addListener(String url, ProgressListener listener) {
LISTENER_MAP.put(url, listener);
}
//取消注册下载监听
public static void removeListener(String url) {
LISTENER_MAP.remove(url);
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
return response;
}
}
注:
使用了一个Map来保存注册的监听器,Map的键是一个URL地址。之所以要这么做,是因为你可能会使用Glide同时加载很多张图片,而这种情况下,必须要能区分出来每个下载进度的回调到底是对应哪个图片URL地址的
5. 创建ProgressResponseBody类,并让它继承自OkHttp的ResponseBody
这个类当中去编写具体的监听下载进度的逻辑
/*
* 创建Okhttp响应的响应体
* 作用:对响应体进行处理,计算下载的进度
* */
public class ProgressResponseBody extends ResponseBody {
private static final String TAG = "ProgressResponseBody";
private BufferedSource bufferedSource;
private ResponseBody responseBody;
private ProgressListener listener;
/*
* 在ProgressResponseBody的构造方法,该构造方法中要求传入一个url参数和一个ResponseBody参数
* url参数:就是图片的url地址了
* ResponseBody参数:则是OkHttp拦截到的原始的ResponseBody对象
* */
public ProgressResponseBody(String url, ResponseBody responseBody) {
this.responseBody = responseBody;
//调用了ProgressInterceptor中的LISTENER_MAP来去获取该url对应的监听器回调对象,有了这个对象,待会就可以回调计算出来的下载进度
listener = ProgressInterceptor.LISTENER_MAP.get(url);
}
@Override
public MediaType contentType() {
//传入的原始ResponseBody的contentType()方法即可,这相当于一种委托模式
return responseBody.contentType();
}
@Override
public long contentLength() {
//传入的原始ResponseBody的contentLength()方法即可,这相当于一种委托模式
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
/*在source()方法中,我们就必须加入点自己的逻辑了,因为这里要涉及到具体的下载进度计算*/
if (bufferedSource == null) {
/*
1. 先是调用了原始ResponseBody的source()方法来去获取Source对象
2. 接下来将这个Source对象封装到了一个ProgressSource对象当中
3. 最终再用Okio的buffer()方法封装成BufferedSource对象返回
*/
bufferedSource = Okio.buffer(new ProgressSource(responseBody.source()));
}
return bufferedSource;
}
/*
ProgressSource类是一个我们自定义的继承自ForwardingSource的实现类。
注:ForwardingSource也是一个使用委托模式的工具,它不处理任何具体的逻辑,只是负责将传入的原始Source对象进行中转
*/
private class ProgressSource extends ForwardingSource {
long totalBytesRead = 0;
int currentProgress;
ProgressSource(Source source) {
super(source);
}
@Override
//ProgressSource中我们重写了read()方法
public long read(Buffer sink, long byteCount) throws IOException {
/*在read()方法中获取该次读取到的字节数以及下载文件的总字节数,并进行一些简单的数学计算就能算出当前的下载进度了*/
long bytesRead = super.read(sink, byteCount);
long fullLength = responseBody.contentLength();
if (bytesRead == -1) {
totalBytesRead = fullLength;
} else {
totalBytesRead += bytesRead;
}
int progress = (int) (100f * totalBytesRead / fullLength);
//打印下载的进度
Log.d(TAG, "图片加载的进度:" + progress);
//通过接口把图片加载的进度传递给onProgress()方法
//通过前面获取到的回调监听器对象将结果进行回调
if (listener != null && progress != currentProgress) {
listener.onProgress(progress);
}
if (listener != null && totalBytesRead == fullLength) {
listener = null;
}
currentProgress = progress;
return bytesRead;
}
}
}
6. 再修改ProgressInterceptor中的代码
public class ProgressInterceptor implements Interceptor {
static final Map<String, ProgressListener> LISTENER_MAP = new HashMap<>();
public static void addListener(String url, ProgressListener listener) {
LISTENER_MAP.put(url, listener);//向HashMap添加数据
}
public static void removeListener(String url) {
LISTENER_MAP.remove(url);//移除HashMap里的数据
}
@Override
public Response intercept(Chain chain) throws IOException {
//对响应的的结果进行拦截并处理
Request request = chain.request();
Response response = chain.proceed(request);
String url = request.url().toString();
ResponseBody body = response.body();
Response newResponse = response.newBuilder().body(new ProgressResponseBody(url, body)).build();
return newResponse;//返回响应的结果
}
}
7. 在Activity中使用
public class MainActivity extends AppCompatActivity {
String url = "http://guolin.tech/book.png";
ImageView image;
ProgressDialog progressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
image = (ImageView) findViewById(R.id.image);
//创建进度条
progressDialog = new ProgressDialog(this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("加载中");
loadImage();
}
public void loadImage() {
//通过接口调用的形式获取图片的下载进度
ProgressInterceptor.addListener(url, new ProgressListener() {
@Override
public void onProgress(int progress) {
progressDialog.setProgress(progress);//给进度条设置进度
}
});
Glide.with(this)
.load(url)
.into(new GlideDrawableImageViewTarget(ivGlide1) {
@Override
//开始加载图片
public void onLoadStarted(Drawable placeholder) {
super.onLoadStarted(placeholder);
progressDialog.show();
}
@Override
//图片加载完成
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
super.onResourceReady(resource, animation);
progressDialog.dismiss();//隐藏进度条
ProgressInterceptor.removeListener(url);//移除监听器
}
});
}
.
更简单的组件替换
Glide官方给我们提供了非常简便的HTTP组件替换方式。并且除了支持OkHttp3之外,还支持OkHttp2和Volley。
比如使用OkHttp3来作为HTTP通讯组件的配置如下:
dependencies {
implementation 'com.squareup.okhttp3:okhttp:3.9.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0@aar'
}
使用OkHttp2来作为HTTP通讯组件的配置如下:
dependencies {
implementation 'com.github.bumptech.glide:okhttp-integration:1.5.0@aar'
implementation 'com.squareup.okhttp:okhttp:2.7.5'
}
使用Volley来作为HTTP通讯组件的配置如下:
dependencies {
implementation 'com.github.bumptech.glide:volley-integration:1.5.0@aar'
implementation 'com.mcxiaoke.volley:library:1.0.19'
}
.
.
Demo项目地址
.