Glide源码分析之三 into()下篇

作者: 是y狗阿 | 来源:发表于2018-12-05 19:46 被阅读6次
    相关文章

    Glide源码分析之一
    Glide源码分析之二
    Glide源码分析之三

    继续我们上篇讲的,这里构建了一个EngineJob,它的主要作用就是用来开启线程的,为后面的异步加载图片做准备。接下来第46行创建了一个DecodeJob对象,从名字上来看,它好像是用来对图片进行解码的,但实际上它的任务十分繁重,待会我们就知道了。继续往下看,创建了一个EngineRunnable对象,并且调用了EngineJob的start()方法来运行EngineRunnable对象,这实际上就是让EngineRunnable的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);
            }
        }
    

    这个方法中的代码并不多,但我们仍然还是要抓重点。调用了一个decode()方法,并且这个方法返回了一个Resource对象。看上去所有的逻辑应该都在这个decode()方法执行的了,那我们跟进去看看:

    private Resource<?> decode() throws Exception {
            if (isDecodingFromCache()) {
                return decodeFromCache();
            } else {
                return decodeFromSource();
            }
        }
    

    decode()方法中又分了两种情况,从缓存当中去decode图片的话就会执行decodeFromCache(),否则的话就执行decodeFromSource()。先来看decodeFromSource()方法的代码吧,如下所示:

    private Resource<?> decodeFromSource() throws Exception {
            return decodeJob.decodeFromSource();
        }
        
        
          public Resource<Z> decodeFromSource() throws Exception {
            Resource<T> decoded = decodeSource();
            return transformEncodeAndTranscode(decoded);
        }
        
        
        private Resource<T> decodeSource() throws Exception {
            Resource<T> decoded = null;
            try {
                long startTime = LogTime.getLogTime();
                final A data = fetcher.loadData(priority);
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    logWithTimeAndKey("Fetched data", startTime);
                }
                if (isCancelled) {
                    return null;
                }
                decoded = decodeFromSourceData(data);
            } finally {
                fetcher.cleanup();
            }
            return decoded;
        }
        
         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;
        }
    

    我们先来看一下decodeFromSource()方法,其实它的工作分为两部,第一步是调用decodeSource()方法来获得一个Resource对象,第二步是调用transformEncodeAndTranscode()方法来处理这个Resource对象。

    我们先来看第一步,decodeSource()方法中的逻辑也并不复杂,首先调用了fetcher.loadData()方法。那么这个fetcher是什么呢?其实就是刚才在onSizeReady()方法中得到的ImageVideoFetcher对象,这里调用它的loadData()方法,代码如下所示:

     @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);
            }
    

    可以看到,在ImageVideoFetcher的loadData()方法中又去调用了streamFetcher.loadData()方法,那么这个streamFetcher是什么呢?自然就是刚才在组装ImageVideoFetcher对象时传进来的HttpUrlFetcher了。因此这里又会去调用HttpUrlFetcher的loadData()方法,那么我们继续跟进去瞧一瞧:

     @Override
        public InputStream loadData(Priority priority) throws Exception {
            return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, 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 {
                // Comparing the URLs using .equals performs additional network I/O and is generally broken.
                // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
                try {
                    if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
                        throw new IOException("In re-direct loop");
                    }
                } catch (URISyntaxException e) {
                    // Do nothing, this is best effort.
                }
            }
            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);
    
            // Connect explicitly to avoid errors in decoders if connection fails.
            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());
            }
        }
    

    可以看到,loadData()方法只是返回了一个InputStream,服务器返回的数据连读都还没开始读呢。所以我们还是要静下心来继续分析,回到刚才ImageVideoFetcher的loadData()方法中,在这个方法的最后一行,创建了一个ImageVideoWrapper对象,并把刚才得到的InputStream作为参数传了进去。

    然后我们回到再上一层,也就是DecodeJob的decodeSource()方法当中,在得到了这个ImageVideoWrapper对象之后,紧接着又将这个对象传入到了decodeFromSourceData()当中,来去解码这个对象。decodeFromSourceData()方法的代码如下所示:

    private Resource<T> decodeFromSourceData(A data) throws IOException {
            final Resource<T> decoded;
            if (diskCacheStrategy.cacheSource()) {
                decoded = cacheAndDecodeSourceData(data);
            } else {
                long startTime = LogTime.getLogTime();
                //而getSourceDecoder()得到的则是一个GifBitmapWrapperResourceDecoder对象
                decoded = loadProvider.getSourceDecoder().decode(data, width, height);
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    logWithTimeAndKey("Decoded from source", startTime);
                }
            }
            return decoded;
        }
    

    可以看到,这里调用了loadProvider.getSourceDecoder().decode()方法来进行解码。loadProvider就是刚才在onSizeReady()方法中得到的FixedLoadProvider,而getSourceDecoder()得到的则是一个GifBitmapWrapperResourceDecoder对象,也就是要调用这个对象的decode()方法来对图片进行解码。

    那么我们来看下GifBitmapWrapperResourceDecoder的代码:

    @SuppressWarnings("resource")
        // @see ResourceDecoder.decode
        @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);
            //输入流总是在调用 mark 之后记录所有读取的字节,并时刻准备在调用方法 reset 时(无论何时),再次提供这些相同的字节。但是,如果在调用 reset 之前可以从流中读取多于 readlimit 的字节,则不需要该流记录任何数据。 
            // readlimit - 在标记位置失效前可以读取字节的最大限制。
            bis.mark(MARK_LIMIT_BYTES);
            
            //大家注意看这一行!!!!!!
            //decodeStream()方法中会先从流中读取2个字节的数据,来判断这张图是GIF图还是普通的静图,
            ImageHeaderParser.ImageType type = parser.parse(bis);
            
            
            //将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。 
            bis.reset();
    
            GifBitmapWrapper result = null;
            if (type == ImageHeaderParser.ImageType.GIF) {
                result = decodeGifWrapper(bis, width, height);
            }
            // Decoding the gif may fail even if the type matches.
            if (result == null) {
                // We can only reset the buffered InputStream, so to start from the beginning of the stream, we need to
                // pass in a new source containing the buffered stream rather than the original stream.
                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;
        }
    
        // Visible for testing.
        static class ImageTypeParser {
            public ImageHeaderParser.ImageType parse(InputStream is) throws IOException {
                return new ImageHeaderParser(is).getType();
            }
        }
        
        
        //就是从这里判断出他是2字节的....
        public ImageType getType() throws IOException {
            int firstTwoBytes = streamReader.getUInt16();
    
            // JPEG.
            if (firstTwoBytes == EXIF_MAGIC_NUMBER) {
                return JPEG;
            }
    
            final int firstFourBytes = firstTwoBytes << 16 & 0xFFFF0000 | streamReader.getUInt16() & 0xFFFF;
            // PNG.
            if (firstFourBytes == PNG_HEADER) {
                // See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha-color-type
                streamReader.skip(25 - 4);
                int alpha = streamReader.getByte();
                // A RGB indexed PNG can also have transparency. Better safe than sorry!
                return alpha >= 3 ? PNG_A : PNG;
            }
    
            // GIF from first 3 bytes.
            if (firstFourBytes >> 8 == GIF_HEADER) {
                return GIF;
            }
    
            return UNKNOWN;
        }
        
         public int getUInt16() throws IOException {
                return  (is.read() << 8 & 0xFF00) | (is.read() & 0xFF);
            }
    

    首先,在decode()方法中,又去调用了另外一个decode()方法的重载。然后调用了decodeStream()方法,准备从服务器返回的流当中读取数据。decodeStream()方法中会先从流中读取2个字节的数据,来判断这张图是GIF图还是普通的静图,如果是GIF图就调用decodeGifWrapper()方法来进行解码,如果是普通的静图就用调用decodeBitmapWrapper()方法来进行解码。这里我们只分析普通静图的实现流程,GIF图的实现有点过于复杂了,无法在本篇文章当中分析。

     @SuppressWarnings("resource")
        // @see ResourceDecoder.decode
        @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;
        }
    

    代码并不复杂,在第14行先调用了source.getStream()来获取到服务器返回的InputStream,然后在第17行调用streamDecoder.decode()方法进行解码。streamDecode是一个StreamBitmapDecoder对象,那么我们再来看这个类的源码,如下所示:

     @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);
        }
    

    可以看到,它的decode()方法又去调用了Downsampler的decode()方法。接下来又到了激动人心的时刻了,Downsampler的代码如下所示:

       /**
         * Load the image for the given InputStream. If a recycled Bitmap whose dimensions exactly match those of the image
         * for the given InputStream is available, the operation is much less expensive in terms of memory.
         *
         * <p>
         *     Note - this method will throw an exception of a Bitmap with dimensions not matching
         *     those of the image for the given InputStream is provided.
         * </p>
         *
         * @param is An {@link InputStream} to the data for the image.
         * @param pool A pool of recycled bitmaps.
         * @param outWidth The width the final image should be close to.
         * @param outHeight The height the final image should be close to.
         * @return A new bitmap containing the image from the given InputStream, or recycle if recycle is not null.
         */
        @SuppressWarnings("resource")
        // see BitmapDecoder.decode
        @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();
    
            // Use to fix the mark limit to avoid allocating buffers that fit entire images.
            RecyclableBufferedInputStream bufferedStream = new RecyclableBufferedInputStream(
                    is, bytesForStream);
            // Use to retrieve exceptions thrown while reading.
            // TODO(#126): when the framework no longer returns partially decoded Bitmaps or provides a way to determine
            // if a Bitmap is partially decoded, consider removing.
            ExceptionCatchingInputStream exceptionStream =
                    ExceptionCatchingInputStream.obtain(bufferedStream);
            // Use to read data.
            // Ensures that we can always reset after reading an image header so that we can still attempt to decode the
            // full image even when the header decode fails and/or overflows our read buffer. See #283.
            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);
    
                // BitmapFactory swallows exceptions during decodes and in some cases when inBitmap is non null, may catch
                // and log a stack trace but still return a non null bitmap. To avoid displaying partially decoded bitmaps,
                // we catch exceptions reading from the stream in our ExceptionCatchingInputStream and throw them here.
                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);
            }
        }
    
    

    可以看到,对服务器返回的InputStream的读取,以及对图片的加载全都在这里了。当然这里其实处理了很多的逻辑,包括对图片的压缩,甚至还有旋转、圆角等逻辑处理,但是我们目前只需要关注主线逻辑就行了。decode()方法执行之后,会返回一个Bitmap对象,那么图片在这里其实也就已经被加载出来了,剩下的工作就是如果让这个Bitmap显示到界面上,到这一步,我们开始从之前的方法一步步跳出,我们继续往下分析。

    总的来看一下数据加载的整体流程
    数据加载流程

    需要这些对象或子类
    ModelLoaderRegistry :注册所有数据加载的loader

    ResourceDecoderRegistry:注册所有资源转换的decoder

    TranscoderRegistry:注册所有对decoder之后进行特殊处理的transcoder

    ResourceEncoderRegistry:注册所有持久化resource(处理过的资源)数据的encoder

    EncoderRegistry : 注册所有的持久化原始数据的encoder

    回到刚才的StreamBitmapDecoder当中,你会发现,它的decode()方法返回的是一个Resource<Bitmap>对象。而我们从Downsampler中得到的是一个Bitmap对象,因此这里在第18行又调用了BitmapResource.obtain()方法,将Bitmap对象包装成了Resource<Bitmap>对象。代码如下所示:

    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;
        }
    
    

    BitmapResource的源码也非常简单,经过这样一层包装之后,如果我还需要获取Bitmap,只需要调用Resource<Bitmap>的get()方法就可以了。

     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;
        }
    

    可以看到,decodeBitmapWrapper()方法返回的是一个GifBitmapWrapper对象。因此,这里又将Resource<Bitmap>封装到了一个GifBitmapWrapper对象当中。这个GifBitmapWrapper顾名思义,就是既能封装GIF,又能封装Bitmap,从而保证了不管是什么类型的图片Glide都能从容应对。

    然后这个GifBitmapWrapper对象会一直向上返回,返回到GifBitmapWrapperResourceDecoder最外层的decode()方法的时候,会对它再做一次封装,经过这一层的封装之后,我们从网络上得到的图片就能够以Resource接口的形式返回,并且还能同时处理Bitmap图片和GIF图片这两种情况。

    那么现在我们可以回到DecodeJob当中了,它的decodeFromSourceData()方法返回的是一个Resource<T>对象,其实也就是Resource<GifBitmapWrapper>对象了。然后继续向上返回,最终返回到decodeFromSource()方法当中,刚才我们就是从这里跟进到decodeSource()方法当中,然后执行了一大堆一大堆的逻辑,最终得到了这个Resource<T>对象。然而你会发现,decodeFromSource()方法最终返回的却是一个Resource<Z>对象,那么这到底是怎么回事呢?我们就需要跟进到transformEncodeAndTranscode()方法来瞧一瞧了,代码如下所示:

     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);
        }
    
    

    首先,这个方法开头的几行transform还有cache,这都是我们后面才会学习的东西,现在不用管它们就可以了。需要注意,这里调用了一个transcode()方法,就把Resource<T>对象转换成Resource<Z>对象了。

    而transcode()方法中又是调用了transcoder的transcode()方法,那么这个transcoder是什么呢?其实这也是Glide源码特别难懂的原因之一,就是它用到的很多对象都是很早很早之前就初始化的,在初始化的时候你可能完全就没有留意过它,因为一时半会根本就用不着,但是真正需要用到的时候你却早就记不起来这个对象是从哪儿来的了。

    其实,在第二步load()方法返回的那个DrawableTypeRequest对象,它的构建函数中去构建了一个FixedLoadProvider对象,然后我们将三个参数传入到了FixedLoadProvider当中,其中就有一个GifBitmapWrapperDrawableTranscoder对象。后来在onSizeReady()方法中获取到了这个参数,并传递到了Engine当中,然后又由Engine传递到了DecodeJob当中。因此,这里的transcoder其实就是这个GifBitmapWrapperDrawableTranscoder对象。

    那么我们来看一下它的源码:

    /**
     * An {@link com.bumptech.glide.load.resource.transcode.ResourceTranscoder} that can transcode either an
     * {@link Bitmap} or an {@link com.bumptech.glide.load.resource.gif.GifDrawable} into an
     * {@link android.graphics.drawable.Drawable}.
     */
    public class GifBitmapWrapperDrawableTranscoder implements ResourceTranscoder<GifBitmapWrapper, GlideDrawable> {
        private final ResourceTranscoder<Bitmap, GlideBitmapDrawable> bitmapDrawableResourceTranscoder;
    
        public GifBitmapWrapperDrawableTranscoder(
                ResourceTranscoder<Bitmap, GlideBitmapDrawable> bitmapDrawableResourceTranscoder) {
            this.bitmapDrawableResourceTranscoder = bitmapDrawableResourceTranscoder;
        }
    
        @SuppressWarnings("unchecked")
        @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();
            }
            // This is unchecked but always safe, anything that extends a Drawable can be safely cast to a Drawable.
            return (Resource<GlideDrawable>) result;
        }
    
        @Override
        public String getId() {
            return "GifBitmapWrapperDrawableTranscoder.com.bumptech.glide.load.resource.transcode";
        }
    }
    

    GifBitmapWrapperDrawableTranscoder的核心作用就是用来转码的。因为GifBitmapWrapper是无法直接显示到ImageView上面的,只有Bitmap或者Drawable才能显示到ImageView上。因此,这里的transcode()方法先从Resource<GifBitmapWrapper>中取出GifBitmapWrapper对象,然后再从GifBitmapWrapper中取出Resource<Bitmap>对象。

    接下来做了一个判断,如果Resource<Bitmap>为空,那么说明此时加载的是GIF图,直接调用getGifResource()方法将图片取出即可,因为Glide用于加载GIF图片是使用的GifDrawable这个类,它本身就是一个Drawable对象了。而如果Resource<Bitmap>不为空,那么就需要再做一次转码,将Bitmap转换成Drawable对象才行,因为要保证静图和动图的类型一致性,不然逻辑上是不好处理的。

    接下来bitmapDrawableResourceTranscoder.transcode(bitmapResource); 又进行了一次转码,是调用的GlideBitmapDrawableTranscoder对象的transcode()方法,代码如下所示:

    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);
        }
    
        @Override
        public String getId() {
            return "GlideBitmapDrawableTranscoder.com.bumptech.glide.load.resource.transcode";
        }
    }
    

    其实就是就是进一步封装。可以看到,这里new出了一个GlideBitmapDrawable对象,并把Bitmap封装到里面。然后对GlideBitmapDrawable再进行一次封装,返回一个Resource<GlideBitmapDrawable>对象。

    现在再返回到GifBitmapWrapperDrawableTranscoder的transcode()方法中,你会发现它们的类型就一致了。因为不管是静图的Resource<GlideBitmapDrawable>对象,还是动图的Resource<GifDrawable>对象,它们都是属于父类Resource<GlideDrawable>对象的。因此transcode()方法也是直接返回了Resource<GlideDrawable>,而这个Resource<GlideDrawable>其实也就是转换过后的Resource<Z>了。

    那么我们继续回到DecodeJob当中,它的decodeFromSource()方法得到了Resource<Z>对象,当然也就是Resource<GlideDrawable>对象。然后继续向上返回会回到EngineRunnable的decodeFromSource()方法,再回到decode()方法,再回到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);
            }
        }
        
        
          private void onLoadComplete(Resource resource) {
            manager.onResourceReady(resource);
        }
        
           @Override
        public void onResourceReady(final Resource<?> resource) {
            this.resource = resource;
            MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
        }
        
        
         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;
            }
        }
        
        
        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;
    
            // Hold on to resource for duration of request so we don't recycle it in the middle of notifying if it
            // synchronously released by one of the callbacks.
            engineResource.acquire();
            listener.onEngineJobComplete(key, engineResource);
            //调用了所有ResourceCallback的onResourceReady()方法。
            for (ResourceCallback cb : cbs) {
                if (!isInIgnoredCallbacks(cb)) {
                    engineResource.acquire();
                    cb.onResourceReady(engineResource);
                }
            }
            // Our request is complete, so we can release the resource.
            engineResource.release();
        }
    
    

    可以看到,这里在onResourceReady()方法使用Handler发出了一条MSG_COMPLETE消息,那么在MainThreadCallback的handleMessage()方法中就会收到这条消息。从这里开始,所有的逻辑又回到主线程当中进行了,因为很快就需要更新UI了。

    然后调用了handleResultOnMainThread()方法,这个方法中又通过一个循环,调用了所有ResourceCallback的onResourceReady()方法。那么这个ResourceCallback是什么呢?答案在addCallback()方法当中,它会向cbs集合中去添加ResourceCallback。那么这个addCallback()方法又是哪里调用的呢?其实调用的地方我们早就已经看过了,只不过之前没有注意,现在重新来看一下Engine的load()方法,如下所示:

     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) {
                
                
                // load 方法太长 只是代码片段
            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);
            
        }
    
    

    就是在这里调用的EngineJob的addCallback()方法来注册的一个ResourceCallback。那么接下来的问题就是,Engine.load()方法的ResourceCallback参数又是谁传过来的呢?这里我们可以看一下ResourceCallback这个接口的实现类都有哪些(也可以回到GenericRequest的onSizeReady()方法当中),我们看到ResourceCallback是load()方法的最后一个参数,那么在onSizeReady()方法中调用load()方法时传入的最后一个参数是什么?

    方法一:

    ResourceCallback实现类:GenericRequest (obtain())->GenericRequestBuilder (子类)-> DrawableRequestBuilder (子类)-> DrawableTypeRequest

    DrawableTypeRequest在哪里用了?

    就在我们Glide的第二步load()方法时返回。

    方法二:
    代码如下所示:

    
     @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));
            }
        }
    

    主要就是这句engine.load()的最后一个参数 this, genericRequest本身实现了resourceCallback接口,所以EngineJob的回调最终就是回调了GenericRequest的onResourceReady()方法,其实就是:

     /**
         * A callback method that should never be invoked directly.
         */
        @SuppressWarnings("unchecked")
        @Override
        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);
                // We can't set the status to complete before asking canSetResource().
                status = Status.COMPLETE;
                return;
            }
    
            onResourceReady(resource, (R) received);
        }
    
        /**
         * Internal {@link #onResourceReady(Resource)} where arguments are known to be safe.
         *
         * @param resource original {@link Resource}, never <code>null</code>
         * @param result object returned by {@link Resource#get()}, checked for type and never <code>null</code>
         */
        private void onResourceReady(Resource<?> resource, R result) {
            // We must call isFirstReadyResource before setting status.
            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);
            }
        }
    

    这里有两个onResourceReady()方法,首先在第一个onResourceReady()方法当中,调用resource.get()方法获取到了封装的图片对象,也就是GlideBitmapDrawable对象,或者是GifDrawable对象。然后将这个值传入到了第二个onResourceReady()方法当中,并调用了target.onResourceReady()方法。

    那么这个target又是什么呢?这个又需要向上翻很久了,在第三步into()方法的一开始,我们就分析了在into()方法的最后一行,调用了glide.buildImageViewTarget()方法来构建出一个Target,而这个Target就是一个GlideDrawableImageViewTarget对象。下面就是GlideDrawableImageViewTarget的onResourceReady()方法了:

       @Override
        public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
            if (!resource.isAnimated()) {
                //TODO: Try to generalize this to other sizes/shapes.
                // This is a dirty hack that tries to make loading square thumbnails and then square full images less costly
                // by forcing both the smaller thumb and the larger version to have exactly the same intrinsic dimensions.
                // If a drawable is replaced in an ImageView by another drawable with different intrinsic dimensions,
                // the ImageView requests a layout. Scrolling rapidly while replacing thumbs with larger images triggers
                // lots of these calls and causes significant amounts of jank.
                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);
    }
    
    
        @Override
        protected void setResource(GlideDrawable resource) {
            view.setImageDrawable(resource);
        }
          
    

    在GlideDrawableImageViewTarget的onResourceReady()方法中做了一些逻辑处理,包括如果是GIF图片的话,就调用resource.start()方法开始播放图片,但是好像并没有看到哪里有将GlideDrawable显示到ImageView上的逻辑。

    确实没有,不过父类里面有,其中调用了super.onResourceReady()方法,GlideDrawableImageViewTarget的父类是ImageViewTarget,我们来看下它的代码吧:

    
        @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);
    
    

    可以看到,在ImageViewTarget的onResourceReady()方法当中调用了setResource()方法,而ImageViewTarget的setResource()方法是一个抽象方法,具体的实现还是在子类那边实现的。

    那子类的setResource()方法是怎么实现的呢?回头再来看一下GlideDrawableImageViewTarget的setResource()方法,没错,调用的view.setImageDrawable()方法,而这个view就是ImageView。代码执行到这里,图片终于也就显示出来了。

    那么,我们对Glide执行流程的源码分析,到这里也终于结束了。

    总结

    反正在我读Glide源码之前,我没想过使用起来如此方便的Glide源码这么复杂,逻辑看的我想吐。
    现在觉得短短的一行:

    Glide.with(this).load(url).into(imageView);
    

    是真的复杂。

    也越发的佩服写Glide的大牛们。

    以上就是最基础Glide的使用流程的源码分析。







    相关文章

      网友评论

        本文标题:Glide源码分析之三 into()下篇

        本文链接:https://www.haomeiwen.com/subject/tuawcqtx.html