Glide源码解析之DecodeJob

前言

DecodeJob的主要工作是从磁盘或者数据源(比如网络)中获取资源,并进行转换和转码。

class DecodeJob implements DataFetcherGenerator.FetcherReadyCallback,
        Runnable,
        Comparable>,
        Poolable {
        
    DecodeJob(DiskCacheProvider diskCacheProvider, Pools.Pool> pool) {
        this.diskCacheProvider = diskCacheProvider;
        this.pool = pool;
    }

    DecodeJob init(
            GlideContext glideContext,
            Object model,
            EngineKey loadKey,
            Key signature,
            int width,
            int height,
            Class resourceClass,
            Class transcodeClass,
            Priority priority,
            DiskCacheStrategy diskCacheStrategy,
            Map, Transformation> transformations,
            boolean isTransformationRequired,
            boolean isScaleOnlyOrNoTransform,
            boolean onlyRetrieveFromCache,
            Options options,
            Callback callback,
            int order) {
        decodeHelper.init(
                glideContext,
                model,
                signature,
                width,
                height,
                diskCacheStrategy,
                resourceClass,
                transcodeClass,
                priority,
                options,
                transformations,
                isTransformationRequired,
                isScaleOnlyOrNoTransform,
                diskCacheProvider);
        this.glideContext = glideContext;
        this.signature = signature;
        this.priority = priority;
        this.loadKey = loadKey;
        this.width = width;
        this.height = height;
        this.diskCacheStrategy = diskCacheStrategy;  //在BaseRequestOptions中默认为DiskCacheStrategy.AUTOMATIC
        this.onlyRetrieveFromCache = onlyRetrieveFromCache;
        this.options = options;
        this.callback = callback;
        this.order = order;
        this.runReason = RunReason.INITIALIZE;  //关注点
        this.model = model;
        return this;
    }
}

DecodeJob 实现了 Runnable 接口,它运行在 EngineJob 里面的线程池里。运行时首先会检查是否已经取消了执行,如果没有则执行 runWrapped()。

在 runWrapped() 中首先会判断当前的 runReason ,由于在 init() 中被赋值为 INITIALIZE ,则获取到的 stage 为 Stage.RESOURCE_CACHE ( Stage 表示当前执行到的阶段)。接着根据当前的 Stage 获取到的 DataFetcherGenerator 为 ResourceCacheGenerator,它用于从磁盘缓存中获取经过转化后的资源。不熟悉这个类的可以看下 Glide源码解析之ResourceCacheGenerator

    @Override
    public void run() {
        DataFetcher localFetcher = currentFetcher;
        try {
            if (isCancelled) {      
                notifyFailed();
                return;
            }
            runWrapped();
        } catch (CallbackException e) {
            throw e;
        } catch (Throwable t) {
            if (stage != Stage.ENCODE) {
                throwables.add(t);
                notifyFailed();
            }
            if (!isCancelled) {
                throw t;
            }
            throw t;
        } finally {
            if (localFetcher != null) {
                localFetcher.cleanup();
            }
        }
    }
    
    private void runWrapped() {
        runWrappedCount++;
        switch (runReason) {    //在 init()中赋值为 INITIALIZE
            case INITIALIZE:        
                stage = getNextStage(Stage.INITIALIZE);
                currentGenerator = getNextGenerator();
                runGenerators();
                break;
            case SWITCH_TO_SOURCE_SERVICE:
                runGenerators();
                break;
            case DECODE_DATA:
                decodeFromRetrievedData();
                break;
            default:
                throw new IllegalStateException("Unrecognized run reason: " + runReason);
        }
    }
    
    private Stage getNextStage(Stage current) {
        switch (current) {
            case INITIALIZE:
                return diskCacheStrategy.decodeCachedResource() //默认为true
                        ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
            case RESOURCE_CACHE:
                return diskCacheStrategy.decodeCachedData() //默认为true
                        ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
            case DATA_CACHE:
                return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
            case SOURCE:
            case FINISHED:
                return Stage.FINISHED;
            default:
                throw new IllegalArgumentException("Unrecognized stage: " + current);
        }
    }
    
    private DataFetcherGenerator getNextGenerator() {
        switch (stage) {
            case RESOURCE_CACHE:
                return new ResourceCacheGenerator(decodeHelper, this);
            case DATA_CACHE:
                return new DataCacheGenerator(decodeHelper, this);
            case SOURCE:
                return new SourceGenerator(decodeHelper, this);
            case FINISHED:
                return null;
            default:
                throw new IllegalStateException("Unrecognized stage: " + stage);
        }
    }

加载数据

获取到 DataFetcherGenerator 后就开始将获取数据的操作交给它的 startNext() 来执行,这里我们以 ResourceCacheGenerator 成功获取为例,则 isStarted 会被赋值为 true ,不用进入循环。最终获取的数据会通过 DecodeJob 实现的 FetcherReadyCallback 的 onDataFetcherReady() 回调。

如果 ResourceCacheGenerator 没有获取到数据,由上可知则依次会交给 DataCacheGenerator (原数据的磁盘缓存) 和 SourceGenerator (从数据源加载,比如网络,不熟悉的可以看下 Glide源码解析之SourceGenerator)

    private void runGenerators() {
        boolean isStarted = false;
        while (!isCancelled && currentGenerator != null
                && !(isStarted = currentGenerator.startNext())) {
            stage = getNextStage(stage);
            currentGenerator = getNextGenerator();

            if (stage == Stage.SOURCE) {
                reschedule();
                return;
            }
        }
        
        if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
            notifyFailed();
        }

    }

加载数据完成

在 ResourceCacheGenerator 加载数据成功后则通过 onDataFetcherReady() 回调给 DecodeJob ,由于 ResourceCacheGenerator 并没有切换线程去获取资源,所以会执行到 decodeFromRetrievedData() 去进行解码。经过一系列的调用,最终会将解码的操作交给 LoadPath 去执行。

    //ResourceCacheGenerator
    @Override
    public void onDataReady(Object data) {
        cb.onDataFetcherReady(sourceKey, data/*ByteBuffer*/, loadData.fetcher/*ByteBufferFetcher*/, DataSource.RESOURCE_DISK_CACHE,
                currentKey);
    }
    
    //DecodeJob
    @Override
    public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher fetcher,
                                   DataSource dataSource, Key attemptedKey) {
        this.currentSourceKey = sourceKey;
        this.currentData = data;
        this.currentFetcher = fetcher;
        this.currentDataSource = dataSource;
        this.currentAttemptingKey = attemptedKey;
        if (Thread.currentThread() != currentThread) {
            runReason = RunReason.DECODE_DATA;
            callback.reschedule(this);
        } else {
            try {
                decodeFromRetrievedData();  // 执行点  
            } finally {
                GlideTrace.endSection();
            }
        }
    }
    
    private void decodeFromRetrievedData() {
        Resource resource = null;
        try {
            resource = decodeFromData(currentFetcher, currentData, currentDataSource);     // 执行点  
        } catch (GlideException e) {
            e.setLoggingDetails(currentAttemptingKey, currentDataSource);
            throwables.add(e);
        }
        if (resource != null) {
            notifyEncodeAndRelease(resource, currentDataSource);
        } else {
            runGenerators();
        }
    }
    
    private  Resource decodeFromData(DataFetcher fetcher, Data data,
                                              DataSource dataSource) throws GlideException {
        try {
            if (data == null) {
                return null;
            }
            Resource result = decodeFromFetcher(data, dataSource);   // 执行点  
            return result;
        } finally {
            fetcher.cleanup();
        }
    }
    
    private  Resource decodeFromFetcher(Data data, DataSource dataSource)
            throws GlideException {
        LoadPath path = decodeHelper.getLoadPath((Class) data.getClass());  // 执行点
        return runLoadPath(data, dataSource, path);
    }
    
    private  Resource runLoadPath(Data data, DataSource dataSource,
                                                         LoadPath path) throws GlideException {
        Options options = getOptionsWithHardwareConfig(dataSource);
        DataRewinder rewinder = glideContext.getRegistry().getRewinder(data);   // ByteBufferRewinder
        try {
            return path.load(
                    rewinder, options, width, height, new DecodeCallback(dataSource));  // 执行点
        } finally {
            rewinder.cleanup();
        }
    }

开始解码

会遍历 decodePaths 来寻找哪个 DecodePath 是能完成解码工作的,而具体解码由分为三步,第一步为解码资源,第二步为解码之后的工作(实际为对资源进行缓存),第三步为转码,即是将获取到的资源类型转为另一种资源类型。

    //LoadPath
    public Resource load(DataRewinder rewinder, @NonNull Options options, int width,
                                    int height, DecodePath.DecodeCallback decodeCallback) throws GlideException {
        List throwables = Preconditions.checkNotNull(listPool.acquire());
        try {
            return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);      // 执行点
        } finally {
            listPool.release(throwables);
        }
    }
    
    private Resource loadWithExceptionList(DataRewinder rewinder,
                                                      @NonNull Options options,
                                                      int width, int height, DecodePath.DecodeCallback decodeCallback,
                                                      List exceptions) throws GlideException {
        Resource result = null;
        for (int i = 0, size = decodePaths.size(); i < size; i++) {
            DecodePath path = decodePaths.get(i);
            try {
                result = path.decode(rewinder, width, height, options, decodeCallback);  // 执行点
            } catch (GlideException e) {
                exceptions.add(e);
            }
            if (result != null) {
                break;
            }
        }

        if (result == null) {
            throw new GlideException(failureMessage, new ArrayList<>(exceptions));
        }

        return result;
    }

    //DecodePath
    public Resource decode(DataRewinder rewinder, int width, int height,
                                      @NonNull Options options, DecodeCallback callback) throws GlideException {
        Resource decoded = decodeResource(rewinder, width, height, options);
        Resource transformed = callback.onResourceDecoded(decoded);
        return transcoder.transcode(transformed, options);
    }
    

解码资源

当资源类型是 ByteBuffer 的时候,对应的 DataRewinder 是 ByteBufferRewinder ,ResourceDecoder 是 ByteBufferBitmapDecoder ,则最终的解码操作将由 ByteBufferBitmapDecoder 来完成。

    @NonNull
    private Resource decodeResource(DataRewinder rewinder, int width,
                                                  int height, @NonNull Options options) throws GlideException {
        List exceptions = Preconditions.checkNotNull(listPool.acquire());
        try {
            return decodeResourceWithList(rewinder, width, height, options, exceptions);   //执行点
        } finally {
            listPool.release(exceptions);
        }
    }
    
    @NonNull
    private Resource decodeResourceWithList(DataRewinder rewinder /*ByteBufferRewinder*/, int width,
                                                          int height, @NonNull Options options, List exceptions) throws GlideException {
        Resource result = null;
        for (int i = 0, size = decoders.size(); i < size; i++) {
            ResourceDecoder decoder = decoders.get(i);
            try {
                DataType data = rewinder.rewindAndGet();
                if (decoder.handles(data, options)) {
                    //ByteBufferBitmapDecoder
                    data = rewinder.rewindAndGet();
                    result = decoder.decode(data, width, height, options);  //执行点
                }
            } catch (IOException | RuntimeException | OutOfMemoryError e) {
                exceptions.add(e);
            }

            if (result != null) {
                break;
            }
        }

        if (result == null) {
            throw new GlideException(failureMessage, new ArrayList<>(exceptions));
        }
        return result;
    }

首先会将 ByteBuffer 转化为 InputStream ,然后再交给 Downsampler来解码。

    public class ByteBufferBitmapDecoder implements ResourceDecoder {
      private final Downsampler downsampler;
    
      public ByteBufferBitmapDecoder(Downsampler downsampler) {
        this.downsampler = downsampler;
      }
    
      @Override
      public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) {
        return downsampler.handles(source);
      }
    
      @Override
      public Resource decode(@NonNull ByteBuffer source, int width, int height,
          @NonNull Options options)
          throws IOException {
        InputStream is = ByteBufferUtil.toStream(source);
        return downsampler.decode(is, width, height, options);
      }
    }
    
    //Downsampler
    public boolean handles(@SuppressWarnings("unused") ByteBuffer byteBuffer) {
        return true;
    }
    
    //ByteBufferUtil
    public static InputStream toStream(@NonNull ByteBuffer buffer) {
        return new ByteBufferStream(buffer);
    }

在解码操作里首先会根据 Bitmap 的宽高和 ImageView 的宽高来计算出缩放值,然后从 BitmapPool 获取一个合适宽高的 Bitmap 交给 BitmapFactory 去进行最终的解码操作,最后再判断是否需要对 Bitmap 进行旋转。

获取到 Bitmap 后将它包装进 BitmapResource 里返回,到此解码资源的工作就完成了。

    //Downsampler
    public Resource decode(InputStream is, int outWidth, int outHeight,
                                   Options options) throws IOException {
        return decode(is, outWidth, outHeight, options, EMPTY_CALLBACKS);
    }
    
    public Resource decode(InputStream is, int requestedWidth, int requestedHeight,
                                   Options options, DecodeCallbacks callbacks) throws IOException {
        //省略取值代码

        try {
            Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
                    downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
                    requestedHeight, fixBitmapToRequestedDimensions, callbacks);
            return BitmapResource.obtain(result, bitmapPool);
        } finally {
            releaseOptions(bitmapFactoryOptions);
            byteArrayPool.put(bytesForOptions);
        }
    }
    
    private Bitmap decodeFromWrappedStreams(InputStream is,
                                            BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
                                            DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
                                            int requestedHeight, boolean fixBitmapToRequestedDimensions,
                                            DecodeCallbacks callbacks) throws IOException {
        
        //省略代码

        //计算图片的缩放值,设置给 options
        calculateScaling(
                imageType,
                is,
                callbacks,
                bitmapPool,
                downsampleStrategy,
                degreesToRotate,
                sourceWidth,
                sourceHeight,
                targetWidth,
                targetHeight,
                options);
                
        //计算配置
        calculateConfig(
                is,
                decodeFormat,
                isHardwareConfigAllowed,
                isExifOrientationRequired,
                options,
                targetWidth,
                targetHeight);

        boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
        if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
            //不用缩放或者系统版本大于19,而且可以使用 BitmapPool
            
            int expectedWidth;
            int expectedHeight;
            if (sourceWidth >= 0 && sourceHeight >= 0
                    && fixBitmapToRequestedDimensions && isKitKatOrGreater) {
                expectedWidth = targetWidth;
                expectedHeight = targetHeight;
            } else {
                float densityMultiplier = isScaling(options)
                        ? (float) options.inTargetDensity / options.inDensity : 1f;
                int sampleSize = options.inSampleSize;
                int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize);
                int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize);
                expectedWidth = Math.round(downsampledWidth * densityMultiplier);
                expectedHeight = Math.round(downsampledHeight * densityMultiplier);
            }

            if (expectedWidth > 0 && expectedHeight > 0) {
                setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);    //去 BitmapPool 中获取期望宽高的 Bitmap (设置给 options.inBitmap)
            }
        }
        
        Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);    //执行点
        callbacks.onDecodeComplete(bitmapPool, downsampled);    //空实现

        Bitmap rotated = null;
        if (downsampled != null) {
            // 在上面 calculateScaling() 会修改 inDensity,这里设置回来
            downsampled.setDensity(displayMetrics.densityDpi);

            rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);  //旋转图片
            
            if (!downsampled.equals(rotated)) {
                //如果旋转了,则缓存旋转后的 Bitmap 。如果没旋转则还是原来的 Bitmap
                bitmapPool.put(downsampled);
            }
        }

        return rotated;
    }
    
    private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options,
                                       DecodeCallbacks callbacks, BitmapPool bitmapPool) throws IOException {
        if (options.inJustDecodeBounds) {
            is.mark(MARK_POSITION);
        } else {
            callbacks.onObtainBounds();
        }
       
        int sourceWidth = options.outWidth;
        int sourceHeight = options.outHeight;
        String outMimeType = options.outMimeType;
        final Bitmap result;
        TransformationUtils.getBitmapDrawableLock().lock();
        try {
            result = BitmapFactory.decodeStream(is, null, options);  //最终还是靠 BitmapFactory 来生成 Bitmap 的
        } catch (IllegalArgumentException e) {
            IOException bitmapAssertionException =
                    newIoExceptionForInBitmapAssertion(e, sourceWidth, sourceHeight, outMimeType, options);
            throw bitmapAssertionException;
        } finally {
            TransformationUtils.getBitmapDrawableLock().unlock();
        }

        return result;
    }

编码资源

解码资源完成后接着就轮到编码资源了,由 DecodeCallback (接口) 去执行,在 DecodeJob 的 runLoadPath() 中给callback 赋的值是 DecodeCallback (实现类),而它将具体的编码操作又交回给了 DecodeJob 的 onResourceDecoded()去执行。

首先会判断资源是否可以编码,由上可知资源类型为 Bitmap ,是可以编码的。接着判断是否可以进行缓存,由于这里是从 ResourceCacheGenerator 获取数据的,本来就是从缓存中取得数据,所以不再需要进行缓存,最终会将源数据返回。

如果是需要缓存的则会交给 DeferredEncodeManager 进行磁盘缓存,如果对磁盘缓存不熟悉的可以看下 Glide源码解析之DiskCache

    //DecodePath
    public Resource decode(DataRewinder rewinder, int width, int height,
                                      @NonNull Options options, DecodeCallback callback) throws GlideException {
        Resource decoded = decodeResource(rewinder, width, height, options);
        Resource transformed = callback.onResourceDecoded(decoded);    //执行点
        return transcoder.transcode(transformed, options);
    }
    
    private final class DecodeCallback implements DecodePath.DecodeCallback {

        private final DataSource dataSource;

        @Synthetic
        DecodeCallback(DataSource dataSource) {
            this.dataSource = dataSource;
        }

        @NonNull
        @Override
        public Resource onResourceDecoded(@NonNull Resource decoded) {
            return DecodeJob.this.onResourceDecoded(dataSource, decoded);
        }
    }
    
     Resource onResourceDecoded(DataSource dataSource,
                                      @NonNull Resource decoded) {
        Class resourceSubClass = (Class) decoded.get().getClass();
        Transformation appliedTransformation = null;
        Resource transformed = decoded;
        
        // ResourceCacheGenerator 回调给 DecodeJob 的就是 DataSource.RESOURCE_DISK_CACHE
        if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
            appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
            transformed = appliedTransformation.transform(glideContext, decoded, width, height);
        }
        
        if (!decoded.equals(transformed)) {
            decoded.recycle();
        }

        final EncodeStrategy encodeStrategy;
        final ResourceEncoder encoder;
        if (decodeHelper.isResourceEncoderAvailable(transformed)) {
            encoder = decodeHelper.getResultEncoder(transformed);   // BitmapEncoder
            encodeStrategy = encoder.getEncodeStrategy(options);    // EncodeStrategy.TRANSFORMED
        } else {
            encoder = null;
            encodeStrategy = EncodeStrategy.NONE;
        }

        Resource result = transformed;
        
        //从 ResourceCacheGenerator 获取数据用的是 sourceKey ,所以这里是 false。
        //因为我们本来就是从缓存中取的数据,所以并不需要再次缓存。
        boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);   
        
        if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,
                encodeStrategy)) {
            
            //这里面是生成缓存的 Key ,并将缓存的操作交给 deferredEncodeManager 去执行
            if (encoder == null) {
                throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
            }
            final Key key;
            switch (encodeStrategy) {
                case SOURCE:
                    key = new DataCacheKey(currentSourceKey, signature);    //代表缓存的是源数据
                    break;  
                case TRANSFORMED:
                    key =
                            new ResourceCacheKey(
                                    decodeHelper.getArrayPool(),
                                    currentSourceKey,
                                    signature,
                                    width,
                                    height,
                                    appliedTransformation,
                                    resourceSubClass,
                                    options);   //代表缓存的是经过转化的数据
                    break;
                default:
                    throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
            }

            LockedResource lockedResult = LockedResource.obtain(transformed);
            deferredEncodeManager.init(key, encoder, lockedResult);     
            result = lockedResult;
        }
        return result;
    }
    
    //DeferredEncodeManager
    void encode(DiskCacheProvider diskCacheProvider, Options options) {
        try {
            //进行磁盘缓存
            diskCacheProvider.getDiskCache().put(key,
                    new DataCacheWriter<>(encoder, toEncode, options));
        } finally {
            toEncode.unlock();
        }
    }

转码

将 Resource 传进 LazyBitmapDrawableResource ,而 LazyBitmapDrawableResource 有一个 get() 将 Resource 转化为 BitmapDrawable 。到此数据的获取过程就结束了。

    //BitmapDrawableTranscoder
    public Resource transcode(@NonNull Resource toTranscode,
      @NonNull Options options) {
        return LazyBitmapDrawableResource.obtain(resources, toTranscode);
    }
    
    //LazyBitmapDrawableResource
    public static Resource obtain(
      @NonNull Resources resources, @Nullable Resource bitmapResource) {
        if (bitmapResource == null) {
            return null;
        }
        return new LazyBitmapDrawableResource(resources, bitmapResource);
    }
    
    public BitmapDrawable get() {
        return new BitmapDrawable(resources /*这是 Android 里面的 Resource */, bitmapResource.get());
    }
    

处理数据

让我们回到 DecodeJob 一开始获取数据的时候,在获取到数据之后通过回调将数据传给 EngineJob 。接着如果需要缓存的则调用 deferredEncodeManager 去执行,最后释放资源。到此 DecodeJob 的使命就全部完成了。

    //DecodeJob
    private void decodeFromRetrievedData() {
        Resource resource = null;
        try {
            resource = decodeFromData(currentFetcher, currentData, currentDataSource);
        } catch (GlideException e) {
            e.setLoggingDetails(currentAttemptingKey, currentDataSource);
            throwables.add(e);
        }
        if (resource != null) {
            notifyEncodeAndRelease(resource, currentDataSource);   //执行点 
        } else {
            runGenerators();
        }
    }
    
    private void notifyEncodeAndRelease(Resource resource, DataSource dataSource) {
        if (resource instanceof Initializable) {
            ((Initializable) resource).initialize();
        }

        Resource result = resource;
        LockedResource lockedResource = null;
        if (deferredEncodeManager.hasResourceToEncode()) {
            lockedResource = LockedResource.obtain(resource);
            result = lockedResource;
        }

        notifyComplete(result, dataSource);     //执行点

        stage = Stage.ENCODE;
        try {
            if (deferredEncodeManager.hasResourceToEncode()) {
                //上面说过 deferredEncodeManager 是用来调用磁盘缓存的,就是在这里调用
                deferredEncodeManager.encode(diskCacheProvider, options);
            }
        } finally {
            if (lockedResource != null) {
                lockedResource.unlock();
            }
        }
        
        //释放资源
        onEncodeComplete();
    }
   
    private void notifyComplete(Resource resource, DataSource dataSource) {
        callback.onResourceReady(resource, dataSource);
    }
    
    //EngineJob    
    public void onResourceReady(Resource resource, DataSource dataSource) {
        synchronized (this) {
            this.resource = resource;
            this.dataSource = dataSource;
        }
        notifyCallbacksOfResult();
    } 

你可能感兴趣的:(Glide源码解析之DecodeJob)