Glide 资源加载流程分析

简介: 转载请标明地址 QuincySx: http://www.jianshu.com/p/eed7054e3722这是 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

目录
相关文章
Glide源码学习八:实现带进度的Glide图片加载功能
Glide源码学习八:实现带进度的Glide图片加载功能
|
1月前
|
缓存 前端开发 UED
前端 8 种图片加载优化方案梳理
本文首发于微信公众号“前端徐徐”,详细探讨了现代网页设计中图片加载速度优化的重要性及方法。内容涵盖图片格式选择(如JPEG、PNG、WebP等)、图片压缩技术、响应式图片、延迟加载、CDN使用、缓存控制、图像裁剪与缩放、Base64编码等前端图片优化策略,旨在帮助开发者提升网页性能和用户体验。
184 0
|
3月前
|
缓存 前端开发 网络协议
性能优化|几个方法让图片加载更快一些
对电商网页的性能而言,图片优化是至关重要的事情,本文就此探讨了一些简单、可靠的图片优化手段。
|
5月前
|
缓存 前端开发 UED
前端优化:首屏加载速度的实践
随着互联网技术的飞速发展,前端网页逐渐取代了传统客户端成为用户获取信息、进行交互的重要渠道,但是网页也有常见的弊端,比如网页首屏加载速度的快慢直接影响着用户体验,那么如何提升网页的首屏加载速度,成为了前端开发者必须面对的问题。本文将从多图片懒加载、避免用户多次点击请求以及骨架屏原理等方面,简单分享一下前端优化首屏加载速度的策略优化。欢迎大家在评论区留言交流。
80 2
前端优化:首屏加载速度的实践
|
6月前
|
前端开发 JavaScript 开发者
深入理解前端性能优化:从加载到渲染的全流程分析
前端性能优化是Web开发中的关键一环。本文将从加载资源、解析HTML、执行JavaScript、样式计算、布局和渲染等多个方面深入探讨前端性能优化的全流程,为开发者提供全面的技术指南和实用建议。
|
Web App开发 存储 缓存
CocosCreator 引擎资源加载与释放原理简析
CocosCreator 引擎资源加载与释放原理简析
462 0
|
移动开发 JavaScript Android开发
webview 优化加载 H5 应用(尝试方案)
webview 优化加载 H5 应用(尝试方案)
376 0
|
XML 存储 Android开发
【Android 性能优化】布局渲染优化 ( CPU 与 GPU 架构分析 | 安卓布局显示流程 | 视觉与帧率分析 | 渲染超时卡顿分析 | 渲染过程与优化 )
【Android 性能优化】布局渲染优化 ( CPU 与 GPU 架构分析 | 安卓布局显示流程 | 视觉与帧率分析 | 渲染超时卡顿分析 | 渲染过程与优化 )
462 0
【Android 性能优化】布局渲染优化 ( CPU 与 GPU 架构分析 | 安卓布局显示流程 | 视觉与帧率分析 | 渲染超时卡顿分析 | 渲染过程与优化 )
|
缓存 编解码 JavaScript
Flutter 图片解码与缓存管理研究
图片解码和缓存管理是渲染引擎的一个重要模块,这是因为图片解码的耗时很长,特别是对于设计为跨平台的通用渲染引擎来说,依赖于CPU来做图片解码,会消耗大量的CPU时间,并且图片解码后占用的内存很大,一张 1024x1024 分辨率的图片解码后就需要 4M 内存(除非硬件支持实时生成无损压缩格式纹理,通常这也不在通用渲染引擎的考虑范围之内)。所以一个设计良好的图片解码和缓存管理模块需要平衡很多不同的因素
1084 0
|
存储 消息中间件 监控