The decodeFromSource() method is a continuation of the last post.
Let’s look at the first step. The logic in the decodeSource() method isn’t too complicated, either. The fetcher.loadData() method is called at line 14. So what is this fetcher? This is the ImageVideoFetcher object we just got in the onSizeReady() method, and we call its loadData() method as follows:
@Override public ImageVideoWrapper loadData(Priority priority) throws Exception { InputStream is = null; if (streamFetcher ! = null) { try { is = streamFetcher.loadData(priority); } catch (Exception e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Exception fetching input stream, trying ParcelFileDescriptor", e); } if (fileDescriptorFetcher == null) { throw e; } } } ParcelFileDescriptor fileDescriptor = null; if (fileDescriptorFetcher ! = null) { try { fileDescriptor = fileDescriptorFetcher.loadData(priority); } catch (Exception e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Exception fetching ParcelFileDescriptor", e); } if (is == null) { throw e; } } } return new ImageVideoWrapper(is, fileDescriptor); }Copy the code
As you can see, in line 6 of ImageVideoFetcher’s loadData() method, the streamFetcher.loadData() method is called again. What is this streamFetcher? The same HttpUrlFetcher that was passed in when we assembled the ImageVideoFetcher object. HttpUrlFetcher loadData() is called again, so let’s go ahead and see:
public class HttpUrlFetcher implements DataFetcher<InputStream> { ... @Override public InputStream loadData(Priority priority) throws Exception { return loadDataWithRedirects(glideUrl.toURL(), 0 , null , glideUrl.getHeaders()); } private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException { if (redirects >= MAXIMUM_REDIRECTS) { throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!" ); } else { try { if (lastUrl ! = null && url.toURI().equals(lastUrl.toURI())) { throw new IOException("In re-direct loop"); } } catch (URISyntaxException e) { } } urlConnection = connectionFactory.build(url); for (Map.Entry<String, String> headerEntry : headers.entrySet()) { urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue()); } urlConnection.setConnectTimeout(2500); urlConnection.setReadTimeout(2500); urlConnection.setUseCaches(false); urlConnection.setDoInput(true); urlConnection.connect(); if (isCancelled) { return null; } final int statusCode = urlConnection.getResponseCode(); if (statusCode / 100 == 2) { return getStreamForSuccessfulRequest(urlConnection); } else if (statusCode / 100 == 3) { String redirectUrlString = urlConnection.getHeaderField("Location"); if (TextUtils.isEmpty(redirectUrlString)) { throw new IOException("Received empty or null redirect url"); } URL redirectUrl = new URL(url, redirectUrlString); return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers); } else { if (statusCode == -1) { throw new IOException("Unable to retrieve response code from HttpUrlConnection."); } throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage()); } } private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection) throws IOException { if (TextUtils.isEmpty(urlConnection.getContentEncoding())) { int contentLength = urlConnection.getContentLength(); stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength); } else { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding()); } stream = urlConnection.getInputStream(); } return stream; }... }Copy the code
After a layer of trekking, we finally found the network communication code here! A friend of mine told me that the source code for Glide was so complicated that I could not even find where the network request was sent. We are also through a section of a section of code tracking, finally the network request code to find out, it is too not easy.
But don’t get too excited. The final analysis is far from complete. As you can see, the loadData() method just returns an InputStream, and the server returns data that hasn’t even started reading yet. So let’s go back to the loadData() method of ImageVideoFetcher, and in the last line of this method, we create an ImageVideoWrapper object, And pass in the InputStream as an argument.
Then we go back to the decodeSource() method of the DecodeJob, get the ImageVideoWrapper object, and then pass it into the decodeFromSourceData(), To decode this object. The decodeFromSourceData() method looks like this:
private Resource<T> decodeFromSourceData(A data) throws IOException {
final Resource<T> decoded;
if (diskCacheStrategy.cacheSource()) {
decoded = cacheAndDecodeSourceData(data);
} else {
long startTime = LogTime.getLogTime();
decoded = loadProvider.getSourceDecoder().decode(data, width, height);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Decoded from source", startTime);
}
}
return decoded;
}
Copy the code
As you can see, here on line 7 calls the loadProvider. GetSourceDecoder (). The decode () method to decode. LoadProvider is the FixedLoadProvider we just got in the onSizeReady() method, And getSourceDecoder (found) is a GifBitmapWrapperResourceDecoder object, that is to invoke the object’s decode () method to decode images. Then we’ll look at GifBitmapWrapperResourceDecoder code:
public class GifBitmapWrapperResourceDecoder implements ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> { ... @SuppressWarnings("resource") @Override public Resource<GifBitmapWrapper> decode(ImageVideoWrapper source, int width, int height) throws IOException { ByteArrayPool pool = ByteArrayPool.get(); byte[] tempBytes = pool.getBytes(); GifBitmapWrapper wrapper = null; try { wrapper = decode(source, width, height, tempBytes); } finally { pool.releaseBytes(tempBytes); } return wrapper ! = null ? new GifBitmapWrapperResource(wrapper) : null; } private GifBitmapWrapper decode(ImageVideoWrapper source, int width, int height, byte[] bytes) throws IOException { final GifBitmapWrapper result; if (source.getStream() ! = null) { result = decodeStream(source, width, height, bytes); } else { result = decodeBitmapWrapper(source, width, height); } return result; } private GifBitmapWrapper decodeStream(ImageVideoWrapper source, int width, int height, byte[] bytes) throws IOException { InputStream bis = streamFactory.build(source.getStream(), bytes); bis.mark(MARK_LIMIT_BYTES); ImageHeaderParser.ImageType type = parser.parse(bis); bis.reset(); GifBitmapWrapper result = null; if (type == ImageHeaderParser.ImageType.GIF) { result = decodeGifWrapper(bis, width, height); } if (result == null) { ImageVideoWrapper forBitmapDecoder = new ImageVideoWrapper(bis, source.getFileDescriptor()); result = decodeBitmapWrapper(forBitmapDecoder, width, height); } return result; } private GifBitmapWrapper decodeBitmapWrapper(ImageVideoWrapper toDecode, int width, int height) throws IOException { GifBitmapWrapper result = null; Resource<Bitmap> bitmapResource = bitmapDecoder.decode(toDecode, width, height); if (bitmapResource ! = null) { result = new GifBitmapWrapper(bitmapResource, null); } return result; }... }Copy the code
First, in the decode() method, an overload of another decode() method is called. The decodeStream() method is then called at line 23, ready to read the data from the stream returned by the server. The decodeStream() method reads 2 bytes of data from the stream to determine whether the image is a GIF or a normal static image. If it is a GIF, the decodeGifWrapper() method is called to decode it. If it is a normal static map it is decoded by calling the decodeBitmapWrapper() method. Here we will only analyze the implementation flow of normal statics, the GIF implementation is a bit too complicated to analyze in this article.
We then look at the decodeBitmapWrapper() method, where the bitmapdecoder.decode () method is called at line 52. BitmapDecoder is an ImageVideoBitmapDecoder object, so let’s look at its code, as follows:
public class ImageVideoBitmapDecoder implements ResourceDecoder<ImageVideoWrapper, Bitmap> { private final ResourceDecoder<InputStream, Bitmap> streamDecoder; private final ResourceDecoder<ParcelFileDescriptor, Bitmap> fileDescriptorDecoder; public ImageVideoBitmapDecoder(ResourceDecoder<InputStream, Bitmap> streamDecoder, ResourceDecoder<ParcelFileDescriptor, Bitmap> fileDescriptorDecoder) { this.streamDecoder = streamDecoder; this.fileDescriptorDecoder = fileDescriptorDecoder; } @Override public Resource<Bitmap> decode(ImageVideoWrapper source, int width, int height) throws IOException { Resource<Bitmap> result = null; InputStream is = source.getStream(); if (is ! = null) { try { result = streamDecoder.decode(is, width, height); } catch (IOException e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Failed to load image from stream, trying FileDescriptor", e); } } } if (result == null) { ParcelFileDescriptor fileDescriptor = source.getFileDescriptor(); if (fileDescriptor ! = null) { result = fileDescriptorDecoder.decode(fileDescriptor, width, height); } } return result; }... }Copy the code
The code is not complicated; source.getstream () is called at line 14 to get the InputStream returned by the server, and the streamdecoder.decode () method is called at line 17 to decode it. StreamDecode is a StreamBitmapDecoder object.
public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> { ... private final Downsampler downsampler; private BitmapPool bitmapPool; private DecodeFormat decodeFormat; public StreamBitmapDecoder(Downsampler downsampler, BitmapPool bitmapPool, DecodeFormat decodeFormat) { this.downsampler = downsampler; this.bitmapPool = bitmapPool; this.decodeFormat = decodeFormat; } @Override public Resource<Bitmap> decode(InputStream source, int width, int height) { Bitmap bitmap = downsampler.decode(source, bitmapPool, width, height, decodeFormat); return BitmapResource.obtain(bitmap, bitmapPool); }... }Copy the code
As you can see, its decode() method in turn calls Downsampler’s decode() method. Now for the exciting moment, the code for Downsampler looks like this:
public abstract class Downsampler implements BitmapDecoder<InputStream> { ... @Override public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, DecodeFormat decodeFormat) { final ByteArrayPool byteArrayPool = ByteArrayPool.get(); final byte[] bytesForOptions = byteArrayPool.getBytes(); final byte[] bytesForStream = byteArrayPool.getBytes(); final BitmapFactory.Options options = getDefaultOptions(); RecyclableBufferedInputStream bufferedStream = new RecyclableBufferedInputStream( is, bytesForStream); ExceptionCatchingInputStream exceptionStream = ExceptionCatchingInputStream.obtain(bufferedStream); MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream); try { exceptionStream.mark(MARK_POSITION); int orientation = 0; try { orientation = new ImageHeaderParser(exceptionStream).getOrientation(); } catch (IOException e) { if (Log.isLoggable(TAG, Log.WARN)) { Log.w(TAG, "Cannot determine the image orientation from header", e); } } finally { try { exceptionStream.reset(); } catch (IOException e) { if (Log.isLoggable(TAG, Log.WARN)) { Log.w(TAG, "Cannot reset the input stream", e); } } } options.inTempStorage = bytesForOptions; final int[] inDimens = getDimensions(invalidatingStream, bufferedStream, options); final int inWidth = inDimens[0]; final int inHeight = inDimens[1]; final int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation); final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight); final Bitmap downsampled = downsampleWithSize(invalidatingStream, bufferedStream, options, pool, inWidth, inHeight, sampleSize, decodeFormat); final Exception streamException = exceptionStream.getException(); if (streamException ! = null) { throw new RuntimeException(streamException); } Bitmap rotated = null; if (downsampled ! = null) { rotated = TransformationUtils.rotateImageExif(downsampled, pool, orientation); if (! downsampled.equals(rotated) && ! pool.put(downsampled)) { downsampled.recycle(); } } return rotated; } finally { byteArrayPool.releaseBytes(bytesForOptions); byteArrayPool.releaseBytes(bytesForStream); exceptionStream.release(); releaseOptions(options); } } private Bitmap downsampleWithSize(MarkEnforcingInputStream is, RecyclableBufferedInputStream bufferedStream, BitmapFactory.Options options, BitmapPool pool, int inWidth, int inHeight, int sampleSize, DecodeFormat decodeFormat) { Bitmap.Config config = getConfig(is, decodeFormat); options.inSampleSize = sampleSize; options.inPreferredConfig = config; if ((options.inSampleSize == 1 || Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) && shouldUsePool(is)) { int targetWidth = (int) Math.ceil(inWidth / (double) sampleSize); int targetHeight = (int) Math.ceil(inHeight / (double) sampleSize); setInBitmap(options, pool.getDirty(targetWidth, targetHeight, config)); } return decodeStream(is, bufferedStream, options); } /** * A method for getting the dimensions of an image from the given InputStream. * * @param is The InputStream representing the image. * @param options The options to pass to * {@link BitmapFactory#decodeStream(InputStream, android.graphics.Rect, * BitmapFactory.Options)}. * @return an array containing the dimensions of the image in the form {width, height}. */ public int[] getDimensions(MarkEnforcingInputStream is, RecyclableBufferedInputStream bufferedStream, BitmapFactory.Options options) { options.inJustDecodeBounds = true; decodeStream(is, bufferedStream, options); options.inJustDecodeBounds = false; return new int[] { options.outWidth, options.outHeight }; } private static Bitmap decodeStream(MarkEnforcingInputStream is, RecyclableBufferedInputStream bufferedStream, BitmapFactory.Options options) { if (options.inJustDecodeBounds) { is.mark(MARK_POSITION); } else { bufferedStream.fixMarkLimit(); } final Bitmap result = BitmapFactory.decodeStream(is, null, options); try { if (options.inJustDecodeBounds) { is.reset(); } } catch (IOException e) { if (Log.isLoggable(TAG, Log.ERROR)) { Log.e(TAG, "Exception loading inDecodeBounds=" + options.inJustDecodeBounds + " sample=" + options.inSampleSize, e); } } return result; }... }Copy the code
As you can see, the reading of the InputStream returned by the server and the loading of the image are all here. Of course, there is a lot of logic involved here, including compression of images, even rotation, rounding, etc., but we only need to focus on the main line logic for now. The decode() method returns a Bitmap, and the image is already loaded. All that remains is for the Bitmap to be displayed on the screen, and we move on.
Going back to the StreamBitmapDecoder, you’ll see that its decode() method returns a Resource object. What we get from Downsampler is a Bitmap object, so here we call the bitmapResource-.obtain () method at line 18 to wrap the Bitmap object as a Resource object. The code looks like this:
public class BitmapResource implements Resource<Bitmap> { private final Bitmap bitmap; private final BitmapPool bitmapPool; /** * Returns a new {@link BitmapResource} wrapping the given {@link Bitmap} if the Bitmap is non-null or null if the * given Bitmap is null. * * @param bitmap A Bitmap. * @param bitmapPool A non-null {@link BitmapPool}. */ public static BitmapResource obtain(Bitmap bitmap, BitmapPool bitmapPool) { if (bitmap == null) { return null; } else { return new BitmapResource(bitmap, bitmapPool); } } public BitmapResource(Bitmap bitmap, BitmapPool bitmapPool) { if (bitmap == null) { throw new NullPointerException("Bitmap must not be null"); } if (bitmapPool == null) { throw new NullPointerException("BitmapPool must not be null"); } this.bitmap = bitmap; this.bitmapPool = bitmapPool; } @Override public Bitmap get() { return bitmap; } @Override public int getSize() { return Util.getBitmapByteSize(bitmap); } @Override public void recycle() { if (! bitmapPool.put(bitmap)) { bitmap.recycle(); }}}Copy the code
The source code for BitmapResource is also very simple. After wrapping this layer, if I still need to get a Bitmap, I can just call the Resource get() method.
And then we need one layer after another to go back up, and the StreamBitmapDecoder will return the value to the ImageVideoBitmapDecoder, And ImageVideoBitmapDecoder will value is returned to GifBitmapWrapperResourceDecoder decodeBitmapWrapper () method. Since the code is a bit too far apart, I’ll re-paste the code for the decodeBitmapWrapper() method:
private GifBitmapWrapper decodeBitmapWrapper(ImageVideoWrapper toDecode, int width, int height) throws IOException { GifBitmapWrapper result = null; Resource<Bitmap> bitmapResource = bitmapDecoder.decode(toDecode, width, height); if (bitmapResource ! = null) { result = new GifBitmapWrapper(bitmapResource, null); } return result; }Copy the code
As you can see, the decodeBitmapWrapper() method returns a GifBitmapWrapper object. So here, in line 5, the Resource is again wrapped in a GifBitmapWrapper object. The GifBitmapWrapper, as the name suggests, can encapsulate both GIFs and Bitmaps, ensuring that Glide can handle any type of image. Let’s take a look at the GifBitmapWrapper source as follows:
public class GifBitmapWrapper { private final Resource<GifDrawable> gifResource; private final Resource<Bitmap> bitmapResource; public GifBitmapWrapper(Resource<Bitmap> bitmapResource, Resource<GifDrawable> gifResource) { if (bitmapResource ! = null && gifResource ! = null) { throw new IllegalArgumentException("Can only contain either a bitmap resource or a gif resource, not both"); } if (bitmapResource == null && gifResource == null) { throw new IllegalArgumentException("Must contain either a bitmap resource or a gif resource"); } this.bitmapResource = bitmapResource; this.gifResource = gifResource; } /** * Returns the size of the wrapped resource. */ public int getSize() { if (bitmapResource ! = null) { return bitmapResource.getSize(); } else { return gifResource.getSize(); } } /** * Returns the wrapped {@link Bitmap} resource if it exists, or null. */ public Resource<Bitmap> getBitmapResource() { return bitmapResource; } /** * Returns the wrapped {@link GifDrawable} resource if it exists, or null. */ public Resource<GifDrawable> getGifResource() { return gifResource; }}Copy the code
GifResource and bitmapResource are encapsulated separately. I believe there is no need to explain.
Then this GifBitmapWrapper object will always back up, back to the outermost layers GifBitmapWrapperResourceDecoder decode () method, will do it again to encapsulate in it, as shown below:
@Override public Resource<GifBitmapWrapper> decode(ImageVideoWrapper source, int width, int height) throws IOException { ByteArrayPool pool = ByteArrayPool.get(); byte[] tempBytes = pool.getBytes(); GifBitmapWrapper wrapper = null; try { wrapper = decode(source, width, height, tempBytes); } finally { pool.releaseBytes(tempBytes); } return wrapper ! = null ? new GifBitmapWrapperResource(wrapper) : null; }Copy the code
As you can see here, in line 11, GifBitmapWrapper is wrapped into a GifBitmapWrapperResource object, which returns a Resource object. The GifBitmapWrapperResource is similar to the BitmapResource. Both GifBitmapWrapperResource interfaces can use get() to retrieve the contents of the GifBitmapWrapperResource. GifBitmapWrapperResource (GifBitmapWrapperResource)
public class GifBitmapWrapperResource implements Resource<GifBitmapWrapper> {
private final GifBitmapWrapper data;
public GifBitmapWrapperResource(GifBitmapWrapper data) {
if (data == null) {
throw new NullPointerException("Data must not be null");
}
this.data = data;
}
@Override
public GifBitmapWrapper get() {
return data;
}
@Override
public int getSize() {
return data.getSize();
}
@Override
public void recycle() {
Resource<Bitmap> bitmapResource = data.getBitmapResource();
if (bitmapResource != null) {
bitmapResource.recycle();
}
Resource<GifDrawable> gifDataResource = data.getGifResource();
if (gifDataResource != null) {
gifDataResource.recycle();
}
}
}
Copy the code
After this layer of encapsulation, images from the network can be returned as Resource interfaces, and both Bitmap and GIF images can be processed simultaneously.
Now we can go back to the DecodeJob, whose decodeFromSourceData() method returns a Resource object, which is essentially a Resource object. Then continue back up and finally return to the decodeFromSource() method as follows:
public Resource<Z> decodeFromSource() throws Exception {
Resource<T> decoded = decodeSource();
return transformEncodeAndTranscode(decoded);
}
Copy the code
That’s where we followed up to the decodeSource() method and executed a bunch of logic to get to the Resource object. Instead, you’ll notice that the decodeFromSource() method returns a Resource object, so what’s going on here? We need to follow up to transformEncodeAndTranscode () method to look a look, the code is as follows:
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
long startTime = LogTime.getLogTime();
Resource<T> transformed = transform(decoded);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transformed resource from source", startTime);
}
writeTransformedToCache(transformed);
startTime = LogTime.getLogTime();
Resource<Z> result = transcode(transformed);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logWithTimeAndKey("Transcoded transformed from source", startTime);
}
return result;
}
private Resource<Z> transcode(Resource<T> transformed) {
if (transformed == null) {
return null;
}
return transcoder.transcode(transformed);
}
Copy the code
First of all, the transform and cache lines at the beginning of this method, which we’ll learn about later, will be ignored for now. Note that on line 9, a transcode() method is called to convert the Resource object into one.
The transcode() method calls the transcoder transcode() method, so what is the transcoder? In fact this is also a Glide source special to understand one of the reasons is that it USES many objects are initialized before long long, at the time of initialization you may completely didn’t noticed it, because for a simply don’t have to, but when you really need to use has already can’t remember where the object came from.
So let me remind you that in the constructor of the DrawableTypeRequest returned by the load() method in step 2, a FixedLoadProvider is built, Then we three parameters into the FixedLoadProvider, including a GifBitmapWrapperDrawableTranscoder object. This parameter was later picked up in the onSizeReady() method and passed to Engine, which in turn passed to the DecodeJob. As a result, the transcoder here is actually the GifBitmapWrapperDrawableTranscoder object. So let’s have a look at its source:
public class GifBitmapWrapperDrawableTranscoder implements ResourceTranscoder<GifBitmapWrapper, GlideDrawable> {
private final ResourceTranscoder<Bitmap, GlideBitmapDrawable> bitmapDrawableResourceTranscoder;
public GifBitmapWrapperDrawableTranscoder(
ResourceTranscoder<Bitmap, GlideBitmapDrawable> bitmapDrawableResourceTranscoder) {
this.bitmapDrawableResourceTranscoder = bitmapDrawableResourceTranscoder;
}
@Override
public Resource<GlideDrawable> transcode(Resource<GifBitmapWrapper> toTranscode) {
GifBitmapWrapper gifBitmap = toTranscode.get();
Resource<Bitmap> bitmapResource = gifBitmap.getBitmapResource();
final Resource<? extends GlideDrawable> result;
if (bitmapResource != null) {
result = bitmapDrawableResourceTranscoder.transcode(bitmapResource);
} else {
result = gifBitmap.getGifResource();
}
return (Resource<GlideDrawable>) result;
}
...
}
Copy the code
Here let me explain, GifBitmapWrapperDrawableTranscoder core function is used for transcoding. The GifBitmapWrapper cannot be displayed directly on the ImageView, only a Bitmap or Drawable can be displayed on the ImageView. Therefore, the Transcode () method here first retrieves the GifBitmapWrapper object from the Resource and then retrieves the Resource object from the GifBitmapWrapper.
Call getGifResource() to retrieve the image. Glide used to load GIF images is the GifDrawable class. It’s a Drawable object in and of itself. If the Resource is not empty, then you need to transcode the Bitmap to a Drawable object again, because the type consistency between the static and the GIF is logically difficult to handle.
Here in line 15 and conducted a transcoding, object is called GlideBitmapDrawableTranscoder transcode () method, the code is as follows:
public class GlideBitmapDrawableTranscoder implements ResourceTranscoder<Bitmap, GlideBitmapDrawable> { private final Resources resources; private final BitmapPool bitmapPool; public GlideBitmapDrawableTranscoder(Context context) { this(context.getResources(), Glide.get(context).getBitmapPool()); } public GlideBitmapDrawableTranscoder(Resources resources, BitmapPool bitmapPool) { this.resources = resources; this.bitmapPool = bitmapPool; } @Override public Resource<GlideBitmapDrawable> transcode(Resource<Bitmap> toTranscode) { GlideBitmapDrawable drawable = new GlideBitmapDrawable(resources, toTranscode.get()); return new GlideBitmapDrawableResource(drawable, bitmapPool); }... }Copy the code
As you can see, the new GlideBitmapDrawable object encapsulates the Bitmap into it. The GlideBitmapDrawable is then encapsulated again, returning a Resource object.
Now back to GifBitmapWrapperDrawableTranscoder transcode () method, you will find that their type is consistent. Because both statics and giFs belong to their parent Resource objects. So the transcode() method also returns a Resource directly, which is actually the converted Resource.
So going back to the DecodeJob, its decodeFromSource() method gets the Resource object, which of course is the Resource object. And then going back up will take you back to EngineRunnable’s decodeFromSource() method, back to decode(), back to run(). EngineRunnable run() :
@Override public void run() { if (isCancelled) { return; } Exception exception = null; Resource<? > resource = null; try { resource = decode(); } catch (Exception e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Exception decoding", e); } exception = e; } if (isCancelled) { if (resource ! = null) { resource.recycle(); } return; } if (resource == null) { onLoadFailed(exception); } else { onLoadComplete(resource); }}Copy the code
That is, after the decode() method on line 9, we finally have the Resource object, so it’s now up to us to display it. As you can see, the onLoadComplete() method is called at line 25 to indicate that the image load is complete, as shown below:
private void onLoadComplete(Resource resource) {
manager.onResourceReady(resource);
}
Copy the code
The manager is the EngineJob object, so the onResourceReady() method of the EngineJob is actually called as follows:
class EngineJob implements EngineRunnable.EngineRunnableManager {
private static final Handler MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper(), new MainThreadCallback());
private final List<ResourceCallback> cbs = new ArrayList<ResourceCallback>();
...
public void addCallback(ResourceCallback cb) {
Util.assertMainThread();
if (hasResource) {
cb.onResourceReady(engineResource);
} else if (hasException) {
cb.onException(exception);
} else {
cbs.add(cb);
}
}
@Override
public void onResourceReady(final Resource<?> resource) {
this.resource = resource;
MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}
private void handleResultOnMainThread() {
if (isCancelled) {
resource.recycle();
return;
} else if (cbs.isEmpty()) {
throw new IllegalStateException("Received a resource without any callbacks to notify");
}
engineResource = engineResourceFactory.build(resource, isCacheable);
hasResource = true;
engineResource.acquire();
listener.onEngineJobComplete(key, engineResource);
for (ResourceCallback cb : cbs) {
if (!isInIgnoredCallbacks(cb)) {
engineResource.acquire();
cb.onResourceReady(engineResource);
}
}
engineResource.release();
}
@Override
public void onException(final Exception e) {
this.exception = e;
MAIN_THREAD_HANDLER.obtainMessage(MSG_EXCEPTION, this).sendToTarget();
}
private void handleExceptionOnMainThread() {
if (isCancelled) {
return;
} else if (cbs.isEmpty()) {
throw new IllegalStateException("Received an exception without any callbacks to notify");
}
hasException = true;
listener.onEngineJobComplete(key, null);
for (ResourceCallback cb : cbs) {
if (!isInIgnoredCallbacks(cb)) {
cb.onException(exception);
}
}
}
private static class MainThreadCallback implements Handler.Callback {
@Override
public boolean handleMessage(Message message) {
if (MSG_COMPLETE == message.what || MSG_EXCEPTION == message.what) {
EngineJob job = (EngineJob) message.obj;
if (MSG_COMPLETE == message.what) {
job.handleResultOnMainThread();
} else {
job.handleExceptionOnMainThread();
}
return true;
}
return false;
}
}
...
}
Copy the code
As you can see, an MSG_COMPLETE message is sent using Handler in the onResourceReady() method, which is then received in the handleMessage() method of MainThreadCallback. From here, all the logic goes back to the main thread, because soon the UI needs to be updated.
The handleResultOnMainThread() method is then called at line 72, which in turn calls the onResourceReady() methods of all ResourcecallBacks through a loop. So what is this ResourceCallback? The answer is in the addCallback() method, which adds ResourceCallback to the CBS collection. So where is the addCallback() method called? Engine’s load() method looks something like this:
public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { ... public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher, DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder, Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) { ... EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable); DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation, transcoder, diskCacheProvider, diskCacheStrategy, priority); EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority); jobs.put(key, engineJob); engineJob.addCallback(cb); engineJob.start(runnable); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Started new load", startTime, key); } return new LoadStatus(cb, engineJob); }... }Copy the code
This time focus on line 18, see? This is a ResourceCallback registered by the addCallback() method of EngineJob called here. The next question is, who passed the ResourceCallback parameter to the engine.load () method? This brings us back to GenericRequest’s onSizeReady() method, where we see ResourceCallback as the last parameter to the Load () method, So what is the last argument passed in when the load() method is called in the onSizeReady() method? The code looks like this:
public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback,
ResourceCallback {
...
@Override
public void onSizeReady(int width, int height) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;
width = Math.round(sizeMultiplier * width);
height = Math.round(sizeMultiplier * height);
ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
if (dataFetcher == null) {
onException(new Exception("Failed to load model: \'" + model + "\'"));
return;
}
ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));
}
loadedFromMemoryCache = true;
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation,
transcoder, priority, isMemoryCacheable, diskCacheStrategy, this);
loadedFromMemoryCache = resource != null;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}
...
}
Copy the code
Focus on the last parameter, this, on line 29. That’s right, this. GenericRequest implements ResourceCallback, so the EngineJob callback is actually called back to GenericRequest’s onResourceReady() method as follows:
public void onResourceReady(Resource<? > resource) { if (resource == null) { onException(new Exception("Expected to receive a Resource<R> with an object of " + transcodeClass + " inside, but instead got null.")); return; } Object received = resource.get(); if (received == null || ! transcodeClass.isAssignableFrom(received.getClass())) { releaseResource(resource); onException(new Exception("Expected to receive an object of " + transcodeClass + " but instead got " + (received ! = null ? received.getClass() : "") + "{" + received + "}" + " inside Resource{" + resource + "}." + (received ! = null ? "" : " " + "To indicate failure return a null Resource object, " + "rather than a Resource object containing null data.") )); return; } if (! canSetResource()) { releaseResource(resource); status = Status.COMPLETE; return; } onResourceReady(resource, (R) received); } private void onResourceReady(Resource<? > resource, R result) { boolean isFirstResource = isFirstReadyResource(); status = Status.COMPLETE; this.resource = resource; if (requestListener == null || ! requestListener.onResourceReady(result, model, target, loadedFromMemoryCache, isFirstResource)) { GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource); target.onResourceReady(result, animation); } notifyLoadSuccess(); if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: " + (resource.getSize() * TO_MEGABYTE) + " fromCache: " + loadedFromMemoryCache); }}Copy the code
There are two onResourceReady() methods. In the first onResourceReady() method, the resource-.get () method gets the wrapped image object, the GlideBitmapDrawable object. Or a GifDrawable object. This value is then passed to the second onResourceReady() method and the target.onresourceready () method is called at line 36.
So what is this target? The need for a long time, turned up in the third step into () method at first, we analyzed in the last line of the into () method, called the glide buildImageViewTarget () method to construct a Target, And the Target is a GlideDrawableImageViewTarget object.
Then we can go to see the GlideDrawableImageViewTarget source, as shown below:
public class GlideDrawableImageViewTarget extends ImageViewTarget<GlideDrawable> { private static final float SQUARE_RATIO_MARGIN = 0.05 f; private int maxLoopCount; private GlideDrawable resource; public GlideDrawableImageViewTarget(ImageView view) { this(view, GlideDrawable.LOOP_FOREVER); } public GlideDrawableImageViewTarget(ImageView view, int maxLoopCount) { super(view); this.maxLoopCount = maxLoopCount; } @Override public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) { if (! resource.isAnimated()) { float viewRatio = view.getWidth() / (float) view.getHeight(); float drawableRatio = resource.getIntrinsicWidth() / (float) resource.getIntrinsicHeight(); if (Math.abs(viewRatio - 1f) <= SQUARE_RATIO_MARGIN && Math.abs(drawableRatio - 1f) <= SQUARE_RATIO_MARGIN) { resource = new SquaringDrawable(resource, view.getWidth()); } } super.onResourceReady(resource, animation); this.resource = resource; resource.setLoopCount(maxLoopCount); resource.start(); } @Override protected void setResource(GlideDrawable resource) { view.setImageDrawable(resource); } @Override public void onStart() { if (resource ! = null) { resource.start(); } } @Override public void onStop() { if (resource ! = null) { resource.stop(); }}}Copy the code
In GlideDrawableImageViewTarget onResourceReady do some logic to handle () method, including if the GIF images, is called the resource. The start () method to start playing pictures, But I don’t seem to see any logic to display GlideDrawable on ImageView.
Really don’t have, but the parent class there are, here in line 25. Call the super onResourceReady () method, the parent class is ImageViewTarget GlideDrawableImageViewTarget, let’s look at its code:
public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z> implements GlideAnimation.ViewAdapter { ... @Override public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) { if (glideAnimation == null || ! glideAnimation.animate(resource, this)) { setResource(resource); } } protected abstract void setResource(Z resource); }Copy the code
As you can see, the setResource() method is called in ImageViewTarget’s onResourceReady() method, and the setResource() method in ImageViewTarget is an abstract method, The actual implementation is in the subclass.
So how do subclasses implement the setResource() method? To look at some of the GlideDrawableImageViewTarget setResource () method, and yes, call the setImageDrawable () method, and this view is ImageView. At this point in the code, the image is finally displayed.
So, we Glide implementation process of the source analysis, here also finally ended.
It’s a really long article, probably the longest article I’ve written so far. If you haven’t read the source code for Glide before, it’s hard to believe this one line of code:
Glide.with(this).load(url).into(imageView);
Copy the code
Is there such a complicated logic behind it?
But Glide didn’t mean to make the code so complex, because It was so powerful, and the code used only the most basic features of Glide.
Now through “Android picture loading framework analysis (a)” and “Android picture loading framework analysis (two)” two articles, we have mastered the basic usage of Glide, and through reading the source code to understand the Glide general execution process. In the next few articles, I will take you to a detail of Glide source code, learn Glide more advanced use skills, interested friends please continue to read the updated [Android picture loading framework most complete analysis (three), explore Glide cache mechanism]
Pay attention to my technical public account “Guo Lin”, there are high-quality technical articles pushed every day. Pay attention to my entertainment public number, work, study tired when relax yourself.