Android基于Glide v4.x的圖片加載進(jìn)度監(jiān)聽(tīng)
Glide是一款優(yōu)秀的圖片加載框架,簡(jiǎn)單的配置便可以使用起來(lái),為開(kāi)發(fā)者省下了很多的功夫。不過(guò),它沒(méi)有提供其加載圖片進(jìn)度的api,對(duì)于這樣的需求,實(shí)現(xiàn)起來(lái)還真頗費(fèi)一番周折。
嘗試
遇到這個(gè)需求,第一反應(yīng)是網(wǎng)上肯定有人實(shí)現(xiàn)過(guò),不妨借鑒一下別人的經(jīng)驗(yàn)。
Glide加載圖片實(shí)現(xiàn)進(jìn)度條效果
可惜,這個(gè)實(shí)現(xiàn)是基于3.7版本的,4.0版本以上的glide改動(dòng)比較大,using函數(shù)已經(jīng)被移除了
using()
The using() API was removed in Glide 4 to encourage users to register their components once with a AppGlideModule to avoid object re-use. Rather than creating a new ModelLoader each time you load an image, you register it once in an AppGlideModule and let Glide inspect your model (the object you pass to load()) to figure out when to use your registered ModelLoader.
To make sure you only use your ModelLoader for certain models, implement handles() as shown above to inspect each model and return true only if your ModelLoader should be used.
思考
又要用最新的版本又希望給其增加功能,魚(yú)與熊掌不可兼得?“貪新厭舊”的我怎會(huì)輕易放棄。我對(duì)加載圖片進(jìn)度的需求再理了一遍,發(fā)現(xiàn)可以用其他思路簡(jiǎn)化一下:
- 加載圖片分為加載本地圖片和網(wǎng)絡(luò)圖片
- 加載本地圖片速度很快,主要耗時(shí)在圖片解碼的工作,如果圖片已經(jīng)在內(nèi)存中,解碼的步驟也省去了
- 加載網(wǎng)絡(luò)圖片主要時(shí)間耗在下載圖片數(shù)據(jù)過(guò)程中
所以,能監(jiān)聽(tīng)到圖片的下載進(jìn)度就大概是圖片的加載進(jìn)度了。那難道要先下載圖片再交給glide去加載?不用,glide支持整合其他網(wǎng)絡(luò)模塊,例如OkHttp,并且如果我們?cè)敢獾脑?huà),也可以利用其接口實(shí)現(xiàn)自己的網(wǎng)絡(luò)加載模塊。
glide官方倉(cāng)庫(kù)提供了OkHttp的整合模塊,于是乎我去這些代碼了尋找一下突破口。
突破
OkHttp模塊是非常簡(jiǎn)單的,只有4個(gè)文件,并且文件都不長(zhǎng)
首先,用過(guò)glide的都知道,繼承GlideModule的類(lèi)是用于設(shè)置glide的配置信息和加載模塊的,在OkHttpGlideModule里,向系統(tǒng)注冊(cè)了一個(gè)用于加載GlideUrl類(lèi)型的組件,簡(jiǎn)單的可以理解為加載網(wǎng)絡(luò)圖片的組件。
public class OkHttpGlideModule implements GlideModule { public OkHttpGlideModule() { } public void applyOptions(Context context, GlideBuilder builder) { } public void registerComponents(Context context, Glide glide, Registry registry) { registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory()); } }
OkHttpLoader文件也很簡(jiǎn)單,實(shí)現(xiàn)了ModelLoader接口,這里ModelLoader是glide的抽象的資源loader的表示,例如這里,就是說(shuō)加載GlideUrl的model,返回InputStream,即圖片的輸入流。關(guān)于Glide的loadData、model、fetch的詳細(xì)介紹,可以查看 官方文檔
OkHttpLoader的最終目的,也就是返回了一個(gè)LoadData對(duì)象?,F(xiàn)在情況明確了,glide框架就是利用這個(gè)LoadData對(duì)象得到圖片的輸入流,從而下載圖片并經(jīng)過(guò)一系列的解碼,裁剪,緩存等操作,最后加載出來(lái)的。LoadData的參數(shù)有一個(gè)OkHttpStreamFetcher,從名字看來(lái),這里一定就是下載圖片的地方了,我們繼續(xù)看下去。
public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> { private final okhttp3.Call.Factory client; public OkHttpUrlLoader(okhttp3.Call.Factory client) { this.client = client; } public boolean handles(GlideUrl url) { return true; } public LoadData<InputStream> buildLoadData(GlideUrl model, int width, int height, Options options) { //返回LoadData對(duì)象,泛型為InputStream return new LoadData(model, new OkHttpStreamFetcher(this.client, model)); } public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> { private static volatile okhttp3.Call.Factory internalClient; private okhttp3.Call.Factory client; private static okhttp3.Call.Factory getInternalClient() { if(internalClient == null) { Class var0 = OkHttpUrlLoader.Factory.class; synchronized(OkHttpUrlLoader.Factory.class) { if(internalClient == null) { internalClient = new OkHttpClient(); } } } return internalClient; } public Factory() { this(getInternalClient()); } public Factory(okhttp3.Call.Factory client) { this.client = client; } public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) { return new OkHttpUrlLoader(this.client); } public void teardown() { } } }
可以看到,所有的重點(diǎn)都在loadData函數(shù)里面了,用過(guò)OkHttp的同學(xué),有沒(méi)有覺(jué)得好簡(jiǎn)單?哈哈。
這里直接得到圖片的InputStream然后通過(guò)回調(diào)函數(shù)callback.onDataReady()返回了
問(wèn)題來(lái)了,callback的glide框架里傳過(guò)來(lái)的,我們并不能控制,我嘗試找了一下調(diào)用loadData的地方,結(jié)果沒(méi)有找到。怎么辦?看最終的實(shí)現(xiàn)吧。
public class OkHttpStreamFetcher implements DataFetcher<InputStream> { private static final String TAG = "OkHttpFetcher"; private final Factory client; private final GlideUrl url; InputStream stream; ResponseBody responseBody; private volatile Call call; public OkHttpStreamFetcher(Factory client, GlideUrl url) { this.client = client; this.url = url; } public void loadData(Priority priority, final DataCallback<? super InputStream> callback) { Builder requestBuilder = (new Builder()).url(this.url.toStringUrl()); Iterator request = this.url.getHeaders().entrySet().iterator(); while(request.hasNext()) { Entry headerEntry = (Entry)request.next(); String key = (String)headerEntry.getKey(); requestBuilder.addHeader(key, (String)headerEntry.getValue()); } Request request1 = requestBuilder.build(); this.call = this.client.newCall(request1); this.call.enqueue(new Callback() { public void onFailure(Call call, IOException e) { if(Log.isLoggable("OkHttpFetcher", 3)) { Log.d("OkHttpFetcher", "OkHttp failed to obtain result", e); } callback.onLoadFailed(e); } public void onResponse(Call call, Response response) throws IOException { OkHttpStreamFetcher.this.responseBody = response.body(); if(response.isSuccessful()) { long contentLength = OkHttpStreamFetcher.this.responseBody.contentLength(); OkHttpStreamFetcher.this.stream = ContentLengthInputStream.obtain(OkHttpStreamFetcher.this.responseBody.byteStream(), contentLength); callback.onDataReady(OkHttpStreamFetcher.this.stream); } else { callback.onLoadFailed(new HttpException(response.message(), response.code())); } } }); } public void cleanup() { try { if(this.stream != null) { this.stream.close(); } } catch (IOException var2) { ; } if(this.responseBody != null) { this.responseBody.close(); } } public void cancel() { Call local = this.call; if(local != null) { local.cancel(); } } public Class<InputStream> getDataClass() { return InputStream.class; } public DataSource getDataSource() { return DataSource.REMOTE; } }
實(shí)現(xiàn)
在loadData函數(shù)里,有這樣一句代碼
OkHttpStreamFetcher.this.stream = ContentLengthInputStream.obtain(OkHttpStreamFetcher.this.responseBody.byteStream(), contentLength);
ContentLengthInputStream是glide的工具類(lèi),用來(lái)讀取輸入流的,點(diǎn)進(jìn)去看看,發(fā)現(xiàn)有幾個(gè)read方法
public synchronized int read() throws IOException { int value = super.read(); this.checkReadSoFarOrThrow(value >= 0?1:-1); return value; } public int read(byte[] buffer) throws IOException { return this.read(buffer, 0, buffer.length); } public synchronized int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { return this.checkReadSoFarOrThrow(super.read(buffer, byteOffset, byteCount)); }
所以,我的解決方法就是在這里計(jì)算下載進(jìn)度和進(jìn)行進(jìn)度的報(bào)告的,把這個(gè)類(lèi)從源碼里拷貝出來(lái),替換掉OkHttp模塊里面的ContentLengthInputStream,并在這插入自己的下載進(jìn)度邏輯就行了。
具體的代碼實(shí)現(xiàn)就不貼出來(lái)了,寫(xiě)個(gè)大概的思路:
下載圖片的時(shí)候傳入圖片進(jìn)度監(jiān)聽(tīng),用集合容器,例如map保存監(jiān)聽(tīng)的實(shí)例,key為url,下載過(guò)程中計(jì)算下載進(jìn)度后通過(guò)url找到對(duì)應(yīng)的回調(diào)返回進(jìn)度,圖片加載完畢后將回調(diào)從集合了移除(glide提供了圖片加載完畢的回調(diào))。
ok,就這樣,有不合理的地方歡迎指出,又更好更優(yōu)雅的實(shí)現(xiàn)方式請(qǐng)不吝指教。也希望大家多多支持腳本之家。
- 詳解Android?GLide圖片加載常用幾種方法
- Android圖片加載框架Coil的詳細(xì)使用總結(jié)
- Android 官推 kotlin-first 的圖片加載庫(kù)——Coil的使用入門(mén)
- Android編程圖片加載類(lèi)ImageLoader定義與用法實(shí)例分析
- Android ListView實(shí)現(xiàn)ImageLoader圖片加載的方法
- Android中RecyclerView 滑動(dòng)時(shí)圖片加載的優(yōu)化
- Android圖片加載框架Glide的基本用法介紹
- Android圖片加載利器之Picasso基本用法
- 如何在A(yíng)ndroid中高效管理圖片加載
相關(guān)文章
Android編程之非調(diào)用系統(tǒng)界面實(shí)現(xiàn)發(fā)送彩信的方法(MMS)
這篇文章主要介紹了Android編程之非調(diào)用系統(tǒng)界面實(shí)現(xiàn)發(fā)送彩信的方法,涉及Android源碼中的mms的使用技巧,需要的朋友可以參考下2016-01-01一文教你如何使用Databinding寫(xiě)一個(gè)關(guān)注功能
這篇文章主要介紹了一文教你如何使用Databinding寫(xiě)一個(gè)關(guān)注功能,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09flutter 動(dòng)手?jǐn)]一個(gè)城市選擇citypicker功能
在一些項(xiàng)目開(kāi)發(fā)中經(jīng)常會(huì)用到城市選擇器功能,今天小編動(dòng)手?jǐn)]一個(gè)基于flutter 城市選擇citypicker功能,具體實(shí)現(xiàn)過(guò)程跟隨小編一起看看吧2021-08-08ImageView的屬性android:scaleType的作用分析
本篇文章是對(duì)ImageView的屬性android:scaleType的作用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06android實(shí)現(xiàn)注冊(cè)頁(yè)面開(kāi)發(fā)
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)注冊(cè)頁(yè)面開(kāi)發(fā),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04android使用surfaceview+MediaPlayer播放視頻
這篇文章主要為大家詳細(xì)介紹了android使用surfaceview+MediaPlayer播放視頻,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11