Android Glide4.0+圖片加載進(jìn)度監(jiān)聽

在近期使用Glide4.0+版本的時候供搀,需要進(jìn)行圖片加載進(jìn)度的監(jiān)聽隅居,于是查找各種資料實(shí)現(xiàn)該功能,便有了這篇記錄葛虐。

一胎源、準(zhǔn)備工作

筆者Glide為:

 //Glide4.7.1 : https://github.com/bumptech/glide
    implementation('com.github.bumptech.glide:glide:4.7.1') {
        //Glide4,對sdk版本有限制挡闰,需要exclude  support庫
        //https://muyangmin.github.io/glide-docs-cn/doc/download-setup.html
        exclude group: "com.android.support"
    }
    annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'

 //okhttp + 攔截器
    api 'com.squareup.okhttp3:okhttp:3.4.1'
    api 'com.github.bumptech.glide:okhttp3-integration:4.3.1'
二乒融、具體實(shí)現(xiàn)

大致思路:通過Okhttp的攔截器掰盘,監(jiān)聽圖片Url的加載進(jìn)度(需要自己實(shí)現(xiàn)邏輯計(jì)算),并回調(diào)赞季!
1愧捕,步驟1,將OkHttpUrlLoader添加到項(xiàng)目:

/**
 * 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申钩,步驟2次绘,將OkHttpStreamFetcher添加到項(xiàng)目:

/**
 * 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,步驟3撒遣,自定義攔截器和回調(diào)接口:

public interface ProgressListener {
    void onProgress(int progress);
}
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邮偎,步驟4,計(jì)算加載進(jìn)度义黎,并在自定義的攔截器中使用:

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

}

5禾进,在Glide中啟用:

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

三、項(xiàng)目使用

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

    }


}
image.png
加載進(jìn)度廉涕,美女鎮(zhèn)樓.gif

本文僅為記錄泻云,詳細(xì)分析參考:郭霖大神Glide系列文章

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市狐蜕,隨后出現(xiàn)的幾起案子宠纯,更是在濱河造成了極大的恐慌,老刑警劉巖层释,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婆瓜,死亡現(xiàn)場離奇詭異,居然都是意外死亡贡羔,警方通過查閱死者的電腦和手機(jī)廉白,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來治力,“玉大人蒙秒,你說我怎么就攤上這事∠常” “怎么了晕讲?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長马澈。 經(jīng)常有香客問我瓢省,道長,這世上最難降的妖魔是什么痊班? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任勤婚,我火速辦了婚禮,結(jié)果婚禮上涤伐,老公的妹妹穿的比我還像新娘馒胆。我一直安慰自己缨称,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布祝迂。 她就那樣靜靜地躺著睦尽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪型雳。 梳的紋絲不亂的頭發(fā)上当凡,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音纠俭,去河邊找鬼沿量。 笑死,一個胖子當(dāng)著我的面吹牛冤荆,可吹牛的內(nèi)容都是我干的朴则。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼匙赞,長吁一口氣:“原來是場噩夢啊……” “哼佛掖!你這毒婦竟也來了妖碉?” 一聲冷哼從身側(cè)響起涌庭,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎欧宜,沒想到半個月后坐榆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冗茸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年席镀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夏漱。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡豪诲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挂绰,到底是詐尸還是另有隱情屎篱,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布葵蒂,位于F島的核電站交播,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏践付。R本人自食惡果不足惜秦士,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望永高。 院中可真熱鬧隧土,春花似錦提针、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至卖毁,卻和暖如春揖曾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亥啦。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工炭剪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人翔脱。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓奴拦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親届吁。 傳聞我的和親對象是個殘疾皇子错妖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容