在近期使用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系列文章