前言
Glide无疑是一款非常优秀的图片加载框架,但是他并没有支持显示图片加载的进度.
那么我们遇到这种需求的时候应该怎么办?自己写一个图片加载框架么?
不,我们肯定是选择自己来扩展这个框架,使得Glide”支持”显示进度
GlideImageView 用来控制start & autoStart , 用来显示 progress , error
OkHttp 用来支持进度的回调
思考
为什么我要选择使用OkHttp来改造那?
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
我们可以看到,ModelLoaderFactory是我们自己传进去的,那么我只要持有这个ModelLoaderFactory不就可以知道进度了么?
追踪源码发现了初始化OkHttpClient的方法,internalClient 是Call.Factory类型的,而OkHttpClient是Call.Factory的实现类.
/**
* Constructor for a new Factory that runs requests using a static singleton client.
*/
public Factory() {
this(getInternalClient());
}
/**
* Constructor for a new Factory that runs requests using given client.
*
* @param client this is typically an instance of {@code OkHttpClient}.
*
* 看到这个有参的构造函数了么?还是Public的..
*/
public Factory(@NonNull Call.Factory client) {
this.client = client;
}
private static Call.Factory getInternalClient() {
if (internalClient == null) {
synchronized (Factory.class) {
if (internalClient == null) {
internalClient = new OkHttpClient();
}
}
}
return internalClient;
}
那么,可以确定替换OkHttpClient的思路是正确的,下面我们来实现一下
OkHttpLoadImage 提供OkHttpClient并添加拦截器
OnProgressListener 由需要显示进度的View实现,提供一个Url,回调progress
OkHttpClient 的实例
HashMap实际上存放的是View自己
ProgressResponseBody 对外提供当前下载的进度
LoadImageView 记录当前url,显示进度
GlideLoaderModule @GlideModule
通过new OkHttpUrlLoader.Factory(OkHttpLoadImage.INSTANCE())替代默认的OkHttpClient
代码实现
OkHttpLoadImage
该类提供一个OkHttpClient的实例,在这个实例中添加一个NetworkInterceptor,这个拦截器提供当前下载的进度
/**
* If you have any questions, you can contact by email {wangzhumoo@gmail.com}
*
* @author 王诛魔 2018/8/9 下午6:10
*/
public final class OkHttpLoadImage {
//一定要记得unRegister,这里存放的是key以及OnProgressListener
//实际上,我们最好采用软引用,这样更加的安全,毕竟我们无法感知View的生命周期
private static HashMap progressListenerMap;
//添加了拦截的OkHttpClient
private OkHttpClient okHttpClient;
static {
progressListenerMap = new HashMap<>();
}
private OkHttpLoadImage() {
initOkHttp();
}
/**
* 构建一个OkHttp的实例
* ProgressResponseBody 是一个比较关键的类,他提供了进度
*/
private void initOkHttp() {
okHttpClient = new OkHttpClient.Builder()
.addNetworkInterceptor(chain -> {
Request request = chain.request();
Response response = chain.proceed(request);
ProgressResponseBody body = new ProgressResponseBody(response, request.url().toString());
//添加监听,使得当前类知道进度
body.addOnResponseListener(responseListener);
return response.newBuilder()
.body(body)
.build();
})
.build();
}
private static class SingletonHolder {
private static OkHttpLoadImage instance = new OkHttpLoadImage();
}
public static OkHttpClient INSTANCE(){
return SingletonHolder.instance.okHttpClient;
}
private static ProgressResponseBody.OnResponseListener responseListener = new ProgressResponseBody.OnResponseListener() {
@Override
public void update(String key, float progress) {
//去寻找这个key,并传回去当前的进度
OnProgressListener listener = progressListenerMap.get(key);
if (listener != null) {
listener.onProgress(progress);
}
}
};
/**
* 注册进度的回调
*/
public static void register(OnProgressListener listener) {
//可能会有多个View注册,但是我们只回调当前这个
progressListenerMap.put(listener.getKey(), listener);
}
/**
* 取消监听
*
* @param listener view
*/
public static void unRegister(OnProgressListener listener) {
progressListenerMap.remove(listener.getKey());
}
/**
* 监听进度的接口
*/
public interface OnProgressListener {
//回调当前的进度给View
void onProgress(float progress);
//获取当前View资源的key(URL即可)
String getKey();
}
}
ProgressResponseBody
这个东西,网上到处都是
public class ProgressResponseBody extends ResponseBody{
//OkHttpLoadImage中传进来的response
private final ResponseBody responseBody;
private String url;
private BufferedSource bufferedSource;
public ProgressResponseBody(Response response,String url) {
this.responseBody = response.body();
this.url = 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(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(BufferedSource source) {
return new ForwardingSource(source) {
long totalBytes = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
// read() returns the number of bytes read, or -1 if this source is exhausted.
totalBytes += bytesRead != -1 ? bytesRead : 0;
if (listener != null) {
//注意这块的 1.0 * , 要不两个long除法会一直是 0
listener.update(url, (float) 1.0 * totalBytes/contentLength());
}
return bytesRead;
}
};
}
private OnResponseListener listener;
public void addOnResponseListener(OnResponseListener listener){
this.listener = listener;
}
public interface OnResponseListener{
void update(String key,float progress);
}
}
LoadImageView/**
* If you have any questions, you can contact by email {wangzhumoo@gmail.com}
*
* @author 王诛魔 2018/8/9 下午5:59
*/
public class LoadImageView extends AppCompatImageView implements OkHttpLoadImage.OnProgressListener{
private Paint mPaint;
private float progress;
private String url;
public LoadImageView(Context context) {
this(context,null);
}
public LoadImageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public LoadImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void setImageUrl(String url){
this.url = url;
//此时开始加载
OkHttpLoadImage.register(this);
ImageLoader.LoaderImg(getContext(),url).into(this);
Glide.with(this).load(url).into(this);
}
/**
* 初始化工具
*/
private void init() {
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(5);
mPaint.setTextSize(50);
}
@SuppressLint("DefaultLocale")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText(String.format("Already : %d",(int)(100 * progress)),100,100,mPaint);
}
public void progress(float progress){
this.progress = progress;
invalidate();
}
@Override
public void onProgress(float progress) {
post(() -> progress(progress));
}
@Override
public String getKey() {
return url;
}
public boolean isLoaded(){
return progress == 1F;
}
}
GlideLoaderModule/**
* If you have any questions, you can contact by email {wangzhumoo@gmail.com}
*
* @author 王诛魔 2017/8/9 下午12:16
*
*/
@GlideModule
public final class GlideLoaderModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
RequestOptions options = new RequestOptions()
.centerCrop()
.placeholder(R.drawable.default_images_load)
.error(R.drawable.default_images_load)
.priority(Priority.HIGH)
.format(DecodeFormat.PREFER_ARGB_8888)
.diskCacheStrategy(DiskCacheStrategy.ALL);
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int memoryCacheSize = maxMemory / 8;
//设置内存缓存大小
File cacheDir = new File(ApiConstants.INSTANCE.getIMAGE_PATH() + File.separator);
if(!cacheDir.exists()){
cacheDir.mkdirs();
}
int diskCacheSize = 1024 * 1024 * 512;
//设置磁盘缓存大小
builder.setMemoryCache(new LruResourceCache(memoryCacheSize));
builder.setDiskCache(new DiskLruCacheFactory(cacheDir.getPath(), "Images", diskCacheSize));
builder.setDefaultRequestOptions(options);
}
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
registry.replace(GlideUrl.class, InputStream.class,
new OkHttpUrlLoader.Factory(OkHttpLoadImage.INSTANCE()));
}
}
使用