-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
FastImageOkHttpProgressGlideModule.java
176 lines (153 loc) · 5.94 KB
/
FastImageOkHttpProgressGlideModule.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package com.dylanvann.fastimage;
import android.content.Context;
import androidx.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.LibraryGlideModule;
import com.facebook.react.modules.network.OkHttpClientProvider;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;
@GlideModule
public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
private static final DispatchingProgressListener progressListener = new DispatchingProgressListener();
@Override
public void registerComponents(
@NonNull Context context,
@NonNull Glide glide,
@NonNull Registry registry
) {
OkHttpClient client = OkHttpClientProvider
.getOkHttpClient()
.newBuilder()
.addInterceptor(createInterceptor(progressListener))
.build();
OkHttpUrlLoader.Factory factory = new OkHttpUrlLoader.Factory(client);
registry.replace(GlideUrl.class, InputStream.class, factory);
}
private static Interceptor createInterceptor(final ResponseProgressListener listener) {
return new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
final String key = request.url().toString();
return response
.newBuilder()
.body(new OkHttpProgressResponseBody(key, response.body(), listener))
.build();
}
};
}
static void forget(String key) {
progressListener.forget(key);
}
static void expect(String key, FastImageProgressListener listener) {
progressListener.expect(key, listener);
}
private interface ResponseProgressListener {
void update(String key, long bytesRead, long contentLength);
}
private static class DispatchingProgressListener implements ResponseProgressListener {
private final Map<String, FastImageProgressListener> LISTENERS = new WeakHashMap<>();
private final Map<String, Long> PROGRESSES = new HashMap<>();
void forget(String key) {
LISTENERS.remove(key);
PROGRESSES.remove(key);
}
void expect(String key, FastImageProgressListener listener) {
LISTENERS.put(key, listener);
}
@Override
public void update(final String key, final long bytesRead, final long contentLength) {
final FastImageProgressListener listener = LISTENERS.get(key);
if (listener == null) {
return;
}
if (contentLength <= bytesRead) {
forget(key);
}
if (needsDispatch(key, bytesRead, contentLength, listener.getGranularityPercentage())) {
listener.onProgress(key, bytesRead, contentLength);
}
}
private boolean needsDispatch(String key, long current, long total, float granularity) {
if (granularity == 0 || current == 0 || total == current) {
return true;
}
float percent = 100f * current / total;
long currentProgress = (long) (percent / granularity);
Long lastProgress = PROGRESSES.get(key);
if (lastProgress == null || currentProgress != lastProgress) {
PROGRESSES.put(key, currentProgress);
return true;
} else {
return false;
}
}
}
private static class OkHttpProgressResponseBody extends ResponseBody {
private final String key;
private final ResponseBody responseBody;
private final ResponseProgressListener progressListener;
private BufferedSource bufferedSource;
OkHttpProgressResponseBody(
String key,
ResponseBody responseBody,
ResponseProgressListener progressListener
) {
this.key = key;
this.responseBody = responseBody;
this.progressListener = progressListener;
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(Source source) {
return new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
long fullLength = responseBody.contentLength();
if (bytesRead == -1) {
// this source is exhausted
totalBytesRead = fullLength;
} else {
totalBytesRead += bytesRead;
}
progressListener.update(key, totalBytesRead, fullLength);
return bytesRead;
}
};
}
}
}