Glide加载图片流程源码解析(2)

作者: JJQ3 | 来源:发表于2017-08-08 09:44 被阅读71次

    因为简书篇幅限制,所以另起一篇。接上文。。。。。
    上文链接:http://www.jianshu.com/p/3cdb77859a63

    在这里,我们看到了fetcher.loadData()方法,这里的fetcher其实就是httpUrlFetcher的实体对象,接着往下看:

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

    哦?我们终于看到了跟网络通讯有关的东西了。在这里,我们只是拿到了一个inputStream的输入流而已。ok,我们再回去,回到DecodeJob的decodeSource()方法,我们发现这个方法其实起到了解码的作用:

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

    这里的loadProvider当然还是之前的FixdLoadProvider,它的sourceDecoder其实就是ImageVideoGifDrawableLoadProvider里的sourceDecoder,也就是GifBitmapWrapperResourceDecoder的一个对象,我们顺着这条路,来看GifBitmapWrapperResourceDecoder这个类的decode()方法:

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

    我们看到,这里通过调用decodeStream()方法来读取数据,根据ImageHeaderParser.ImageType来判断这张图到底是gif图还是普通图片,gif图的解码过程我看了半天其实还没搞清楚,有点复杂,等我搞清楚了再来,我们这里先看静图的解码过程吧

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

    这里调用了一个bitmapDecoder.decode(toDecode, width, height)方法,这其实就是ImageVideoBitmapDecoder的decode()方法

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

    我们看到这里又是调用了streamDecoder.decode(is, width, height);的方法,定位一下其实streamDecoder就是StreamBitmapDecoder类,ok,继续:

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

    oh shit,终于看到bitmap了,ok,来看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进行了读取,以及图片的加载, MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream)这个就是用来读取数据的。这个方法里还操作了很多逻辑,旋转,圆角等,具体就不详述了。ok,这里我们得到了bitmap对象,接下来任务就是就是把bitmap对象去显示出来。我们再次回到GifBitmapWrapperResourceDecoder的decodeBitmapWrapper方法中我们发现这里对bitmap进行了一次封装,同时支持了bitmap图片和gif图片,返回的是GifBitmapWrapper类的对象。ok知道了这个以后我们再回去,回到DecodeJob的decodeFromSource()方法:

    public Resource<Z> decodeFromSource() throws Exception {
        Resource<T> decoded = decodeSource();
        return transformEncodeAndTranscode(decoded);
    }
    

    再来看transformEncodeAndTranscode(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;
        }
    

    这里前面的代码都没什么用,我们来看一开始transformed是一个Resource对象,经过transcode()方法之后变成了一个Resource对象,其实这里的transformed就是GifBitmapWrapper转变后的GifBitmapWrapper对象。我们再来看transcode()方法:

    private Resource<Z> transcode(Resource<T> transformed) {
            if (transformed == null) {
                return null;
            }
            return transcoder.transcode(transformed);
        }
    

    ok,再定位,这里的transcoder其实就是GifBitmapWrapperDrawableTranscoder的一个实例,接下去看它的transcode()方法:

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

    这里transcode()方法直接返回了Resource。ok,接下来,时间回滚,我们又要回去了,回到EngineRunnable的decodeFromSource()方法,刚才的这么多分析,我们最终知道了decode()方法执行以后得到一个Resource对象。接下来就是如何显示的问题了,一个是错误的时候onLoadFailed(),还有一个就是加载完成以后调用的onLoadComplete(resource);方法,我们看里面return了onResourceReady(resource)方法,定位到EngineJob的onResourceReady()方法:

    @Override
        public void onResourceReady(final Resource<?> resource) {
            this.resource = resource;
            MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
        }
    

    这里用到了handler,说明之前的EngineRunnable都是在子线程中加载的,handler通知MSG_COMPLETE,然后我们看到了:

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

    感觉胜利在望了,已经又回到了主线程来更新UI了,接着来看 job.handleResultOnMainThread()方法:

    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);
    
            for (ResourceCallback cb : cbs) {
                if (!isInIgnoredCallbacks(cb)) {
                    engineResource.acquire();
                    cb.onResourceReady(engineResource);
                }
            }
            // Our request is complete, so we can release the resource.
            engineResource.release();
        }
    

    这里发现调用了两遍onResouceReady(),ok,我们沿着方法的调用顺序回去,回到GenericRequest的onResourceReady()方法:

    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);
        }
    
    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()方法,第一个取得了封装的图片对象,然后传给了第二个onResouceReady()方法,注意这里有一个:
    target.onResourceReady(result, animation);

    定位这个target,发现其实就是GlideDrawableImageViewTarget的实例,来看这个类:

    public class GlideDrawableImageViewTarget extends ImageViewTarget<GlideDrawable> {
        private static final float SQUARE_RATIO_MARGIN = 0.05f;
        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();
        }
    
        /**
         * Sets the drawable on the view using
         * {@link android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)}.
         *
         * @param resource The {@link android.graphics.drawable.Drawable} to display in the view.
         */
        @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();
            }
        }
    }
    

    注意这里的onSourceReady()方法,super.onResourceReady(resource, animation);
    我们来到它的父类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);
    

    这是个抽象方法,所以我们去子类寻找实现的逻辑,也就是GlideDrawableImageViewTarget类:

    @Override
        protected void setResource(GlideDrawable resource) {
            view.setImageDrawable(resource);
        }
    

    酷哦。到这里也就显示出来了。

    厉害了,写了这么多,本来想把Glide的缓存一并讲进去了,但实在太长了,算了,另起一篇了只能。接下去会写一篇Glide的缓存机制,Fresco源码解析(都说Fresco性能上比Glide要优秀,但是到底优秀在哪里。我觉得有必要深究一下)。

    我的微信公共账号,不定期更新一些干货,欢迎关注

    相关文章

      网友评论

        本文标题:Glide加载图片流程源码解析(2)

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