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".

![demo GIF](https://cloud.githubusercontent.com/assets/2906988/12400279/5f5fe0d2-be1f-11e5-9769-8623e1d81560.gif)

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。

SPDY 不适用于 OkHttp

数据中台POC演示流程

POC-T源码分析

证书固定不适用于 Android 上的 OkHttp

可用于构建数字双胞胎的完整开源软件堆栈?