Glide源码解析之SourceGenerator

SourceGenerator

在上文 Glide 源码解析之 ResourceCacheGenerator 我们分析了从磁盘获取资源,而 SourceGenerator 的任务则是从来源地获取资源,比如说传入的是 url ,则是从网络获取,这里分析的是从网络获取的情况。

在 startNext() 中,首先会判断 dataToCache 是否为 null ,这个是获取到资源后才赋值的,所以一开始是 null 。接着判断 sourceCacheGenerator 是否为 null ,这个是当 cacheData() 的时候才赋值的,所以也是 null 。

然后会进入循环中,这里的 DataFetcher 实际为 HttpUrlFetcher ,资源的提取过程就是由它执行。 HttpUrlFetcher 的具体的获取过程可以看下 Glide 源码解析之 DecodeHelper

class SourceGenerator implements DataFetcherGenerator,
        DataFetcher.DataCallback<Object>,
        DataFetcherGenerator.FetcherReadyCallback {

    private final DecodeHelper<?> helper;
    private final FetcherReadyCallback cb;

    private int loadDataListIndex;
    private DataCacheGenerator sourceCacheGenerator;
    private Object dataToCache;
    
    @Override
    public boolean startNext() {
        //第一次执行的时候是等于 null 的
        if (dataToCache != null) {
            Object data = dataToCache;
            dataToCache = null;
            cacheData(data);
        }

        //第一次执行的时候是等于 null 的
        if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
            return true;
        }
        sourceCacheGenerator = null;

        loadData = null;
        boolean started = false;
        
        while (!started && hasNextModelLoader()) {
            loadData = helper.getLoadData().get(loadDataListIndex++);
            if (loadData != null
                    && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
                    || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
                started = true;
                //此处为实际发起请求的部分
                loadData.fetcher.loadData(helper.getPriority(), this);
            }
        }
        return started;
    }
    
}

资源的提取

使用HttpURLConnection来进行网络连接,获取输入流。

最后就会把结果回调给 callback 了,这个 callback 是由 SourceGenerator 来实现的,也就是加载完后会通知到 SourceGenerator 。

    //HttpUrlFetcher
    @Override
    public void loadData(@NonNull Priority priority,
                         @NonNull DataCallback<? super InputStream> callback) {
        try {
            InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
            callback.onDataReady(result);
        } catch (IOException e) {
            callback.onLoadFailed(e);
        } 
    }
    
    private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
                                              Map<String, String> headers) throws IOException {
        if (redirects >= MAXIMUM_REDIRECTS) {
            //重定向次数限制
            throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
        } else {
            try {
                if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
                    throw new HttpException("In re-direct loop");
                }
            } catch (URISyntaxException e) {
                // Do nothing, this is best effort.
            }
        }

        urlConnection = connectionFactory.build(url);   // (HttpURLConnection) url.openConnection();
        
        //添加Header
        for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
            urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
        }
        urlConnection.setConnectTimeout(timeout);
        urlConnection.setReadTimeout(timeout);
        urlConnection.setUseCaches(false);
        urlConnection.setDoInput(true);

        // 关闭网络连接的重定向,如果需要重定向会递归调用 loadDataWithRedirects() 自己处理
        urlConnection.setInstanceFollowRedirects(false);

        urlConnection.connect();
        stream = urlConnection.getInputStream();
        if (isCancelled) {
            return null;
        }
        
        final int statusCode = urlConnection.getResponseCode();
        if (isHttpOk(statusCode)) {
            //返回状态码正常,则返回结果
            return getStreamForSuccessfulRequest(urlConnection);
        } else if (isHttpRedirect(statusCode)) {
            //需要重定向
            String redirectUrlString = urlConnection.getHeaderField("Location");
            if (TextUtils.isEmpty(redirectUrlString)) {
                throw new HttpException("Received empty or null redirect url");
            }
            URL redirectUrl = new URL(url, redirectUrlString);
            // 关闭输入流和连接,重定向时会重新连接
            cleanup();
            //开始重定向
            return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
        } else if (statusCode == INVALID_STATUS_CODE) {
            throw new HttpException(statusCode);
        } else {
            throw new HttpException(urlConnection.getResponseMessage(), statusCode);
        }
    }

    //获取网络连接返回的输入流
    private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
            throws IOException {
        if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
            int contentLength = urlConnection.getContentLength();
            stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);   //new ContentLengthInputStream(other, contentLength);
        } else {
            stream = urlConnection.getInputStream();
        }
        return stream;
    }
    @Override
    public void onDataReady(Object data) {
        //默认的 DiskCacheStrategy 为 DiskCacheStrategy.AUTOMATIC ,在 BaseRequestOptions中定义
        //所以默认是会缓存远程资源的
        DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
        if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
            //把资源赋值给dataToCache
            dataToCache = data;   
            
            // 可能是在其他线程回调的,在执行接下来的任务时先切回到Glide的线程
            cb.reschedule();
        } else {
            cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
                    loadData.fetcher.getDataSource(), originalKey);
        }
    }

资源获取完成

资源获取完成后会回调到 SourceGenerator 的 onDataReady(),如果是可以进行磁盘缓存的话(默认可以)则会回调给 DecodeJob 的 reschedule(),接着交给 EngineJob 使用线程池来重新执行 DecodeJob 。最后又会执行到 SourceGenerator 的startNext()方法。

    @Override
    public void onDataReady(Object data) {
        DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
        if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
            dataToCache = data;     //赋值给dataToCache
            //可能会在其他线程回回调,在执行其他东西之前先回调到 Glide 的线程
            cb.reschedule();
        } else {
            cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
                    loadData.fetcher.getDataSource(), originalKey);
        }
    }
    
    //DecodeJob
    @Override
    public void reschedule() {
        runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
        callback.reschedule(this);
    }
    
    //EngineJob
    @Override
    public void reschedule(DecodeJob<?> job) {
        getActiveSourceExecutor().execute(job);
    }
    
    //DecodeJob
    @Override
    public void run() {
        runWrapped();
    }
    
    private void runWrapped() {
        switch (runReason) {
            case SWITCH_TO_SOURCE_SERVICE:
                runGenerators();
                break;
        }
    }
    
    private void runGenerators() {
        boolean isStarted = false;
        while (!isCancelled && currentGenerator != null
                && !(isStarted = currentGenerator.startNext())) {
        }
    }

缓存资源

由于在上面的 onDataReady()后给 dataToCache 赋值了,所以这里会进入到 cacheData() 里面。在里面将资源写进磁盘缓存,然后给 sourceCacheGenerator 赋值为 DataCacheGenerator ,接着调用它的 startNext() 去磁盘获取资源。

为什么 SourceGenerator 获取到资源后不自己缓存后就返回还要调用 DataCacheGenerator 去再获取一遍呢?我想主要是为了使各个类遵循单一职责,DataCacheGenerator是负责从磁盘获取资源的则交给它去完成。在 onDataReady() 中,当不需要缓存的时候,SourceGenerator 也是直接回调给 DecodeJob 的。

    @Override
    public boolean startNext() {
        if (dataToCache != null) {
            Object data = dataToCache;
            dataToCache = null;
            cacheData(data);
        }

        if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
            return true;
        }
    }
    
    /**
     * 将数据缓存至磁盘缓存
     */
    private void cacheData(Object dataToCache) {
        try {
            Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
            DataCacheWriter<Object> writer =
                    new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
            originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
            helper.getDiskCache().put(originalKey, writer);     //写进磁盘缓存
        } finally {
            loadData.fetcher.cleanup();
        }

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