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

上一篇:PostgreSQL PostGIS so 预加载(preload) 性能提升 - 暨什么动态库建议预加载


下一篇:Photoshop 快速给人物加上红润色调