Glide进阶(一)网络图片下载进度监听

学习自郭霖大神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就可以了。

猜你喜欢

转载自blog.csdn.net/zhang___yong/article/details/80214418