Android图片加载框架——Glide(Glide v4)
android中图片加载框架有很多,所有框架最终达到的目都是在Android平台上以极度简单的方式加载和展示图片,如果我们每个都拿来学习和使用的话,这样会极大的浪费我们的时间和经历,所以经过我的一番比较之后,我决定用Glide作为我研究和今后使用的对象。经过一个礼拜的学习和研究之后,我发现Glide是真的强大,通过简单的一行代码就可以展示图片,但是其背后所做的是事却是我们没法想象的,想深入去学习源码的可以去看下郭神的Glide最全解析专栏。本人由于精力和实力的不足,源码就不解析了,郭神的那个系列也写的很详细了,自己看了差不多一个礼拜,可能还是有很多不懂的地方,这里说下看的方法,源码看起来肯定很枯燥,一定要有耐心,不能急,不然可能就看不下去了,一遍看不懂可以多看几遍,来回看慢慢看就会有感觉的。郭神的那个系列第二篇是Glide源码的整个流程,特别长,我的建议可以先跳过去,先看后面几篇关于源码的细节部分,看完再回来看整个流程,这样会好一点。不然第二篇不仅量大而且乱很容易走神,这样一来很可能就看不下去直接放弃了,后面几篇先过下留个大致印象再回过来看整体流程这样会容易一点。还有一个就是可能有人会觉得我会用不就好了,我一个开发人员我没事看懂框架源码干嘛,我又不需要写源码,我要会写我还用这个框架干嘛。这个首先你看了可以增长你技术方面的见识,虽然可能能力增长的也不多,但是最起码开开眼界。其次看懂了源码如果在使用框架过程中有些方面不符合需求,你可以快速在源码的基础上做修改来满足我们的需求。最后还有一点就是看了源码之后可以更好的掌握这个框架的用法,了解的更深,对其内部的实现最起码有个大概的了解,不仅知其然,还知其所以然。好了,前面的话就扯这么多了,在本篇博客中只对Glide(基于Glide v4)用法做个总结,尽量把所有用法总结全,若有没涉及到的欢迎评论补充,不胜感激。
注意:这里我们要添加两个依赖库,其中compiler库是用于生成Generated API的,后面我们会讲到它。
dependencies {
implementation 'com.github.bumptech.glide:glide:4.8.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
}
<uses-permission android:name="android.permission.INTERNET" />
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="加载图片"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />
android.support.constraint.ConstraintLayout>
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageView);
}
public void doClick(View view) {
String url = "http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg";
Glide.with(this).load(url).into(imageView);
}
}
加载图片.gif
可以看到,一张网络上的图片已经被成功下载,并且展示到ImageView上了。而我们到底做了什么?实际上核心的代码就只有这一行而已:
Glide.with(this).load(url).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(this)
.load(url)
//transition(TransitionOptions transitionOptions)
.transition(DrawableTransitionOptions.withCrossFade())
.into(imageView);
- GenericTransitionOptions 通用型
- DrawableTransitionOptions
- BitmapTransitionOptions
RequestOptions options = new RequestOptions()
.dontTransform();
RequestOptions options = new RequestOptions()
.priority(Priority.NORMAL);
RequestOptions options = new RequestOptions()
.placeholder(R.mipmap.ic_launcher);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
RequestOptions options = new RequestOptions()
.placeholder(R.mipmap.ic_launcher)
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
加载图片1.gif
RequestOptions options = new RequestOptions()
.placeholder(R.mipmap.ic_launcher)
.error(R.drawable.error)
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
很简单,这里又串接了一个error()方法就可以指定异常占位图了。
现在你可以将图片的url地址修改成一个不存在的图片地址,或者干脆直接将手机的网络给关了,然后重新运行程序,效果如下图所示:
加载图片2.gif
这样我们就把Glide提供的占位图功能都掌握了。
http://p1.pstatp.com/large/166200019850062839d3
现在重新运行一下代码,效果如下图所示:
加载图片3.gif
Glide.with(this)
.asBitmap()
.load(url)
.apply(options)
.into(imageView);
RequestOptions options = new RequestOptions()
.override(100, 100);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
RequestOptions options = new RequestOptions()
override(Target.SIZE_ORIGINAL);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
RequestOptions options = new RequestOptions()
.skipMemoryCache(true);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
RequestOptions options = new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
- DiskCacheStrategy.NONE: 表示不缓存任何内容。
- DiskCacheStrategy.DATA: 表示只缓存原始图片。
- DiskCacheStrategy.RESOURCE: 表示只缓存转换过后的图片。
- DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
- DiskCacheStrategy.AUTOMATIC: 表示让Glide根据图片资源智能地选择使用哪一种缓存策略(默认选项)。
//清理内存缓存 可以在UI主线程中进行
Glide.get(this).clearMemory();
//清理磁盘缓存 需要在子线程中执行
new Thread(new Runnable() {
@Override
public void run() {
Glide.get(MainActivity.this).clearDiskCache();
}
}).start();
import android.content.Context;
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
import java.io.File;
import java.math.BigDecimal;
public class GlideDiskCacheUtil {
/**
* 获取Glide造成的磁盘缓存大小
* @return DiskCacheSize
*/
public static String getDiskCacheSize(Context context) {
try {
return getFormatSize(getFolderSize(new File(context.getCacheDir() + "/"+ InternalCacheDiskCacheFactory.DEFAULT_DISK_CACHE_DIR)));
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 获取指定文件夹内所有文件大小的和
* @param file file
* @return size
* @throws Exception
*/
private static long getFolderSize(File file) throws Exception {
long size = 0;
try {
File[] fileList = file.listFiles();
for (File aFileList : fileList) {
if (aFileList.isDirectory()) {
size = size + getFolderSize(aFileList);
} else {
size = size + aFileList.length();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return size;
}
/**
* 格式化单位
* @param size size
* @return size
*/
private static String getFormatSize(double size) {
double kiloByte = size / 1024;
if (kiloByte < 1) {
return size + "Byte";
}
double megaByte = kiloByte / 1024;
if (megaByte < 1) {
BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));
return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB";
}
double gigaByte = megaByte / 1024;
if (gigaByte < 1) {
BigDecimal result2 = new BigDecimal(Double.toString(megaByte));
return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB";
}
double teraBytes = gigaByte / 1024;
if (teraBytes < 1) {
BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));
return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB";
}
BigDecimal result4 = new BigDecimal(teraBytes);
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB";
}
}
http://url.com/image.jpg?token=d9caa6e02c990b0a
import com.bumptech.glide.load.model.GlideUrl;
public class MyGlideUrl extends GlideUrl {
private String mUrl;
public MyGlideUrl(String url) {
super(url);
}
@Override
public String getCacheKey() {
//将token部分参数替换为空字符串后返回作为缓存Key
return mUrl.replace(findTokenParam(), "");
}
/**
* 查找token部分参数的方法
* @return token部分参数String
*/
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;
}
}
Glide.with(this)
.load(new MyGlideUrl(url))
.into(imageView);
SimpleTarget<Drawable> simpleTarget = new SimpleTarget<Drawable>() {
@Override
public void onResourceReady(Drawable resource, Transition<? super Drawable> transition) {
imageView.setImageDrawable(resource);
}
};
public void loadImage(View view) {
Glide.with(this)
.load("https://www.baidu.com/img/bd_logo1.png")
.into(simpleTarget);
}
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import com.bumptech.glide.request.target.ViewTarget;
import com.bumptech.glide.request.transition.Transition;
public class MyLayout extends LinearLayout{
private ViewTarget<MyLayout,Drawable> viewTarget;
public MyLayout(Context context) {
this(context,null);
}
public MyLayout(Context context, @Nullable AttributeSet attrs) {
this(context,attrs,0);
}
public MyLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
viewTarget = new ViewTarget<MyLayout,Drawable>(this) {
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
MyLayout myLayout = getView();
myLayout.setBackgroundDrawable(resource);
}
};
}
public ViewTarget<MyLayout,Drawable> getTarget() {
return viewTarget;
}
}
public class MainActivity extends AppCompatActivity {
private MyLayout myLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myLayout = (MyLayout) findViewById(R.id.background);
}
public void loadImage(View view) {
String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
Glide.with(this)
.load(url)
.into(myLayout.getTarget());
}
}
Glide.with(this)
.load("https://www.baidu.com/img/bd_logo1.png")
.preload();
Glide.with(this)
.load("https://www.baidu.com/img/bd_logo1.png")
.into(imageView);
- submit():下载原始尺寸的图片。
- submit(int width, int height):下载指定尺寸的图片。
private void downloadImage() {
new Thread(new Runnable() {
@Override
public void run() {
try {
String url = "http://p1.pstatp.com/large/166200019850062839d3";
final Context context = getApplicationContext();
FutureTarget<File> target = Glide.with(context)
.asFile()
.load(url)
.submit();
final File imageFile = target.get();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, imageFile.getPath(), Toast.LENGTH_LONG).show();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
submit方法可以下载图片并获取到图片缓存路径路径,但是不知道大家有没有这样的需要就是想让图片下载到指定的路径,因为这样我们之后快速使用这部分图片,也方便对这部分图片进行管理,同时不需要受限于Glide的磁盘缓存机制,因为如果由Glide自动管理缓存的话,当下载的图片超过设定的缓存大小,一些比如长时间不使用的图片就会被Glide删除,但是其实这张图片我们之后还是需要使用的,这就很尴尬了,所以我们需要把图片下载到我们指定的位置,由我们自己来进行管理。代码如下:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.Target;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class DownloadImage implements Runnable{
//要下载图片的url
private String url;
//Glide下载所需的Context,最好用ApplicationContext,
//如果再用Activity作为Context了,可能会有Activity销毁了但子线程下载还没执行完这种情况出现。
private Context context;
//指定下载宽度,如果想下载原始宽带指定为0
private int width;
//指定下载高度,如果想下载原始高带指定为0
private int height;
//指定下载位置
private File mFile;
//下载完之后的回调
private ImagedownLoadCallBack callBack;
public interface ImagedownLoadCallBack{
void onDownLoadSuccess(Bitmap bitmap);
void onDownLoadFailed();
}
//用于回调到主线程的Handler,便于在回调回去的方法中执行UI操作
private Handler mHandler;
public DownloadImage(String url, Context context, int width, int height, File mFile, ImagedownLoadCallBack callBack) {
this.url = url;
this.context = context;
this.width = width;
this.height = height;
this.mFile = mFile;
this.callBack = callBack;
mHandler = new Handler(Looper.getMainLooper());
}
@Override
public void run() {
Bitmap bitmap = null;
FileOutputStream fos = null;
try {
if (width==0){
width = Target.SIZE_ORIGINAL;
}
if (height==0){
height = Target.SIZE_ORIGINAL;
}
bitmap = Glide.with(context)
.asBitmap()
.load(url)
.submit(width,height)
.get();
if (bitmap != null){
//上级文件夹不存在则创建
if (!mFile.getParentFile().exists()){
mFile.getParentFile().mkdirs();
}
//文件不存在则创建
if (!mFile.exists()){
mFile.createNewFile();
}
fos = new FileOutputStream(mFile);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bitmap != null && mFile.exists()) {
final Bitmap finalBitmap = bitmap;
mHandler.post(new Runnable() {
@Override
public void run() {
callBack.onDownLoadSuccess(finalBitmap);
}
});
} else {
mHandler.post(new Runnable() {
@Override
public void run() {
callBack.onDownLoadFailed();
}
});
}
}
}
}
public void doClick(View view) {
String url = "http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg";
File mFile = new File(Environment.getExternalStorageDirectory()+File.separator+"Glide","glideDownload.png");
DownloadImage downloadImage = new DownloadImage(url, getApplicationContext(), 600, 600, mFile, new DownloadImage.ImagedownLoadCallBack() {
@Override
public void onDownLoadSuccess(Bitmap bitmap) {
Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
}
@Override
public void onDownLoadFailed() {
Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show();
}
});
new Thread(downloadImage).start();
}
注意:这里submit(width,height)方法指定宽高后下载下来的图片尺寸并不是完全按我们指定尺寸来的,Glide是会做处理的,会保留原始尺寸的宽高比,以缩小比例较小边取我们指定的值的大小。比如上面我们下载的那张图片原始尺寸是1920 * 1080,我们指定尺寸为600 * 600,最后下载下来的图片尺寸是1067 * 600(宽是缩小比例较小边(1080/600<1920/600)取指定值600,长则按长宽比不变进行计算求得,计算过程:长=600 * (1920/1080)=1067)。除此之外我还试了下override()方法和preload()方法指定尺寸Glide是否也做了同样的处理,结果是是的。其实这如果源码读的很细应该是可以看出来的,不过这也确实有点太细节了,毕竟源码篇幅还是很多的,这种细节还是挺难注意到的,如果有精力可以找这部分去精读下,理解会更深。
Glide.with(this)
.load("http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg")
.listener(new RequestListener() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) {
return false;
}
})
.into(imageView);
RequestOptions options = new RequestOptions()
.transforms(...);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
RequestOptions options = new RequestOptions()
.dontTransform();
RequestOptions options = new RequestOptions()
.centerCrop();
RequestOptions options = new RequestOptions()
.fitCenter();
RequestOptions options = new RequestOptions()
.circleCrop();
String url = "http://guolin.tech/book.png";
RequestOptions options = new RequestOptions()
.circleCrop();
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
重新运行一下程序并点击加载图片按钮,效果如下图所示。
加载图片4.gif
dependencies {
implementation 'jp.wasabeef:glide-transformations:3.0.1'
}
String url = "http://guolin.tech/book.png";
RequestOptions options = new RequestOptions()
.transforms(new BlurTransformation(), new GrayscaleTransformation());
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
可以看到,同时执行多种图片变换的时候,只需要将它们都传入到transforms()方法中即可。现在重新运行一下程序,效果如下图所示。
加载图片5.gif
import android.content.Context;
import android.support.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
//更改Glide配置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
}
//替换Glide组件
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
super.registerComponents(context, glide, registry);
}
}
- setMemoryCache()
用于配置Glide的内存缓存策略,默认配置是LruResourceCache。- setBitmapPool()
用于配置Glide的Bitmap缓存池,默认配置是LruBitmapPool。- setDiskCache()
用于配置Glide的硬盘缓存策略,默认配置是InternalCacheDiskCacheFactory。- setDiskCacheExecutor()
用于配置Glide读取缓存中图片的异步执行器,默认配置是FifoPriorityThreadPoolExecutor,也就是先入先出原则。- setResizeService()
用于配置Glide读取非缓存中图片的异步执行器,默认配置也是FifoPriorityThreadPoolExecutor。- setDefaultRequestOptions()
用于配置Glide加载图片的默认请求选项,其中解码模式的配置就包含在里面,默认配置是RGB_565。
//更改Glide配置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context));
}
private static final long DISK_CACHE_SIZE = 500*1024*1024;
//更改Glide配置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context,DISK_CACHE_SIZE));
}
String url = "http://guolin.tech/book.png";
Glide.with(this)
.load(url)
.into(imageView);
运行一下程序,图片加载出来之后我们去找找它的缓存吧。
ExternalPreferredCacheDiskCacheFactory的默认缓存路径是在Android/data/包名/cache/image_manager_disk_cache目录当中,我们使用文件浏览器进入到这个目录,结果如下图所示。
缓存路径文件夹.png
可以看到,这里有两个文件,其中journal文件是DiskLruCache算法的日志文件,这个文件必不可少,且只会有一个。而另外一个文件就是那张缓存的图片了,它的文件名虽然看上去很奇怪,但是我们只需要把这个文件的后缀改成.png,然后用图片浏览器打开,结果就一目了然了,如下图所示。
第一行代码封面图.png
由此证明,我们已经成功将Glide的硬盘缓存路径修改到SD卡上了。
private static final long DISK_CACHE_SIZE = 500*1024*1024;
//更改Glide配置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context,DISK_CACHE_SIZE));
builder.setDefaultRequestOptions(
new RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888)
);
}
dependencies {
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
}
import android.support.annotation.NonNull;
import android.util.Log;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.HttpException;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.util.ContentLengthInputStream;
import com.bumptech.glide.util.Preconditions;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import okhttp3.Call;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class OkHttpFetcher implements DataFetcher<InputStream>, okhttp3.Callback {
private static final String TAG = "OkHttpFetcher";
private final Call.Factory client;
private final GlideUrl url;
private InputStream stream;
private ResponseBody responseBody;
private DataFetcher.DataCallback<? super InputStream> callback;
private volatile Call call;
@SuppressWarnings("WeakerAccess")
public OkHttpFetcher(Call.Factory client, GlideUrl url) {
this.client = client;
this.url = url;
}
@Override
public void loadData(@NonNull Priority priority,
@NonNull final DataCallback<? super InputStream> callback) {
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());
}
Request request = requestBuilder.build();
this.callback = callback;
call = client.newCall(request);
call.enqueue(this);
}
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "OkHttp failed to obtain result", e);
}
callback.onLoadFailed(e);
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
responseBody = response.body();
if (response.isSuccessful()) {
long contentLength = Preconditions.checkNotNull(responseBody).contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
callback.onDataReady(stream);
} else {
callback.onLoadFailed(new HttpException(response.message(), response.code()));
}
}
@Override
public void cleanup() {
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
// Ignored
}
if (responseBody != null) {
responseBody.close();
}
callback = null;
}
@Override
public void cancel() {
Call local = call;
if (local != null) {
local.cancel();
}
}
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
}
import android.support.annotation.NonNull;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import java.io.InputStream;
import okhttp3.Call;
import okhttp3.OkHttpClient;
public class OkHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
private final Call.Factory client;
@SuppressWarnings("WeakerAccess")
public OkHttpGlideUrlLoader(@NonNull Call.Factory client) {
this.client = client;
}
@Override
public boolean handles(@NonNull GlideUrl url) {
return true;
}
@Override
public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
@NonNull Options options) {
return new LoadData<>(model, new OkHttpFetcher(client, model));
}
@SuppressWarnings("WeakerAccess")
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private static volatile Call.Factory internalClient;
private final Call.Factory client;
private static Call.Factory getInternalClient() {
if (internalClient == null) {
synchronized (OkHttpGlideUrlLoader.Factory.class) {
if (internalClient == null) {
internalClient = new OkHttpClient();
}
}
}
return internalClient;
}
public Factory() {
this(getInternalClient());
}
public Factory(@NonNull Call.Factory client) {
this.client = client;
}
@NonNull
@Override
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new OkHttpGlideUrlLoader(client);
}
@Override
public void teardown() {}
}
}
import android.content.Context;
import android.support.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.cache.ExternalPreferredCacheDiskCacheFactory;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.AppGlideModule;
import com.bumptech.glide.request.RequestOptions;
import java.io.InputStream;
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
private static final long DISK_CACHE_SIZE = 500*1024*1024;
//更改Glide配置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context,DISK_CACHE_SIZE));
builder.setDefaultRequestOptions(
new RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888)
);
}
//替换Glide组件
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
super.registerComponents(context, glide, registry);
registry.replace(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory());
}
}
implementation "com.github.bumptech.glide:okhttp3-integration:4.7.1"
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
implementation "com.github.bumptech.glide:volley-integration:4.7.1"
implementation 'com.mcxiaoke.volley:library:1.0.19'
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class ProgressInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
return response;
}
}
import android.content.Context;
import android.support.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.AppGlideModule;
import java.io.InputStream;
import okhttp3.OkHttpClient;
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
private static final long DISK_CACHE_SIZE = 500*1024*1024;
//更改Glide配置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
}
//替换Glide组件
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
super.registerComponents(context, glide, registry);
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new ProgressInterceptor());
OkHttpClient okHttpClient = builder.build();
registry.replace(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory(okHttpClient));
}
}
public interface ProgressListener {
void onProgress(int progress);
}
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
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;
}
}
import android.util.Log;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;
public class ProgressResponseBody extends ResponseBody {
private static final String TAG = "ProgressResponseBody";
private BufferedSource bufferedSource;
private ResponseBody responseBody;
private ProgressListener listener;
public ProgressResponseBody(String url, ResponseBody responseBody) {
this.responseBody = responseBody;
listener = ProgressInterceptor.LISTENER_MAP.get(url);
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(new ProgressSource(responseBody.source()));
}
return bufferedSource;
}
private class ProgressSource extends ForwardingSource {
long totalBytesRead = 0;
int currentProgress;
ProgressSource(Source source) {
super(source);
}
@Override
public long read(Buffer sink, long byteCount) throws IOException {
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, "download progress is " + progress);
if (listener != null && progress != currentProgress) {
listener.onProgress(progress);
}
if (listener != null && totalBytesRead == fullLength) {
listener = null;
}
currentProgress = progress;
return bytesRead;
}
}
}
public class ProgressInterceptor implements Interceptor {
...
@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;
}
}
这里也都是一些OkHttp的简单用法。我们通过Response的newBuilder()方法来创建一个新的Response对象,并把它的body替换成刚才实现的ProgressResponseBody,最终将新的Response对象进行返回,这样计算下载进度的逻辑就能生效了。
代码写到这里,我们就可以来运行一下程序了。现在无论是加载任何网络上的图片,都应该是可以监听到它的下载进度的。运行下程序,图片加载出来后可以看到我们在ProgressResponseBody中加的日志打印出来如下所示:
下载进度日志.png
GlideApp.with(this)
.load(url)
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.override(Target.SIZE_ORIGINAL)
.circleCrop()
.into(imageView);
import com.bumptech.glide.annotation.GlideExtension;
import com.bumptech.glide.annotation.GlideOption;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
@GlideExtension
public class MyGlideExtension {
private MyGlideExtension() {
}
@GlideOption
public static void cacheSource(RequestOptions options) {
options.diskCacheStrategy(DiskCacheStrategy.DATA);
}
}
GlideApp.with(this)
.load(url)
.cacheSource()
.into(imageView);
有了这个强大的功能之后,我们使用Glide就能变得更加灵活了。
到此为止,Glide 4的用法我基本也总结完了。这篇博客也是我写博客以来不论是篇幅和用时都是最长的的一篇,如果你能从头看到这里你对Glide的用法也可以说是深度掌握了,基本上可以满足你项目上的所有要求了。同时你能坚持看下来你也是很棒的哦,加油吧,少年!