Glide加入圖片加載進度展示

最近一個項目中需要實現(xiàn)圖片加載時下載進度百分比的展示只洒。如果使用原始的HttpUrlConnection方式來加載圖片的話,實現(xiàn)起來就非常簡單了骚烧,直接讀取InputStream時將進度回調到上層即可。在Glide中有這樣的實現(xiàn)方式么肾请?網(wǎng)上查到的都是使用okhttp方式來加載的示例枚尼,其實用系統(tǒng)的HttpUrlConnection方式來加載也是能夠實現(xiàn)的贴浙。

因為Glide并沒有直接暴露出下載進度這個信息,需要研究下有沒有辦法重寫掉Glide的圖片下載這個流程署恍。很自然的想起Glide有個接口能夠重寫掉加載的一些過程:
<code>com.bumptech.glide.RequestManager#using(com.bumptech.glide.load.model.stream.StreamModelLoader)</code>
看一下文檔中這個方法的定義:

  • Returns a request builder that uses the given {@link com.bumptech.glide.load.model.stream.StreamModelLoader} to* fetch an {@link InputStream} for loading images.

可以通過這個方法重寫Glide獲取圖片的InputStream崎溃,完全代理掉網(wǎng)絡模塊。網(wǎng)上比較多的例子是通過okhttp方式來重寫網(wǎng)絡模塊盯质,但實際用什么網(wǎng)絡模塊是沒有任何關系的袁串。如何實現(xiàn)StreamModelLoader實際上在Glide源碼中也有非常多的例子。因為我實現(xiàn)的是單次請求時的網(wǎng)絡模塊替換呼巷,StreamModelLoader中的cache邏輯完全可以不用去理會囱修,關注核心點的接口就行了,簡單的實現(xiàn)如下:
<pre>
public class ImageUrlLoader implements StreamModelLoader<GlideUrl> {
private InputStreamReadCallback inputStreamReadCallback;

public ImageUrlLoader(InputStreamReadCallback inputStreamReadCallback) {
    this.inputStreamReadCallback = inputStreamReadCallback;
}

@Override
public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
    //這里簡單粗暴王悍,直接返回用于獲得網(wǎng)絡InputStream的DataFetcher破镰。內部才是真正讀網(wǎng)絡的邏輯,所以將讀網(wǎng)絡的回調傳進去
    return new HttpUrlFetcher(model, inputStreamReadCallback);
}

}
</pre>

HttpUrlFetcher在Glide中有默認實現(xiàn)压储,我這里的實現(xiàn)跟它提供的沒有太多區(qū)別啤咽,唯一的區(qū)別就是將回調傳到InputStream中的Wrapper里面去,讓讀取網(wǎng)絡進度時能夠將進度回調出來渠脉。
<pre>
/**

  • A DataFetcher that retrieves an {@link java.io.InputStream} for a Url.
    */
    public class HttpUrlFetcher implements DataFetcher<InputStream> {
    private static final String TAG = "HttpUrlFetcher";
    private static final int MAXIMUM_REDIRECTS = 5;
    private final GlideUrl glideUrl;
    private HttpURLConnection urlConnection;
    private InputStream stream;
    private volatile boolean isCancelled;
    private InputStreamReadCallback inputStreamReadCallback;
// Visible for testing.
HttpUrlFetcher(GlideUrl glideUrl, InputStreamReadCallback inputStreamReadCallback) {
    this.glideUrl = glideUrl;
    this.inputStreamReadCallback = inputStreamReadCallback;
}

@Override
public InputStream loadData(Priority priority) throws Exception {
    return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
}

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
        throws IOException {
    if (redirects >= MAXIMUM_REDIRECTS) {
        throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
    } else {
        // Comparing the URLs using .equals performs additional network I/O and is generally broken.
        // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
        try {
            if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
                throw new IOException("In re-direct loop");
            }
        } catch (URISyntaxException e) {
            // Do nothing, this is best effort.
        }
    }
    urlConnection = (HttpURLConnection) url.openConnection();
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
        urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    urlConnection.setConnectTimeout(5500);
    urlConnection.setReadTimeout(5500);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);

    // Connect explicitly to avoid errors in decoders if connection fails.
    urlConnection.connect();
    if (isCancelled) {
        return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (statusCode / 100 == 2) {
        return getStreamForSuccessfulRequest(urlConnection, inputStreamReadCallback);
    } else if (statusCode / 100 == 3) {
        String redirectUrlString = urlConnection.getHeaderField("Location");
        if (TextUtils.isEmpty(redirectUrlString)) {
            throw new IOException("Received empty or null redirect url");
        }
        URL redirectUrl = new URL(url, redirectUrlString);
        return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
    } else {
        if (statusCode == -1) {
            throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
        }
        throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
    }
}

private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection, InputStreamReadCallback inputStreamReadCallback)
        throws IOException {
    if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
        int contentLength = urlConnection.getContentLength();
        //修改這里宇整,對網(wǎng)絡返回的InputStream做一個包裝,并提供回調的接口
        stream = ContentLengthReadCallbackInputStream.obtain(urlConnection.getInputStream(), contentLength, inputStreamReadCallback);
    } else {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());
        }
        stream = urlConnection.getInputStream();
    }
    return stream;
}

@Override
public void cleanup() {
    if (stream != null) {
        try {
            stream.close();
        } catch (IOException e) {
            // Ignore
        }
    }
    if (urlConnection != null) {
        urlConnection.disconnect();
    }
}

@Override
public String getId() {
    return glideUrl.getCacheKey();
}

@Override
public void cancel() {
    // TODO: we should consider disconnecting the url connection here, but we can't do so directly because cancel is
    // often called on the main thread.
    isCancelled = true;
}

}
</pre>

InputStream的包裝類實現(xiàn)如下:
<pre>
public final class ContentLengthReadCallbackInputStream extends FilterInputStream {
private InputStreamReadCallback readCallback;

private final long contentLength;
private int readSoFar;

public static InputStream obtain(InputStream other, long contentLength, InputStreamReadCallback readCallback) {
    return new ContentLengthReadCallbackInputStream(other, contentLength, readCallback);
}


ContentLengthReadCallbackInputStream(InputStream in, long contentLength, InputStreamReadCallback readCallback) {
    super(in);
    this.readCallback = readCallback;
    this.contentLength = contentLength;
}

@Override
public synchronized int available() throws IOException {
    return (int) Math.max(contentLength - readSoFar, in.available());
}

@Override
public synchronized int read() throws IOException {
    return checkReadSoFarOrThrow(super.read());
}

@Override
public int read(byte[] buffer) throws IOException {
    return read(buffer, 0 /*byteOffset*/, buffer.length /*byteCount*/);
}

@Override
public synchronized int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
    return checkReadSoFarOrThrow(super.read(buffer, byteOffset, byteCount));
}

private int checkReadSoFarOrThrow(int read) throws IOException {
    if (read >= 0) {
        readSoFar += read;
    } else if (contentLength - readSoFar > 0) {
        throw new IOException("Failed to read all expected data"
                + ", expected: " + contentLength
                + ", but read: " + readSoFar);
    }
    if (readCallback != null) {
        //實際網(wǎng)絡讀取數(shù)和總長度在這里回調到外部
        readCallback.onRead(readSoFar, contentLength);
    }
    return read;
}

}
</pre>

在UI層更新進度時注意下芋膘,底層的回調是很快的鳞青,UI更新時做個間隔,例如30MS才更新一次不然會很卡的为朋。

<code>Glide.with(context).using(new ImageUrlLoader(this)).load(new GlideUrl(picUrl))</code>通過這種方式就可以愉快的顯示圖片加載進度了:


加載效果示例
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末臂拓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子习寸,更是在濱河造成了極大的恐慌胶惰,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件霞溪,死亡現(xiàn)場離奇詭異孵滞,居然都是意外死亡,警方通過查閱死者的電腦和手機鸯匹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門坊饶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人殴蓬,你說我怎么就攤上這事匿级。” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵痘绎,是天一觀的道長津函。 經常有香客問我,道長孤页,這世上最難降的妖魔是什么尔苦? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮散庶,結果婚禮上,老公的妹妹穿的比我還像新娘凌净。我一直安慰自己悲龟,他們只是感情好,可當我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布冰寻。 她就那樣靜靜地躺著须教,像睡著了一般。 火紅的嫁衣襯著肌膚如雪斩芭。 梳的紋絲不亂的頭發(fā)上轻腺,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天,我揣著相機與錄音划乖,去河邊找鬼贬养。 笑死,一個胖子當著我的面吹牛琴庵,可吹牛的內容都是我干的误算。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼迷殿,長吁一口氣:“原來是場噩夢啊……” “哼儿礼!你這毒婦竟也來了?” 一聲冷哼從身側響起庆寺,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蚊夫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后懦尝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體知纷,經...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年陵霉,在試婚紗的時候發(fā)現(xiàn)自己被綠了屈扎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡撩匕,死狀恐怖鹰晨,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤模蜡,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布漠趁,位于F島的核電站,受9級特大地震影響忍疾,放射性物質發(fā)生泄漏闯传。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一卤妒、第九天 我趴在偏房一處隱蔽的房頂上張望甥绿。 院中可真熱鬧,春花似錦则披、人聲如沸共缕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽图谷。三九已至,卻和暖如春阱洪,著一層夾襖步出監(jiān)牢的瞬間便贵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工冗荸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留承璃,地道東北人。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓蚌本,卻偏偏與公主長得像绸硕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子魂毁,可洞房花燭夜當晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內容