Glide 资源加载流程分析

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


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


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

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

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

创建完 ViewTarget 之后调用 init()

 public > 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 modelLoader = loadProvider.getModelLoader();
        //获取资源加载器
        final DataFetcher dataFetcher = modelLoader.getResourceFetcher(model, width, height);

        if (dataFetcher == null) {
            onException(new Exception("Failed to load model: \'" + model + "\'"));
            return;
        }

        //变换的处理(暂不介绍)
        ResourceTranscoder 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  LoadStatus load(Key signature, int width, int height, DataFetcher fetcher,
            DataLoadProvider loadProvider, Transformation transformation, ResourceTranscoder 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 decodeJob = new DecodeJob(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 decodeFromSource() throws Exception {
        //获取资源
        Resource decoded = decodeSource();
        //资源转换以后再说不在这篇文章的讨论范围内
        return transformEncodeAndTranscode(decoded);
    }

我们先看获取资源的方法

private Resource decodeSource() throws Exception {
        Resource 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 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 资源加载流程分析)