我们的项目是一个图片分享社区,所以要对图片处理的一些逻辑进行学习。
学习自郭霖大神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变量每次读完累加,相除就是当前的加载进度。
在自定义拦截器中用这个body替换原body:
public class ProcessInterceptor 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(body)).build();
return newResponse;
}
}
最后在自定义GlideModule中创建okhttpclient并add这个拦截器,传入自定义ModuleLoader即可:
public class MyGlideMoudule 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 ProcessInterceptor());
OkHttpClient okHttpClient = builder.build();
glide.register(GlideUrl.class, InputStream.class,new MyModolLoader.Factory(okHttpClient));
}
}
至此,我们已经成功的获取到了加载进度。但是这肯定不够,实际开发中一般需要把进度通过回调接口与活动或者其他组件交互。这里郭霖大神的方案是找个地方放一个全局static Map<String url,回调接口>,每次加载图片时手动向map中加入回调接口,然后在上面的read中在map里取出回调接口执行回调方法。
感觉这样使用起来有点别扭,想了很久有没有更好的办法,也没想出来。。。。、
二。单次使用自定义加载
上面的方案直接替换了框架对于网络请求的加载,是全局性的。如果我们想要只进行单个的替换也是可以的:
Glide.with.using(StreamModelLoader).load.into;
来看看StreamModelLoader里有什么:
是不是很熟悉,还是找DataFetcher,直接返回我们上面自定义的DataFetcher就可以了。