java 完整的POC,用于显示通过OkHttp v2在Glide v3中加载的进度
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java 完整的POC,用于显示通过OkHttp v2在Glide v3中加载的进度相关的知识,希望对你有一定的参考价值。
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_margin="4dp"
>
<!-- see interactions with MyProgressTarget.image
scaleType is fitXY because the LevelListDrawable in github_232_progress contains a fixed sized
indeterminate drawable. fitXY stretches everything out so it's screen-wide.
.centerCrop() on the Glide load will load an appropriately resized bitmap, so that won't be stretched. -->
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
tools:src="@drawable/github_232_progress"
tools:ignore="ContentDescription"
/>
<!-- see interactions with MyProgressTarget.text -->
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:padding="4dp"
android:background="#60000000"
android:textColor="#ffffff"
tools:text="progress: ??.? %"
/>
<!-- see interactions with MyProgressTarget.progress -->
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="top|end"
android:max="100"
android:progress="0"
android:progressDrawable="@drawable/github_232_circular"
/>
</FrameLayout>
<!-- Display indeterminate progress at the beginning and end, see setImageLevel calls inside MyProgressTarget -->
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- keep [1, 9999] range first to optimize lookup, see LevelListDrawable.LevelListState#indexOfLevel -->
<item android:drawable="@android:drawable/progress_horizontal"
android:minLevel="1" android:maxLevel="9999" />
<item android:drawable="@android:drawable/progress_indeterminate_horizontal"
android:minLevel="0" android:maxLevel="0" />
<item android:drawable="@android:drawable/progress_indeterminate_horizontal"
android:minLevel="10000" android:maxLevel="10000" />
</level-list>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:innerRadiusRatio="2.3"
android:shape="ring"
android:thickness="3.8sp"
android:useLevel="true">
<solid android:color="#ff0000" />
</shape>
public class WrappingTarget<Z> implements Target<Z> {
protected final Target<Z> target;
public WrappingTarget(Target<Z> target) {
this.target = target;
}
@Override public void getSize(SizeReadyCallback cb) {
target.getSize(cb);
}
@Override public void onLoadStarted(Drawable placeholder) {
target.onLoadStarted(placeholder);
}
@Override public void onLoadFailed(Exception e, Drawable errorDrawable) {
target.onLoadFailed(e, errorDrawable);
}
@Override public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {
target.onResourceReady(resource, glideAnimation);
}
@Override public void onLoadCleared(Drawable placeholder) {
target.onLoadCleared(placeholder);
}
@Override public Request getRequest() {
return target.getRequest();
}
@Override public void setRequest(Request request) {
target.setRequest(request);
}
@Override public void onStart() {
target.onStart();
}
@Override public void onStop() {
target.onStop();
}
@Override public void onDestroy() {
target.onDestroy();
}
}
public class TestFragment extends Fragment {
@Override public @Nullable View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
RecyclerView list = new RecyclerView(container.getContext());
list.setLayoutParams(new MarginLayoutParams(MarginLayoutParams.MATCH_PARENT, MarginLayoutParams.MATCH_PARENT));
list.setLayoutManager(new LinearLayoutManager(container.getContext()));
list.setAdapter(new ProgressAdapter(Arrays.asList(
// few results from https://www.google.com/search?tbm=isch&q=image&tbs=isz:lt,islt:4mp
"http://www.noaanews.noaa.gov/stories/images/goes-12%2Dfirstimage-large081701%2Ejpg",
"http://www.spektyr.com/PrintImages/Cerulean%20Cross%203%20Large.jpg",
"https://cdn.photographylife.com/wp-content/uploads/2014/06/Nikon-D810-Image-Sample-6.jpg",
"https://upload.wikimedia.org/wikipedia/commons/5/5b/Ultraviolet_image_of_the_Cygnus_Loop_Nebula_crop.jpg",
"https://upload.wikimedia.org/wikipedia/commons/c/c5/Polarlicht_2_kmeans_16_large.png",
"https://www.hq.nasa.gov/alsj/a15/M1123519889LCRC_isometric_min-8000_g0dot5_enhanced_labeled.jpg",
"http://oceanexplorer.noaa.gov/explorations/02fire/logs/hirez/octopus_hires.jpg",
"https://upload.wikimedia.org/wikipedia/commons/b/bf/GOES-13_First_Image_jun_22_2006_1730Z.jpg",
"http://www.zastavki.com/pictures/originals/2013/Photoshop_Image_of_the_horse_053857_.jpg",
"http://www.marcogiordanotd.com/blog/wp-content/uploads/2014/01/image9Kcomp.jpg",
"https://cdn.photographylife.com/wp-content/uploads/2014/06/Nikon-D810-Image-Sample-7.jpg",
"https://www.apple.com/v/imac-with-retina/a/images/overview/5k_image.jpg",
"https://www.gimp.org/tutorials/Lite_Quickies/lordofrings_hst_big.jpg",
"http://www.cesbio.ups-tlse.fr/multitemp/wp-content/uploads/2015/07/Mad%C3%A8re-022_0_1.jpg",
"https://www.spacetelescope.org/static/archives/fitsimages/large/slawomir_lipinski_04.jpg",
"https://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg",
"http://4k.com/wp-content/uploads/2014/06/4k-image-tiger-jumping.jpg"
)));
return list;
}
private static class ProgressViewHolder extends ViewHolder {
private final ImageView image;
private final TextView text;
private final ProgressBar progress;
/** Cache target because all the views are tied to this view holder. */
private final ProgressTarget<String, Bitmap> target;
ProgressViewHolder(View root) {
super(root);
image = (ImageView)root.findViewById(R.id.image);
text = (TextView)root.findViewById(R.id.text);
progress = (ProgressBar)root.findViewById(R.id.progress);
target = new MyProgressTarget<>(new BitmapImageViewTarget(image), progress, image, text);
}
void bind(String url) {
target.setModel(url); // update target's cache
Glide
.with(image.getContext())
.load(url)
.asBitmap()
.placeholder(R.drawable.github_232_progress)
.centerCrop() // needs explicit transformation, because we're using a custom target
.into(target)
;
}
}
/**
* Demonstrates 3 different ways of showing the progress:
* <ul>
* <li>Update a full fledged progress bar</li>
* <li>Update a text view to display size/percentage</li>
* <li>Update the placeholder via Drawable.level</li>
* </ul>
* This last one is tricky: the placeholder that Glide sets can be used as a progress drawable
* without any extra Views in the view hierarchy if it supports levels via <code>usesLevel="true"</code>
* or <code>level-list</code>.
*
* @param <Z> automatically match any real Glide target so it can be used flexibly without reimplementing.
*/
private static class MyProgressTarget<Z> extends ProgressTarget<String, Z> {
private final TextView text;
private final ProgressBar progress;
private final ImageView image;
public MyProgressTarget(Target<Z> target, ProgressBar progress, ImageView image, TextView text) {
super(target);
this.progress = progress;
this.image = image;
this.text = text;
}
@Override public float getGranualityPercentage() {
return 0.1f; // this matches the format string for #text below
}
@Override protected void onConnecting() {
progress.setIndeterminate(true);
progress.setVisibility(View.VISIBLE);
image.setImageLevel(0);
text.setVisibility(View.VISIBLE);
text.setText("connecting");
}
@Override protected void onDownloading(long bytesRead, long expectedLength) {
progress.setIndeterminate(false);
progress.setProgress((int)(100 * bytesRead / expectedLength));
image.setImageLevel((int)(10000 * bytesRead / expectedLength));
text.setText(String.format("downloading %.2f/%.2f MB %.1f%%",
bytesRead / 1e6, expectedLength / 1e6, 100f * bytesRead / expectedLength));
}
@Override protected void onDownloaded() {
progress.setIndeterminate(true);
image.setImageLevel(10000);
text.setText("decoding and transforming");
}
@Override protected void onDelivered() {
progress.setVisibility(View.INVISIBLE);
image.setImageLevel(0); // reset ImageView default
text.setVisibility(View.INVISIBLE);
}
}
private static class ProgressAdapter extends Adapter<ProgressViewHolder> {
private final List<String> models;
public ProgressAdapter(List<String> models) {
this.models = models;
}
@Override public ProgressViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.github_232_item, parent, false);
return new ProgressViewHolder(view);
}
@Override public void onBindViewHolder(ProgressViewHolder holder, int position) {
holder.bind(models.get(position));
}
@Override public int getItemCount() {
return models.size();
}
}
}
How to read this gist:
1. Take a look at usage in `TestFragment.java`
that's the usual code for a recycler view and Glide + a custom target for progress
2. If you want to go deeper start with `OkHttpProgressGlideModule.java`
3. The GlideModule provides and interface `UIProgressListener` this POC uses the example implementation of `ProgressTarget`.
4. All the above "glue" makes it so simple to use the progress
5. The xml files are just examples so that we have a full working "app".

Note: decoding and transforming takes a while in the demo, because there's a hardcoded 1000ms delay, to make it more visible. In real uses it's much faster than this.
public abstract class ProgressTarget<T, Z> extends WrappingTarget<Z> implements UIProgressListener {
private T model;
private boolean ignoreProgress = true;
public ProgressTarget(Target<Z> target) {
this(null, target);
}
public ProgressTarget(T model, Target<Z> target) {
super(target);
this.model = model;
}
public final T getModel() {
return model;
}
public final void setModel(T model) {
Glide.clear(this); // indirectly calls cleanup
this.model = model;
}
/**
* Convert a model into an Url string that is used to match up the OkHttp requests. For explicit
* {@link com.bumptech.glide.load.model.GlideUrl GlideUrl} loads this needs to return
* {@link com.bumptech.glide.load.model.GlideUrl#toStringUrl toStringUrl}. For custom models do the same as your
* {@link com.bumptech.glide.load.model.stream.BaseGlideUrlLoader BaseGlideUrlLoader} does.
* @param model return the representation of the given model, DO NOT use {@link #getModel()} inside this method.
* @return a stable Url representation of the model, otherwise the progress reporting won't work
*/
protected String toUrlString(T model) {
return String.valueOf(model);
}
@Override public float getGranualityPercentage() {
return 1.0f;
}
@Override public void onProgress(long bytesRead, long expectedLength) {
if (ignoreProgress) {
return;
}
if (expectedLength == Long.MAX_VALUE) {
onConnecting();
} else if (bytesRead == expectedLength) {
onDownloaded();
} else {
onDownloading(bytesRead, expectedLength);
}
}
/**
* Called when the Glide load has started.
* At this time it is not known if the Glide will even go and use the network to fetch the image.
*/
protected abstract void onConnecting();
/**
* Called when there's any progress on the download; not called when loading from cache.
* At this time we know how many bytes have been transferred through the wire.
*/
protected abstract void onDownloading(long bytesRead, long expectedLength);
/**
* Called when the bytes downloaded reach the length reported by the server; not called when loading from cache.
* At this time it is fairly certain, that Glide either finished reading the stream.
* This means that the image was either already decoded or saved the network stream to cache.
* In the latter case there's more work to do: decode the image from cache and transform.
* These cannot be listened to for progress so it's unsure how fast they'll be, best to show indeterminate progress.
*/
protected abstract void onDownloaded();
/**
* Called when the Glide load has finished either by successfully loading the image or failing to load or cancelled.
* In any case the best is to hide/reset any progress displays.
*/
protected abstract void onDelivered();
private void start() {
OkHttpProgressGlideModule.expect(toUrlString(model), this);
ignoreProgress = false;
onProgress(0, Long.MAX_VALUE);
}
private void cleanup() {
ignoreProgress = true;
T model = this.model; // save in case it gets modified
onDelivered();
OkHttpProgressGlideModule.forget(toUrlString(model));
this.model = null;
}
@Override public void onLoadStarted(Drawable placeholder) {
super.onLoadStarted(placeholder);
start();
}
@Override public void onResourceReady(Z resource, GlideAnimation<? super Z> animation) {
cleanup();
super.onResourceReady(resource, animation);
}
@Override public void onLoadFailed(Exception e, Drawable errorDrawable) {
cleanup();
super.onLoadFailed(e, errorDrawable);
}
@Override public void onLoadCleared(Drawable placeholder) {
cleanup();
super.onLoadCleared(placeholder);
}
}
// TODO add <meta-data android:value="GlideModule" android:name="....OkHttpProgressGlideModule" />
// TODO add <meta-data android:value="GlideModule" tools:node="remove" android:name="com.bumptech.glide.integration.okhttp.OkHttpGlideModule" />
// or not use 'okhttp@aar' in Gradle depdendencies
public class OkHttpProgressGlideModule implements GlideModule {
@Override public void applyOptions(Context context, GlideBuilder builder) { }
@Override public void registerComponents(Context context, Glide glide) {
OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(createInterceptor(new DispatchingProgressListener()));
glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(client));
}
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);
return response.newBuilder()
.body(new OkHttpProgressResponseBody(request.httpUrl(), response.body(), listener))
.build();
}
};
}
public interface UIProgressListener {
void onProgress(long bytesRead, long expectedLength);
/**
* Control how often the listener needs an update. 0% and 100% will always be dispatched.
* @return in percentage (0.2 = call {@link #onProgress} around every 0.2 percent of progress)
*/
float getGranualityPercentage();
}
public static void forget(String url) {
DispatchingProgressListener.forget(url);
}
public static void expect(String url, UIProgressListener listener) {
DispatchingProgressListener.expect(url, listener);
}
private interface ResponseProgressListener {
void update(HttpUrl url, long bytesRead, long contentLength);
}
private static class DispatchingProgressListener implements ResponseProgressListener {
private static final Map<String, UIProgressListener> LISTENERS = new HashMap<>();
private static final Map<String, Long> PROGRESSES = new HashMap<>();
private final Handler handler;
DispatchingProgressListener() {
this.handler = new Handler(Looper.getMainLooper());
}
static void forget(String url) {
LISTENERS.remove(url);
PROGRESSES.remove(url);
}
static void expect(String url, UIProgressListener listener) {
LISTENERS.put(url, listener);
}
@Override public void update(HttpUrl url, final long bytesRead, final long contentLength) {
//System.out.printf("%s: %d/%d = %.2f%%%n", url, bytesRead, contentLength, (100f * bytesRead) / contentLength);
String key = url.toString();
final UIProgressListener listener = LISTENERS.get(key);
if (listener == null) {
return;
}
if (contentLength <= bytesRead) {
forget(key);
}
if (needsDispatch(key, bytesRead, contentLength, listener.getGranualityPercentage())) {
handler.post(new Runnable() {
@Override public void run() {
listener.onProgress(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 HttpUrl url;
private final ResponseBody responseBody;
private final ResponseProgressListener progressListener;
private BufferedSource bufferedSource;
OkHttpProgressResponseBody(HttpUrl url, ResponseBody responseBody,
ResponseProgressListener progressListener) {
this.url = url;
this.responseBody = responseBody;
this.progressListener = progressListener;
}
@Override public MediaType contentType() {
return responseBody.contentType();
}
@Override public long contentLength() throws IOException {
return responseBody.contentLength();
}
@Override public BufferedSource source() throws IOException {
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(url, totalBytesRead, fullLength);
return bytesRead;
}
};
}
}
}
以上是关于java 完整的POC,用于显示通过OkHttp v2在Glide v3中加载的进度的主要内容,如果未能解决你的问题,请参考以下文章
java 一个持久的CookieStore实现,用于在Android中使用HTTPUrlConnection或OkHttp。