Glide 资源加载流程分析

转载请标明地址 QuincySx: http://www.jianshu.com/p/eed7054e3722


这是 Glide 的第二篇,在上一篇中讲的都是大概流程,直接阅读起来可能比较困难,推荐结合源码浏览,在这一篇中就讲资源加载,所以贴上来的源码就会多一些。


public Target<TranscodeType> into(ImageView view) {
       .....
        //调用 (glide.buildImageViewTarget()
        return into(glide.buildImageViewTarget(view, transcodeClass));
    }

然后在调用以下方法 这个地方无论调什么都是生成 ViewTarget 就不细追究了

public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
        if (GlideDrawable.class.isAssignableFrom(clazz)) {
            return (Target<Z>) new GlideDrawableImageViewTarget(view);
        } else if (Bitmap.class.equals(clazz)) {
            return (Target<Z>) new BitmapImageViewTarget(view);
        } else if (Drawable.class.isAssignableFrom(clazz)) {
            return (Target<Z>) new DrawableImageViewTarget(view);
        } else {
            throw new IllegalArgumentException("Unhandled class: " + clazz
                    + ", try .as*(Class).transcode(ResourceTranscoder)");
        }
    }

创建完 ViewTarget 之后调用 init()

 public <Y extends Target<TranscodeType>> Y into(Y target) {
     ...
        //在 Target 中获取请求对象
        Request previous = target.getRequest();

        //如果有请求对象则把它清掉
        if (previous != null) {
            previous.clear();
            requestTracker.removeRequest(previous);
            previous.recycle();
        }
        
        //重新构建新的请求对象 并设置到 target 中,添加生命周期监听
        Request request = buildRequest(target);
        target.setRequest(request);
        lifecycle.addListener(target);
        //开始请求资源
        requestTracker.runRequest(request);

        return target;
    }

然后我们进去到 RequestTracker 的 runRequestra() 方法中

public void runRequest(Request request) {
        //先把请求添加到队列中,然后判断这个队列是不是暂停状态,暂停的话就放到暂停列表里,不是暂停的话就开始运行
        requests.add(request);
        if (!isPaused) {
            request.begin();
        } else {
            pendingRequests.add(request);
        }
    }

简单说一下 RequestTracker 请求队列,为什么这里要维护一个暂停或运行的状态呢,因为 RequestTracker 的生命周期是跟随 RequestManager 的,如果你看过 Glide 往页面中添加 Fragment 的那个步骤的话,你就会发现 RequestManager 是在 Fragment 中维护的,他同样监听这 Fragment 的显示状态,通俗点说就是一个显示的页面那么请求队列的状态就是运行,其他已不显示的页面 队列就会成为暂停状态,因为队列是监听 Fragment 的生命周期的,会动态调整每个页面请求队列的状态,已达到节省系统资源的目的。

接下来再看 request.begin() 方法

public void begin() {
        startTime = LogTime.getLogTime();
        if (model == null) {
            onException(null);
            return;
        }
        
        //更新请求的状态
        status = Status.WAITING_FOR_SIZE;
        //因为如果没有设置缩小 overrideWidth,overrideHeight 默认为 -1
        if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
            //如果设置了图片缩小,并且重新设置的宽高大于0 直接调用 onSizeReady
            onSizeReady(overrideWidth, overrideHeight);
        } else {
            //如果没有设置了图片缩小则去计算 View 本身的宽高
            target.getSize(this);
        }

        if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
            //设置占位图片
            target.onLoadStarted(getPlaceholderDrawable());
        }
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logV("finished run method in " + LogTime.getElapsedMillis(startTime));
        }
    }

再看 onSizeReady 方法之前我们先看一下 ViewTager 的 getSize 方法

public void getSize(SizeReadyCallback cb) {
            int currentWidth = getViewWidthOrParam();
            int currentHeight = getViewHeightOrParam();
            //获取View 的宽高
            if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {  
                //宽高不为0回调 onSizeReady
                cb.onSizeReady(currentWidth, currentHeight);
            } else {
                //宽高为0 就监听 View 的绘制之前 的事件再去获得宽高,回调进行加载
                // We want to notify callbacks in the order they were added and we only expect one or two callbacks to
                // be added a time, so a List is a reasonable choice.
                if (!cbs.contains(cb)) {
                    cbs.add(cb);
                }
                if (layoutListener == null) {
                    final ViewTreeObserver observer = view.getViewTreeObserver();
                    layoutListener = new SizeDeterminerLayoutListener(this);
                    observer.addOnPreDrawListener(layoutListener);
                }
            }
        }

//查看 onSizeReady 方法

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;
        //再看一下 engine.load
        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 方法 在调用 load 方法的时候,看到最后有一个 this 参数这是一个回调接口 这个地方注意一下

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) {
        Util.assertMainThread();
        long startTime = LogTime.getLogTime();

        final String id = fetcher.getId();
        //根据各个请求参数生成key
        EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
                loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
                transcoder, loadProvider.getSourceEncoder());
        //根据 key 在内存缓存中获取缓存
        EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
        //如果不为 null 则调用完成的回调接口
        if (cached != null) {
            cb.onResourceReady(cached);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from cache", startTime, key);
            }
            return null;
        }
        
        //获取现在活动的资源(加载相同的资源 防止已加载过的资源再加载一次)
        EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
        if (active != null) {
            cb.onResourceReady(active);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            }
            return null;
        }

        //jobs 是一个正在运行的任务集合,获取 key 相同的任务 防止有相同的请求正在运作
        EngineJob current = jobs.get(key);
        if (current != null) {
            current.addCallback(cb);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Added to existing load", startTime, key);
            }
            return new LoadStatus(cb, current);
        }

        //如果以上条件都不符合那就创建一个资源请求
        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 的时候他把 engineJob  也传了进去而他继承自
        //EngineRunnable.EngineRunnableManager 这个地方记住
        EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        //当前请求加入 jobs 维护
        jobs.put(key, engineJob);
        //添加回调到 GenericRequest 的方法
        engineJob.addCallback(cb);
        //运行任务 
        engineJob.start(runnable);

        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Started new load", startTime, key);
        }
        return new LoadStatus(cb, engineJob);
    }

接下来我们再看 engineJob.start(runnable); 方法

 public void start(EngineRunnable engineRunnable) {
        this.engineRunnable = engineRunnable;
        //因为 EngineRunnable 是继承的 Runnable 所以执行 EngineRunnable 的 run 方法
        //还要注意的是这个地方是 在 缓存服务中提交 了工作线程
        future = diskCacheService.submit(engineRunnable);
    }

我们接着看 EngineRunnable 的 run 方法 ,因为这个方法会走两次稍微注意一下

    @Override
    public void run() {
        if (isCancelled) {
            return;
        }

        Exception exception = null;
        Resource<?> resource = null;
        try {
            //开始获得数据
            resource = decode();
        } catch (OutOfMemoryError e) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "Out Of Memory Error decoding", e);
            }
            exception = new ErrorWrappingGlideException(e);
        } 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);
        }
    }

   //这个方法将会调用两次
   //第一次:因为在构造中 this.stage = Stage.CACHE 所以第一次肯定调用 decodeFromCache() 方法
   //第二次:因为 stage = Stage.SOURCE 状态改变 所以调用 decodeFromSource()
   private Resource<?> decode() throws Exception {
        if (isDecodingFromCache()) {
            return decodeFromCache();
        } else {
            return decodeFromSource();
        }
    }

    private Resource<?> decodeFromCache() throws Exception {
        Resource<?> result = null;
        try {
            result = decodeJob.decodeResultFromCache();
        } catch (Exception e) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Exception decoding result from cache: " + e);
            }
        }
        
        if (result == null) {
            result = decodeJob.decodeSourceFromCache();
        }
        //在缓存中读取资源
        return result;
    }

    private Resource<?> decodeFromSource() throws Exception {
        return decodeJob.decodeFromSource();
    }
    
    private void onLoadComplete(Resource resource) {
        manager.onResourceReady(resource);
    }
    
    //如果资源加载失败会把stage 加载状态修改 然后调用 回调接口去请求资源
    private void onLoadFailed(Exception e) {
        if (isDecodingFromCache()) {
            stage = Stage.SOURCE;
            manager.submitForSource(this);
        } else {
            //如果资源请求还失败就会抛出异常
            manager.onException(e);
        }
    }

如果加载资源的话 会调用 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();
            //它是通过加载器 fetcher 来获取资源 我们的环境是(加载 String 的 Url 并且没有配置第三方网络加载器)
            //那么 fetcher 在哪里来的呢 还记不记得 GenericRequest 的 onSizeReady() 方法 ,他是从 modelLoader.getResourceFetcher() 获取的 那么 modelLoader  哪来的呢
            //你可以去 Glide 构造里发现 register(String.class, InputStream.class, new StreamStringLoader.Factory()); 这么一句代码
            //我们看到了 StreamStringLoader 这个类 但是并没有发现 getResourceFetcher() 方法,我们看一下他的父类 StringLoader 现在发现了 getResourceFetcher 方法 在看到父类的时候我们又发现 父类是一个带参的构造,StreamStringLoader 在构造的时候查找了 Uri 的加载器给了父类 (查找就在流程我就不细分析了,自己看源码吧)
            //我们又在 Glide 构造里查到 Uri 的加载器是 StreamUriLoader 
            //进去有一看 StreamUriLoader 继承自构造 UriLoader 的时候 传入了  GlideUrl 类型的加载器
            //接着找到 HttpUrlGlideUrlLoader ,我们接着看 UriLoader 的 getResourceFetcher() 方法 他判断了资源是本地资源还是网络资源,本地资源就直接加载,方法自己看一下吧,否则就调用 HttpUrlGlideUrlLoader 进行网络加载 
            // HttpUrlGlideUrlLoader 的 getResourceFetcher 是个 HttpUrlFetcher 调用 loadData 进行网络加载,怎么加载的代码自己看一下吧,我就不贴了
            //这个地方比较乱,大家慢慢理一下
            final A data = fetcher.loadData(priority);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Fetched data", startTime);
            }
            if (isCancelled) {
                return null;
            }
            //如果打开磁盘缓存就将在的资源缓存起来,然后再拿出来 ,并且装换成 Resource
            //如果没有开启磁盘缓存 就直接转换为 Resource
            decoded = decodeFromSourceData(data);
        } finally {
            fetcher.cleanup();
        }
        return decoded;
    }

图片的变换下篇文章讲,如果没有什么妖蛾子的话该调用 onLoadComplete() 方法,回调 EngineJob.onResourceReady(resource); 的方法 ,接着往下跟踪发现来到了如下方法

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;

        //记录是否回调的标志 此时 acquire 为 1
        engineResource.acquire();

        //回调 Engine 的 onEngineJobComplete 的方法做了这么几件事件事
        //1. 添加 Engine 的回调
        //2. 缓存资源
        //3. 将任务在 jobs 中删除任务
        listener.onEngineJobComplete(key, engineResource);

        //调用所有等待此资源加载的回调
        for (ResourceCallback cb : cbs) {
            if (!isInIgnoredCallbacks(cb)) {
                //回调一次 acquire 的数值加 1
                engineResource.acquire();
                //回调 GenericRequest 的 onResourceReady  代码看下方
                cb.onResourceReady(engineResource);
            }
        }
        
        //查看acquire 的值减 1 是否等于0,如果等于零,就说明此资源没有任何回调,则
        回调Engine 的 onResourceReleased 在活动资源缓存中删除,并且判断是否缓存到内存中,然后清理释放资源
        engineResource.release();
    }

接下来看 GenericRequest 的 onResourceReady() 方法
资源有了剩下的就是将它放到 imageView 上

public void onResourceReady(Resource<?> resource) {
        ...
        Object received = resource.get();
        if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
            //如果资源为 NULL 则清除掉活动资源,并缓存
            releaseResource(resource);
            ...
            return;
        }

        if (!canSetResource()) {
            releaseResource(resource);
            // We can't set the status to complete before asking canSetResource().
            status = Status.COMPLETE;
            return;
        }

        onResourceReady(resource, (R) received);
    }

继续查看 onResourceReady 方法

private void onResourceReady(Resource<?> resource, R result) {
        // We must call isFirstReadyResource before setting status.
        boolean isFirstResource = isFirstReadyResource();
        status = Status.COMPLETE;
        this.resource = resource;

        //往上查 requestListener 这个参数,发现是在 GenericRequestBuilder 中的 buildRequestRecursive 方法中,咱们的情景设置是没有设置缩放,所以 requestListener 是 null
 的
        //requestListener 可以在外面设置Glide 加载失败或成功的监听
        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);
       
}

如果资源加载失败则回调 GenericRequest 的 onException 方法

status = Status.FAILED; //修改状态  
//TODO: what if this is a thumbnail request?
//requestListener 可以在外面设置Glide 加载失败或成功的监听
if (requestListener == null || !requestListener.onException(e, model, target, isFirstReadyResource())) {
      setErrorPlaceholder(e); //给view设置错误占位符
}

到此资源的获取加载流程就完了


小结

到此我们已经简单的分析了一遍图片资源在网络上加载,并且设置到 view 中,欢迎大家品尝,并提出意见

注:
本篇基于 Glide 3.8.0

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,472评论 25 707
  • Glide遇坑记之背景API性能 Glide遇坑记之阐述 Glide遇坑记之分析Glide源码分析(一)Glide...
    Mr槑阅读 5,379评论 1 5
  • 声明 本文章是基于 glide 3.6.1的 个各类的功能介绍 1.Glide:向外暴露单例静态接口,构建Requ...
    河里的枇杷树阅读 1,138评论 0 3
  • 转载请标明地址 QuincySx: http://www.jianshu.com/p/cf8f8f90f621 这...
    QuincySx阅读 1,406评论 0 1
  • 现在的天气实在是尴尬,白天好热。导致大地的温度到半夜都降不下去。现在睡觉又不敢不盖被子,半夜之后气温有会降下来,即...
    xiao钱钱阅读 181评论 0 0