Glide源码解析之加载流程

前言

Glide 源码解析系列到目前为止刚好写了10篇,而本篇做为收篇之作终于要完成了。一开始决定写这个系列是因为面试被问到源码比较多,虽然也看过别人写的博客,但是大多数都是流程走一遍,很多细节的东西并没有掌握,一被问到就懵逼。我们做技术的不像其他行业,不懂就是真的不懂,想吹也没法吹。所以还是决定亲自撸一遍源码,不仅为了面试,而且对自己能力的提升也有好处。

开始加载

Glide源码解析之RequestBuilder 中我们讲了 SingleRequest 的创建,并且当生命周期为 onStart() 的时候会调用 begin() 来开始执行加载请求。

在 begin() 里首先会对当前的状态进行检查,然后判断宽高是否有设置或者能获取到。由于 ImageView 是需要经过 onLayout() 后才能确定自身宽高的,所以在这里我们先要去获取到 ImageView 的宽高。

    public synchronized void begin() {

        //防止同时执行
        if (status == Status.RUNNING) {
            throw new IllegalArgumentException("Cannot restart a running request");
        }

        //如果已经完成的则直接处理
        if (status == Status.COMPLETE) {
            onResourceReady(resource, DataSource.MEMORY_CACHE);
            return;
        }

        //等待能获取到 ImageView 的宽高
        status = Status.WAITING_FOR_SIZE;
        
        // overrideWidth 和 overrideHeight 默认为-1,所以这里为 false
        if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
            onSizeReady(overrideWidth, overrideHeight);     //执行点
        } else {
            target.getSize(this);
        }

        if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
                && canNotifyStatusChanged()) {
            target.onLoadStarted(getPlaceholderDrawable()); //设置占位图
        }
    }

获取宽高

由于 View 的宽高是要在 onLayout() 之后才能确定下来,而 onDraw() 是在 onLayout() 之后执行的,所以这里通过向 ViewTreeObserver 添加 OnPreDrawListener ,这样在 onDraw() 之前就能获取到宽高。

    //ViewTarget
    public void getSize(@NonNull SizeReadyCallback cb) {
        sizeDeterminer.getSize(cb);
    }
    
    //SizeDeterminer
    void getSize(@NonNull SizeReadyCallback cb) {
        int currentWidth = getTargetWidth();
        int currentHeight = getTargetHeight();
        if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
            cb.onSizeReady(currentWidth, currentHeight);
            return;
        }

        if (!cbs.contains(cb)) {
            cbs.add(cb);
        }
        
        if (layoutListener == null) {
            ViewTreeObserver observer = view.getViewTreeObserver();
            layoutListener = new SizeDeterminerLayoutListener(this);    //关注点
            observer.addOnPreDrawListener(layoutListener);              //执行点
        }
    }
    
    //SizeDeterminerLayoutListener
    public boolean onPreDraw() {
        SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
        if (sizeDeterminer != null) {
            sizeDeterminer.checkCurrentDimens();    //执行点
        }
        return true;
    }
    
    //SizeDeterminer
    void checkCurrentDimens() {
        if (cbs.isEmpty()) {
            return;
        }

        int currentWidth = getTargetWidth();
        int currentHeight = getTargetHeight();
        if (!isViewStateAndSizeValid(currentWidth, currentHeight)) {
            return;
        }

        notifyCbs(currentWidth, currentHeight);     //执行点
        clearCallbacksAndListener();                //移除监听
    }
    
    private void notifyCbs(int width, int height) {
        for (SizeReadyCallback cb : new ArrayList<>(cbs)) {
            cb.onSizeReady(width, height);  //回调给 SingleRequest
        }
    }
    

启动加载

在获取到宽高之后就将加载的工作交给 Engine 来完成,它首先会去 ActiveResources 中获取缓存,对 ActiveResources 不了解的可以看下 Glide源码解析之ActiveResources

如果 ActiveResources 没有缓存则接着去 MemoryCache 中获取缓存,对 MemoryCache 不了解的可以看下 Glide源码解析之MemoryCache

如果没有内存缓存,则将获取数据的工作交给 DecodeJob 去执行。

PS:很多人都写着 Glide 有三级缓存,分别是内存、磁盘、网络。我估计很多人都没有亲自看过源码,看着当初第一个人这样写,结果都跟着写,误导了一大片人。Glide 的确有三级缓存,但是前两个都是内存缓存,就是 ActiveResources 和 MemoryCache ,最后一级是磁盘缓存 ,是 DiskCache 。

    //SingleRequest
    public synchronized void onSizeReady(int width, int height) {
        if (status != Status.WAITING_FOR_SIZE) {
            return;
        }
        status = Status.RUNNING;

        float sizeMultiplier = requestOptions.getSizeMultiplier();
        this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
        this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

        loadStatus =
                engine.load(
                        glideContext,
                        model,
                        requestOptions.getSignature(),
                        this.width,
                        this.height,
                        requestOptions.getResourceClass(),
                        transcodeClass,
                        priority,
                        requestOptions.getDiskCacheStrategy(),
                        requestOptions.getTransformations(),
                        requestOptions.isTransformationRequired(),
                        requestOptions.isScaleOnlyOrNoTransform(),
                        requestOptions.getOptions(),
                        requestOptions.isMemoryCacheable(),
                        requestOptions.getUseUnlimitedSourceGeneratorsPool(),
                        requestOptions.getUseAnimationPool(),
                        requestOptions.getOnlyRetrieveFromCache(),
                        this,
                        callbackExecutor);  //执行点

        if (status != Status.RUNNING) {
            loadStatus = null;
        }
    }

    //Engine
    public synchronized <R> LoadStatus load(
            GlideContext glideContext,
            Object model,
            Key signature,
            int width,
            int height,
            Class<?> resourceClass,
            Class<R> transcodeClass,
            Priority priority,
            DiskCacheStrategy diskCacheStrategy,
            Map<Class<?>, Transformation<?>> transformations,
            boolean isTransformationRequired,
            boolean isScaleOnlyOrNoTransform,
            Options options,
            boolean isMemoryCacheable,
            boolean useUnlimitedSourceExecutorPool,
            boolean useAnimationPool,
            boolean onlyRetrieveFromCache,
            ResourceCallback cb,
            Executor callbackExecutor) {

        EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
                resourceClass, transcodeClass, options);

        //获取 ActiveResources 缓存
        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);     //执行点
        if (active != null) {
            cb.onResourceReady(active, DataSource.MEMORY_CACHE);
            return null;
        }

        //获取 MemoryCache 缓存
        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);               //执行点
        if (cached != null) {
            cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
            return null;
        }

        // jobs 是缓存 EngineJob 的
        EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
        if (current != null) {
            current.addCallback(cb, callbackExecutor);
            return new LoadStatus(cb, current);
        }

        EngineJob<R> engineJob =
                engineJobFactory.build(
                        key,
                        isMemoryCacheable,
                        useUnlimitedSourceExecutorPool,
                        useAnimationPool,
                        onlyRetrieveFromCache);

        //从磁盘或者数据源(比如网络)中获取资源,并进行转换和转码。
        DecodeJob<R> decodeJob =
                decodeJobFactory.build(
                        glideContext,
                        model,
                        key,
                        signature,
                        width,
                        height,
                        resourceClass,
                        transcodeClass,
                        priority,
                        diskCacheStrategy,
                        transformations,
                        isTransformationRequired,
                        isScaleOnlyOrNoTransform,
                        onlyRetrieveFromCache,
                        options,
                        engineJob);

        jobs.put(key, engineJob);

        engineJob.addCallback(cb, callbackExecutor);    //关注点
        engineJob.start(decodeJob);                     //执行点

        return new LoadStatus(cb, engineJob);
    }

从 ActiveResources 获取缓存

这个没啥好说的,有就返回,没有就 null 。

    private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {   // isMemoryCacheable 默认为 true
            return null;
        }
        EngineResource<?> active = activeResources.get(key);
        if (active != null) {
            active.acquire();
        }

        return active;
    }

从 MemoryCache 获取缓存

首先调用 remove() 获取缓存,这里之所以不调用 get() 是因为 ActiveResources 同样是内存缓存,它保存的是没被垃圾回收的资源。而 MemoryCache 是有内存大小限制的,将缓存存入 ActiveResources 后它也没必要再持有多一份。

如果有缓存的话则将资源包装成 EngineResource 返回。

    private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        if (!isMemoryCacheable) {
            return null;
        }

        EngineResource<?> cached = getEngineResourceFromCache(key); // 执行点
        if (cached != null) {
            cached.acquire();
            activeResources.activate(key, cached);                  // 将获取到的缓存存入 ActiveResources 中
        }
        return cached;
    }
    
    private EngineResource<?> getEngineResourceFromCache(Key key) {
        Resource<?> cached = cache.remove(key);                     // 关注点

        final EngineResource<?> result;
        if (cached == null) {
            result = null;
        } else if (cached instanceof EngineResource) {
            result = (EngineResource<?>) cached;
        } else {
            result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
        }
        return result;
    }

执行加载

首先会获取线程池,由于默认的磁盘缓存策略是允许解码缓存资源的,所以获取到的是 diskCacheExecutor 。这是一个核心线程数和最大线程数为1,工作队列为 PriorityBlockingQueue 的线程池。Glide 默认的 Priority 为 NORMAL ,我们在使用的时候可以通过设置 Priority 来改变加载的优先级。

接着把 DecodeJob 放进线程池执行,因为它实现了 Runnable 接口。DecodeJob 的具体调用过程请查看 Glide源码解析之DecodeJob

面试经常会被问到线程池,但是实际项目中却很少用到,因为大多数工作第三方库都帮我们做了,所以阅读源码的一个好处是能学到平时很少接触的知识。

    //EngineJob
    public synchronized void start(DecodeJob<R> decodeJob) {
        this.decodeJob = decodeJob;
        GlideExecutor executor = decodeJob.willDecodeFromCache()
                ? diskCacheExecutor
                : getActiveSourceExecutor();
        executor.execute(decodeJob);
    }
    
    //DecodeJob
    boolean willDecodeFromCache() {
        Stage firstStage = getNextStage(Stage.INITIALIZE);
        return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
    }
    
    
    private Stage getNextStage(Stage current) {
        switch (current) {
            case INITIALIZE:
                //默认 diskCacheStrategy 的类为 AUTOMATIC ,decodeCachedResource() 返回true
                return diskCacheStrategy.decodeCachedResource()
                        ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
            case RESOURCE_CACHE:
                return diskCacheStrategy.decodeCachedData()
                        ? 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);
        }
    }

获取数据完成

在 DecodeJob 获取到数据之后会通过 onResourceReady() 回调给 EngineJob ,首先对当前的状态进行检查,接着会将 resource 包装为一个 EngineResource ,这也不难解释为什么在上面 Engine 的 load() 里面从缓存获取的是 EngineResource 了。

然后对监听进行回调,这是由 Engine 实现的,在资源加载成功后对它进行缓存。

最后将 CallResourceReady 放进线程池执行。

    //EngineJob    
    public void onResourceReady(Resource<R> resource, DataSource dataSource) {
        synchronized (this) {
            this.resource = resource;
            this.dataSource = dataSource;
        }
        notifyCallbacksOfResult();  //执行点
    }
    
    void notifyCallbacksOfResult() {
        ResourceCallbacksAndExecutors copy;
        Key localKey;
        EngineResource<?> localResource;
        synchronized (this) {
            stateVerifier.throwIfRecycled();
            if (isCancelled) {
                resource.recycle();
                release();
                return;
            } else if (cbs.isEmpty()) {
                throw new IllegalStateException("Received a resource without any callbacks to notify");
            } else if (hasResource) {
                throw new IllegalStateException("Already have resource");
            }
            engineResource = engineResourceFactory.build(resource, isCacheable);
            
            hasResource = true;
            copy = cbs.copy();
            incrementPendingCallbacks(copy.size() + 1);

            localKey = key;
            localResource = engineResource;
        }

        listener.onEngineJobComplete(this, localKey, localResource);    //执行点

        //在 Engine 的 load() 中通过 engineJob.addCallback(cb, callbackExecutor); 添加进来
        for (final ResourceCallbackAndExecutor entry : copy) {
            entry.executor.execute(new CallResourceReady(entry.cb));    //执行点
        }
        decrementPendingCallbacks();
    }
    
    //Engine
    public synchronized void onEngineJobComplete(
            EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
        if (resource != null) {
            resource.setResourceListener(key, this);

            if (resource.isCacheable()) {
                activeResources.activate(key, resource);    //存进 ActiveResources 缓存
            }
        }

        jobs.removeIfCurrent(key, engineJob);
    }

回调数据

在上面我们知道会将 CallResourceReady 放进线程池执行,那么这个线程池是从哪来的呢。答案就是在一开始使用 into() 的时候进行赋值,一步步传递给 EnginJob的。这个线程池里面的 execute() 则是使用了主线程的 Handler 来进行 post() ,这样就把线程从子线程切换为主线程来执行了。

接着就会把资源通过回调传给 SingleRequest 了,它首先也会对资源进行检查,如果没有问题则最终会通过 onResourceReady() 来进行处理。

    //RequestBuilder
    public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
        return into(target, /*targetListener=*/ null, Executors.mainThreadExecutor());
    }
    
    public static Executor mainThreadExecutor() {
        return MAIN_THREAD_EXECUTOR;
    }
  
    private static final Executor MAIN_THREAD_EXECUTOR =
        new Executor() {
         private final Handler handler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(@NonNull Runnable command) {
          handler.post(command);
        }
    };
      
    
    //CallResourceReady
    public void run() {
        synchronized (EngineJob.this) {
            if (cbs.contains(cb)) {
                engineResource.acquire();
                callCallbackOnResourceReady(cb);    //执行点
                removeCallback(cb);
            }
            decrementPendingCallbacks();
        }
    }
    
    //EngineJob
    synchronized void callCallbackOnResourceReady(ResourceCallback cb) {
        try {
            cb.onResourceReady(engineResource, dataSource);     //执行点
        } catch (Throwable t) {
            throw new CallbackException(t);
        }
    }
    
    //SingleRequest
    public synchronized void onResourceReady(Resource<?> resource, DataSource dataSource) {
        stateVerifier.throwIfRecycled();
        loadStatus = null;
        if (resource == null) {
            GlideException exception = new GlideException("Expected to receive a Resource<R> with an "
                    + "object of " + transcodeClass + " inside, but instead got null.");
            onLoadFailed(exception);
            return;
        }

        Object received = resource.get();   //获取实际的资源,这里是 BitmapDrawable
        if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
            releaseResource(resource);
            GlideException exception = new GlideException("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."));
            onLoadFailed(exception);
            return;
        }

        if (!canSetResource()) {
            releaseResource(resource);
            status = Status.COMPLETE;
            return;
        }

        onResourceReady((Resource<R>) resource, (R) received, dataSource);  //执行点
    }
    

加载图片

如果有设置 Listener 的,则会先调用 Listener 的回调,没有则会将资源交给 Target 去加载。需要注意的是如果你设置的 Listener 在 onResourceReady() 中返回了 true ,则 Glide 认为你已经自己处理了,不会再帮你把图片加载到 ImageView 里的。

ImageView 接受到资源后,最终会调用 ImageView 的 setImageDrawable() 将图片显示处理,到此整个加载流程就结束了。

    private synchronized void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
        boolean isFirstResource = isFirstReadyResource();
        status = Status.COMPLETE;
        this.resource = resource;

        isCallingCallbacks = true;
        try {
            boolean anyListenerHandledUpdatingTarget = false;
            
            //这个 requestListeners 是使用时自己添加来监听加载成功与否的,比如
            /* 
             *  Glide.with(this)
             *  .load("")
             *  .listener(new RequestListener<Drawable>() {
             *      @Override
             *      public boolean onLoadFailed(@Nullable GlideException e, *Object model, Target<Drawable> target, boolean *isFirstResource) {
             *          return false;
             *      }
             *
             *      @Override
             *      public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
             *          return false;
             *      }
             *  })
             *  .centerCrop()
             *  .into(imageView);*/
            if (requestListeners != null) {
                for (RequestListener<R> listener : requestListeners) {
                    anyListenerHandledUpdatingTarget |=
                            listener.onResourceReady(result, model, target, dataSource, isFirstResource);
                }
            }
            
            // 在 RequestBuilder 的 into() 中可知 targetListener 为 null
            anyListenerHandledUpdatingTarget |=
                    targetListener != null
                            && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);

            if (!anyListenerHandledUpdatingTarget) {
                Transition<? super R> animation =
                        animationFactory.build(dataSource, isFirstResource);   // NO_ANIMATION
                target.onResourceReady(result, animation);  //执行点
            }
        } finally {
            isCallingCallbacks = false;
        }

        notifyLoadSuccess();
    }
    
    //ImageViewTarget
    public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
        if (transition == null || !transition.transition(resource, this)) {
            setResourceInternal(resource);  //执行点
        } else {
            maybeUpdateAnimatable(resource);
        }
    }
    
    private void setResourceInternal(@Nullable Z resource) {
        setResource(resource);              //执行点
        maybeUpdateAnimatable(resource);
    }
    
    //DrawableImageViewTarget
    protected void setResource(@Nullable Drawable resource) {
        view.setImageDrawable(resource);
    }
    

总结

Glide 源码解析系列到此就完结了,仍然记得一开始看源码的时候随着调用链的越来越深,看的云里雾里的,曾经一度想放弃。但是后来想明白了,人生会经历的挫折还很多,一遇到困难就想放弃,那还怎么成大器。所幸的是自己坚持了下来,并且阅读源码的能力又更上一层楼。

总有一天,你会感谢那个不曾放弃的自己!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343