学习自郭霖大神glide系列博客,自己经过思考重新整理。
一。全局替换加载策略
首先要知道Glide在实例化时的工作(也就是框架的初始化):
设计模式是builder模式,可以分为两部分,首先builder调用Glide构造器传入所需模块(内存策略,图片解码模式等),然后Glide构造器中对各种类型的图片需求加载进行注册(如File,int,String,GlideUrl等,需要注意的是String类型方法实际调用了GlideUrl的方法)。
相关代码:
遍历Manifest文件寻找所有的GlideModule放入列表,然后遍历执行这些GlideModule的applyOptions(此方法中可以对builder的几个模块使用set方法配置),然后调用构造器生成Glide实例,然后遍历执行这些GlideModule的registerComponents(此方法中可以调用Glide单例注册新的图片类型加载策略,或者替换默认的策略。)
综上,因为我们的目标是对String(实际是GlideUrl)类型加载请求进行改写,所以需要自定义一个GlideModule并且在其registerComponents中对GlideUrl的策略进行替换。另外,因为网络请求要用okhttp而不是默认的httpUrlClient所以要添加okhttp的依赖。
首先自定义MyGlideModule implements GlideModule,然后在Manifest文件中注册:
看看原本Glide构造器中是怎么样注册的:
前两个参数都不用变,我们只需要自定义第三个参数,参照原HttpUrlGlideUrlLoader自定义MyModelLoader imp ModelLoader<GlideUrl,InputStream>。
public class MyModolLoader implements ModelLoader<GlideUrl,InputStream> { private OkHttpClient client; public MyModolLoader(OkHttpClient client) { this.client = client; } @Override public DataFetcher getResourceFetcher(GlideUrl model, int width, int height) { return new MyDataFetcher(client,model); } public static class Factory implements ModelLoaderFactory<GlideUrl,InputStream>{ private OkHttpClient client; public Factory(){ } public Factory(OkHttpClient client) { this.client = client; } @Override public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) { return new MyModolLoader(getOkhttpClient()); } private OkHttpClient getOkhttpClient() { if (client==null){ synchronized (this){ if (client==null){ client=new OkHttpClient(); } } } return client; } @Override public void teardown() { } } }
这里有一个构造器传入了一个okhttpclient,因为后面的操作需要用到我们改造的okhttpclient,用了两次判空做单例。
这个类可以看到并没什么操作,只是将OkHttpClient和GlideUrl交给了一个DataFetcher.
所以自定义MyDataFetcher imp DataFetcher<InputStream>(依然是参照源码写):
public class MyDataFetcher implements DataFetcher<InputStream> { private OkHttpClient client; private InputStream stream; private GlideUrl glideUrl; private volatile boolean isCanceled = false; private ResponseBody responseBody; public MyDataFetcher(OkHttpClient client, GlideUrl glideUrl) { this.client = client; this.glideUrl = glideUrl; } @Override public InputStream loadData(Priority priority) throws Exception { Log.v("tag","loaddata"); Request.Builder requestBuilder = new Request.Builder() .url(glideUrl.toStringUrl()); for (Map.Entry<String, String> headerEntry : glideUrl.getHeaders().entrySet()) { String key = headerEntry.getKey(); requestBuilder.addHeader(key, headerEntry.getValue()); } //requestBuilder.addHeader("httplib", "OkHttp"); Request request = requestBuilder.build(); if (isCanceled) {//在发出请求前进行最后一遍确认 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() { if (stream!=null){ try { stream.close(); } catch (IOException e) { e.printStackTrace(); } } if (responseBody!=null){ responseBody.close(); } } @Override public String getId() { return glideUrl.getCacheKey(); } @Override public void cancel() { isCanceled = true; } }
只是在loadData中使用okhttp进行网络请求操作而已。
到这里,我们已经用自定义的模块替换掉原模块,实现了用okhttp加载图片,接下来要获取下载进度,要通过自定义okhttp的拦截器。在自定义的拦截器中,我们将响应体(ResponseBody)替换为自定义的ResponseBody,获取进度的操作就在这个ResponseBody中。
先来看这个自定义ResponseBody,构造器传入原ResponseBody:
public class ProgressResponseBody extends ResponseBody { private ResponseBody responseBody; private BufferedSource bufferedSource; public ProgressResponseBody(ResponseBody responseBody) { this.responseBody = responseBody; } @Nullable @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{ private long currentBytes;//已读取的数据长度 private long totalLength;//数据总长度 public ProgressSource(Source delegate) { super(delegate); totalLength=responseBody.contentLength(); } @Override public long read(Buffer sink, long byteCount) throws IOException { Log.v("tag","read"); long result=super.read(sink,byteCount);//这一次读取的长度 if (result==-1){ currentBytes=totalLength; } else { currentBytes+=result; } int progress= (int) (currentBytes*100f/totalLength); Log.v("tag","进度"+progress); return result; } } }
主要操作在自定义内部类的read中,read指的是每次读取数据流的过程,返回值为本次成功读取的长度,-1表示读完。所以思路也很清楚了,首先在ProgressSource的构造器中获取数据源总长度,然后维护一个currentBytes变量每次读完累加,相除就是当前的加载进度。
至此,我们已经成功的获取到了加载进度。但是这肯定不够,实际开发中一般需要把进度通过回调接口与活动或者其他组件交互。这里郭霖大神的方案是找个地方放一个全局static Map<String url,回调接口>,每次加载图片时手动向map中加入回调接口,然后在上面的read中在map里取出回调接口执行回调方法。
感觉这样使用起来有点别扭,想了很久有没有更好的办法,也没想出来。。。。、
二。单次使用自定义加载
上面的方案直接替换了框架对于网络请求的加载,是全局性的。如果我们想要只进行单个的替换也是可以的:
Glide.with.using(StreamModelLoader).load.into;
来看看StreamModelLoader里有什么:
是不是很熟悉,还是找DataFetcher,直接返回我们上面自定义的DataFetcher就可以了。