ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Glide4实现网络图片加载进度监听

2019-07-09 15:44:09  阅读:204  来源: 互联网

标签:url void 监听 client Override new Glide4 public 加载


前言
我们都知道,使用Glide来加载一张网络上的图片是非常简单的,但是让人头疼的是,我们却无从得知当前图片的下载进度。如果这张图片很小的话,那么问题也不大,反正很快就会被加载出来。但如果这是一张比较大的GIF图,用户耐心等了很久结果图片还没显示出来,这个时候你就会觉得下载进度功能是十分有必要的了。

实现思路如下
我们知道Glide内部HTTP通讯组件的底层实现是基于HttpUrlConnection来进行定制的。但是HttpUrlConnection的可扩展性比较有限,我们在它的基础之上无法实现监听下载进度的功能,因此我们可以将Glide中的HTTP通讯组件替换成OkHttp,然后利用OkHttp强大的拦截器机制,通过向OkHttp中添加一个自定义的拦截器,就可以在拦截器中捕获到整个HTTP的通讯过程,然后加入一些自己的逻辑来计算下载进度,这样就可以实现下载进度监听的功能了。

0,首先添加依赖

dependencies {
    compile 'com.github.bumptech.glide:glide:4.7.1'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
}

添加依赖时如果报错,尝试解决如下,在Project的build.gradle文件中添加仓库:

allprojects {
    repositories {
        jcenter()
        //需要添加的部分
        maven { url "https://maven.google.com"}
    }
}

在添加依赖时,相比于Glide 3,这里多添加了一个compiler的库,这个库是用于生成Generated API的,有了它我们就可以完全使用Glide3的语法来书写代码了,不同的是需要使用GlideApp去调用。
1,将OkHttpUrlLoader添加到项目:

/**
 * A simple model loader for fetching media over http/https using OkHttp.
 */
public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
 
    private final Call.Factory client;
 
    // Public API.
    @SuppressWarnings("WeakerAccess")
    public OkHttpUrlLoader(Call.Factory client) {
        this.client = client;
    }
 
    @Override
    public boolean handles(GlideUrl url) {
        return true;
    }
 
    @Override
    public LoadData<InputStream> buildLoadData(GlideUrl model, int width, int height,
                                               Options options) {
        return new LoadData<>(model, new OkHttpStreamFetcher(client, model));
    }
 
    /**
     * The default factory for {@link OkHttpUrlLoader}s.
     */
    // Public API.
    @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 (Factory.class) {
                    if (internalClient == null) {
                        internalClient = new OkHttpClient();
                    }
                }
            }
            return internalClient;
        }
 
        /**
         * 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 Factory(Call.Factory client) {
            this.client = client;
        }
 
        @Override
        public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
            return new OkHttpUrlLoader(client);
        }
 
        @Override
        public void teardown() {
            // Do nothing, this instance doesn't own the client.
        }
    }
}

2,将OkHttpStreamFetcher添加到项目:

/**
 * Fetches an {@link InputStream} using the okhttp library.
 */
public class OkHttpStreamFetcher implements DataFetcher<InputStream>,
        okhttp3.Callback {
    private static final String TAG = "OkHttpFetcher";
    private final Call.Factory client;
    private final GlideUrl url;
    @SuppressWarnings("WeakerAccess")
    @Synthetic
    InputStream stream;
    @SuppressWarnings("WeakerAccess")
    @Synthetic
    ResponseBody responseBody;
    private volatile Call call;
    private DataCallback<? super InputStream> callback;
 
    // Public API.
    @SuppressWarnings("WeakerAccess")
    public OkHttpStreamFetcher(Call.Factory client, GlideUrl url) {
        this.client = client;
        this.url = url;
    }
 
    @Override
    public void loadData(Priority priority, 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);
        if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O) {
            call.enqueue(this);
        } else {
            try {
                // Calling execute instead of enqueue is a workaround for #2355, where okhttp throws a
                // ClassCastException on O.
                onResponse(call, call.execute());
            } catch (IOException e) {
                onFailure(call, e);
            } catch (ClassCastException e) {
                // It's not clear that this catch is necessary, the error may only occur even on O if
                // enqueue is used.
                onFailure(call, new IOException("Workaround for framework bug on O", e));
            }
        }
    }
 
    @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) throws IOException {
        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;
    }
}

3,自定义拦截器:

public class ProgressInterceptor implements Interceptor{
    public 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);
        String url = request.url().toString();
        ResponseBody body = response.body();
        Response newResponse = response.newBuilder().body(new ProgressResponseBody(url, body)).build();
        return newResponse;
    }
}

4,自定义回调接口:

public interface ProgressListener {
    void onProgress(int progress);
}

5,计算加载进度,并在自定义的拦截器中使用:

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;
        }
    }
 
}

6,自定义一个MyGlideModel,来继承AppGlideModule:

@GlideModule
public class MyGlideModel extends AppGlideModule {
 
    public MyGlideModel() {
        super();
    }
 
    @Override
    public boolean isManifestParsingEnabled() {
        return super.isManifestParsingEnabled();
    }
 
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
        super.applyOptions(context, builder);
    }
 
    @Override
    public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
//        super.registerComponents(context,glide,registry);
 
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .addInterceptor(new ProgressInterceptor())
                .build();
 
        registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(okHttpClient));
    }
}

然后在Android Studio中点击菜单栏Build -> Rebuild Project,GlideApp这个类就会自动生成了。
7,项目中使用:

public class Glide4Activity extends BaseActivity {
    private static final String TAG = "Glide4Activity";
 
    String imgUrl = "http://img5.adesk.com/5ab8ce65e7bce736a953c83c?imageMogr2/thumbnail/!720x1280r/gravity/Center/crop/720x1280";
    @BindView(R.id.glide_iv)
    ImageView mGlideIv;
    ProgressDialog progressDialog;
    @Override
    public int getLayoutId() {
        return R.layout.activity_glide;
    }
 
    @Override
    public void initView() {
        progressDialog = new ProgressDialog(this);
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setMessage("加载中");
        ProgressInterceptor.addListener(imgUrl, new ProgressListener() {
            @Override
            public void onProgress(int progress) {
                Log.d(TAG, "onProgress: " + progress);
                progressDialog.setProgress(progress);
            }
        });
 
        SimpleTarget<Drawable> simpleTarge = new SimpleTarget<Drawable>() {
            @Override
            public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
                progressDialog.dismiss();
                mGlideIv.setImageDrawable(resource);
                Log.d(TAG, "onResourceReady: ");
                ProgressInterceptor.removeListener(imgUrl);
            }
 
            @Override
            public void onStart() {
                super.onStart();
                Log.d(TAG, "onStart: ");
                progressDialog.show();
            }
        };
        GlideApp.with(this)
                .load(imgUrl)
                .diskCacheStrategy(DiskCacheStrategy.NONE)//不使用缓存
                .skipMemoryCache(true)
                .into(simpleTarge);
 
    }
 
}

关于进度条的设置可参考另一篇博文:自定义progress之三种风格的图片加载进度显示样式

标签:url,void,监听,client,Override,new,Glide4,public,加载
来源: https://blog.csdn.net/gpf1320253667/article/details/95049226

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有