Glide拆解2-ModelLoader模型加载器与其注册机

上一篇介绍为了Glide 当中的缓存机制,接下来就要介绍Glide当中对整个图片的加载过程了
没错,ModelLoader就是对图片加载过程的封装;
接下来提出两个问题,我们带着这两个问题继续后面的讲解;

//sd path
Glide.width(this).load("file://"+Environment.getExternalStorageDirectory().getPath()+"/123.png");
//assets 下
Glide.width(this).load("file://android_asset/123.png");
// 网络图片 scheme 为http
Glide.width(this).load("http://img.my.csdn.NET/123.png");
// 网络图片 scheme 为https
Glide.with(this).load("https://img.my.csdn.NET/123.png");

上面这几种load方式,最终Glide都能成功的将图片加载出来并且正常的展示,那么问题来了,都是为字符串,Glide是怎么知道当前要从assets下读取,网络上读取,还是从sd卡中读取呢?
带着这个问题,我们开始对Glide进行拆解,了解图片加载过程的内部工作机制。

ModelLoader

/**
* Model和Data为定义的两个泛型
* Model代表了加载来源模型:Uri、File等
* Data则代表加载模型后的数据:InputSream、byte[]等
*/
public interface ModelLoader<Model, Data> {
    //ModelLoader工厂
    interface ModelLoaderFactory<Model, Data> {
        //参数modelLoaderRegistry为注册机,具体左后后面会讲到
        ModelLoader<Model, Data> build(ModelLoaderRegistry modelLoaderRegistry);
    }


    //我们最终由Model经过转换之后,最终输出数据类型即为LoadData,
    //LoadData内的fetcher封装了对数据的操作
    class LoadData<Data> {
        //key Glide缓存会用到Key
        public final Key sourceKey;
        //数据加载器
        public final DataFetcher<Data> fetcher;
        public LoadData(Key sourceKey, DataFetcher<Data> fetcher) {
            this.sourceKey = sourceKey;
            this.fetcher = fetcher;
        }
    }
    /**
     * 加载结构,将输入的model转换为目标数据类型,即Data
     * @param model
     * @return
     */
    LoadData<Data> buildLoadData(Model model);
    /**
     * 此Loader是否可以处理对应Model,
     * 即该Model类型是否符合我们当前数据类型
     * @param model
     * @return
     */
    boolean handles(Model model);
}

使用Glide,图片可能存在于文件、网络等地方。其中Model则代表了加载来源模型:Uri、File等;Data则代表加载模型后的数据:InputSream、byte[]等。
通过buildLoadData函数创建LoadData。LoadData中的DataFetcher如下:

public interface DataFetcher<T> {
    interface DataFetcherCallback<T> {
        /**
         * 数据加载完成
         * @param data
         */
        void onFetcherReady(T data);
        /**
         * 数据加载失败
         * @param e
         */
        void onLoadFailed(Exception e);
    }
    /**
     * 加载数据
     * @param callback
     */
    void loadData(DataFetcherCallback<? super T> callback);
    /**
     * 取消
     */
    void cancel();
    /**
     * 数据类型
     * @return
     */
    Class<T> getDataClass();
}

HttpUriLoader实现类,已网络图片加载为例

public class HttpUriLoader implements ModelLoader<Uri, InputStream> {
    /**
     * http类型的uri此loader才支持
     *
     * @param uri
     * @return
     */
    @Override
    public boolean handles(Uri uri) {
        String scheme = uri.getScheme();
        //因为来源于文件也可以转换为Uri,所以这个时候通过scheme来判断当前图片是否来源于网络
        return scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https");
    }


    /**
    * 将Uri转化为我们要的目标数据类型,并通过HttpUriFetcher对数据进行处理
    */
    @Override
    public LoadData<InputStream> buildData(Uri uri) {
        return new LoadData<InputStream>(new ObjectKey(uri), new HttpUriFetcher(uri));
    }

    public static class Factory implements ModelLoaderFactory<Uri, InputStream> {
        @Override
        public ModelLoader<Uri, InputStream> build(ModelLoaderRegistry registry) {
            return new HttpUriLoader();
        }
    }
}

HttpUriFetcher实现类,已网络图片加载为例

public class HttpUriFetcher implements DataFetcher<InputStream> {
    private final Uri uri;
    private boolean isCanceled;
    public HttpUriFetcher(Uri uri) {
        this.uri = uri;
    }
    @Override
    public void loadData(DataFetcherCallback<InputStream> callback) {
        HttpURLConnection conn = null;
        InputStream is = null;
        try {
            URL url = new URL(uri.toString());
            conn = (HttpURLConnection) url.openConnection();
            conn.connect();
            is = conn.getInputStream();
            int responseCode = conn.getResponseCode();
            if (isCanceled) {
                return;
            }
            if (responseCode == HttpURLConnection.HTTP_OK) {
                callback.onFetcherReady(is);
            } else {
                callback.onLoadFaled(new RuntimeException(conn.getResponseMessage()));
            }
        } catch (Exception e) {
            callback.onLoadFaled(e);
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != conn) {
                conn.disconnect();
            }
        }
    }

    @Override
    public void cancel() {
        isCanceled = true;
    }
}

FileUriLoader实现类,已文件类型为例

public class FileUriLoader implements ModelLoader<Uri, InputStream> {
    //由于该Uri来源于文件,所以需要通过ContentResolver进行加载
    private final ContentResolver contentResolver;
    public FileUriLoader(ContentResolver contentResolver) {
        this.contentResolver = contentResolver;
    }


    //判断当前数据是否符合FileUriLoader的条件
    @Override
    public boolean handles(Uri uri) {
//        ContentResolver.SCHEME_FILE
        return ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme());
    }
    @Override
    public LoadData<InputStream> buildData(Uri uri) {
        //生成LoadData的时候需要传入对应的Key,缓存的时候需要用到
        return new LoadData<>(new ObjectKey(uri), new FileUriFetcher(uri, contentResolver));
    }

    public static class Factory implements ModelLoaderFactory<Uri, InputStream> {
        private final ContentResolver contentResolver;
        public Factory(ContentResolver contentResolver) {
            this.contentResolver = contentResolver;
        }
        @Override
        public ModelLoader<Uri, InputStream> build(ModelLoaderRegistry registry) {
            return new FileUriLoader(contentResolver);
        }
    }

}

FileUriFetcher实现类,以文件类型为例

public class FileUriFetcher implements DataFetcher<InputStream> {
    private final Uri uri;
    private final ContentResolver cr;

    public FileUriFetcher(Uri uri, ContentResolver cr) {
        this.uri = uri;
        this.cr = cr;
    }

    @Override
    public void loadData(DataFetcherCallback<InputStream> callback) {
        InputStream is = null;
        try {
            is = cr.openInputStream(uri);
            callback.onFetcherReady(is);
        } catch (FileNotFoundException e) {
            callback.onLoadFaled(e);
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void cancel() {

    }
}

如果Mode和Data能同时对应多个modelLoader的时候我们该怎么办呢,这个时候我们引入了MultiModelLoader,它是包含多个modelLoader的装饰类

public class MultiModelLoader<Model, Data> implements ModelLoader<Model, Data> {

    //FileUriModelLoader HttpUriModelLoader
    private final List<ModelLoader<Model, Data>> modelLoaders;

    public MultiModelLoader(List<ModelLoader<Model, Data>> modelLoaders) {
        this.modelLoaders = modelLoaders;
    }

    @Override
    public boolean handles(Model model) {
        for (ModelLoader<Model, Data> modelLoader : modelLoaders) {
            if (modelLoader.handles(model)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public LoadData<Data> buildData(Model model) {
        for (int i = 0; i < modelLoaders.size(); i++) {
            ModelLoader<Model, Data> modelLoader = modelLoaders.get(i);
            // Model=>Uri:http
            if (modelLoader.handles(model)){
                LoadData<Data> loadData = modelLoader.buildData(model);
                return loadData;
            }
        }
        return null;
    }
}

在Glide的使用过程中,如果针对文件或者网络图片全都转成Uri的方式未免会有些比较麻烦,我们用的最多的还是直接给个资源路径,也就是Model为String类型,这样相对于Uri来说要方便很多,在这里,我们要介绍另一个常用的ModelLoader,也就是StringModelLoader

public class StringModelLoader implements ModelLoader<String, InputStream> {
    private final ModelLoader<Uri, InputStream> loader;

    public StringModelLoader(ModelLoader<Uri, InputStream> loader) {
        this.loader = loader;
    }

    @Override
    public boolean handles(String s) {
        return true;
    }

    /**
    * 判断当前model是否为文件路径,并且将其转换为对应的Uri,然后再将其设置给相应的ModelLoader进行处理
    * 有可能你会有疑问,StringModelLoader为什么自己不进行处理还有交给其他的loader进行处理呢,
    * 在这里我们是为了实现更好的插拔,不然的话就得写一堆的逻辑判断语句了
    */
    @Override
    public LoadData<InputStream> buildData(String model) {
        Uri uri;        
        if (model.startsWith("/")) {
            uri = Uri.fromFile(new File(model));
        } else {
            uri = Uri.parse(model);
        }
        return loader.buildData(uri);
    }

    /**
    * 工厂类,构建ModelLoader实例,StringModelLoader的构建方式和其他的构建方式有点不同,在这里我们是通过注册机进行注册的,因为针对<Url.class,InputStream.class>
    * 我们可以有多种modelLoader与其对应,所以我们这边是通过registry.build()获取到这个modelLoader
    */
    public static class StreamFactory implements ModelLoaderFactory<String, InputStream> {

        @Override
        public ModelLoader<String, InputStream> build(ModelLoaderRegistry registry) {
            return new StringModelLoader(registry.build(Uri.class, InputStream.class));
        }
    }
}

注册机--ModelLoaderRegistry

ModelLoaderRegister负责注册所有的ModelLoader

public class ModelLoaderRegistry {
    private List<Entry<?, ?>> entries = new ArrayList<>();

    /**
     * 注册 Loader
     *
     * @param modelClass 数据来源类型 String File
     * @param dataClass  数据转换后类型 加载后类型 String/File->InputStream
     * @param factory    创建ModelLoader的工厂
     * @param <Model>
     * @param <Data>
     */
    public synchronized <Model, Data> void add(Class<Model> modelClass, Class<Data> dataClass,
                                               ModelLoader.ModelLoaderFactory<Model, Data> factory) {
        entries.add(new Entry<>(modelClass, dataClass, factory));
    }

    /**
     * 获得 对应 model与data类型的 modelloader
     *
     * @param modelClass
     * @param dataClass
     * @param <Model>
     * @param <Data>
     * @return
     */
    public <Model, Data> ModelLoader<Model, Data> build(Class<Model> modelClass, Class<Data> dataClass) {
        List<ModelLoader<Model, Data>> loaders = new ArrayList<>();
        for (Entry<?, ?> entry : entries) {
            //找到我们需要的Model与Data类型的Loader
            if (entry.handles(modelClass, dataClass)) {
                loaders.add((ModelLoader<Model, Data>) entry.factory.build(this));
            }
        }
        //找到多个匹配的loader
        if (loaders.size() > 1) {
            return new MultiModelLoader<>(loaders);
        } else if (loaders.size() == 1) {
            return loaders.get(0);
        }
        throw new RuntimeException("No Match:" + modelClass.getName() + " Data:" + dataClass.getName());
    }

    /**
     * 查找匹配的 Model类型的ModelLoader
     * @param modelClass
     * @param <Model>
     * @return
     */
    public <Model> List<ModelLoader<Model, ?>> getModelLoaders(Class<Model> modelClass) {
        List<ModelLoader<Model, ?>> loaders = new ArrayList<>();
        for (Entry<?, ?> entry : entries) {
            if (entry.handles(modelClass)) {
                loaders.add((ModelLoader<Model, ?>) entry.factory.build(this));
            }
        }
        return loaders;
    }

    private static class Entry<Model, Data> {
        Class<Model> modelClass;
        Class<Data> dataClass;
        ModelLoader.ModelLoaderFactory<Model, Data> factory;

        public Entry(Class<Model> modelClass, Class<Data> dataClass, ModelLoader.ModelLoaderFactory<Model, Data> factory) {
            this.modelClass = modelClass;
            this.dataClass = dataClass;
            this.factory = factory;
        }

        boolean handles(Class<?> modelClass, Class<?> dataClass) {
            // A.isAssignableFrom(B) B和A是同一个类型 或者 B是A的子类
            return this.modelClass.isAssignableFrom(modelClass) && this.dataClass.isAssignableFrom(dataClass);
        }

        boolean handles(Class<?> modelClass) {
            // A.isAssignableFrom(B) B和A是同一个类型 或者 B是A的子类
            return this.modelClass.isAssignableFrom(modelClass);
        }
    }
}

使用之前调用add函数对需要组装的Loader进行注册

.add(String.class, InputStream.class, new StringLoader.StreamFactory())
.add(Uri.class, InputStream.class, new HttpUriLoader.Factory())
.add(Uri.class, InputStream.class, new UriFileLoader.Factory(contentResolver))
.add(File.class, InputStream.class, new FileLoader.Factory())

当需要加载一个String类型的来源则会查找到StringLoader。但是一个String它可能属于文件地址也可能属于一个网络地址,所以StringLoader.StreamFactory在创建StringLoader的时候,它会根据Uri与InputStream类型创建一个MultiModelLoader对象,这个MultiModelLoader中存在一个集合,只要集合中存在一个Loader能够处理对应的Model,那么这个MultiModelLoader就可以处理对应的Model。

所以当需要处理String类型的来源的时候,会创建一个MultiModelLoader,这个MultiModelLoader中包含了一个HttpUriLoader与一个UriFileLoader。当字符串是以http或者https开头则能由HttpUriLoader处理,否则交给UriFileLoader来加载。


image.png

定位到Loader,通过buildData获得一个LoadData,使用其中的Fetcher就可以加载到一个泛型Data类型的数据,比如InputStream。然后通过注册的解码器解码InputStream获得Bitmap(解码器的注册相对于Loader更简单)。

看到这里,modelLoader的工作机制已经讲完了

Demo

public class LoaderTest {

    private static final String TAG = "LoaderTest";

    public static void testFindLoader(Context context) {


        ModelLoaderRegistry loaderRegistry = new ModelLoaderRegistry();
        //注册各种ModelLoader
        loaderRegistry.add(String.class, InputStream.class, new StringModelLoader.StreamFactory());
        loaderRegistry.add(Uri.class, InputStream.class, new FileUriLoader.Factory(context.getContentResolver()));
        loaderRegistry.add(Uri.class, InputStream.class, new HttpUriLoader.Factory());
        loaderRegistry.add(File.class, InputStream.class, new FileLoader.Factory());

//        context.getAssets().open("/a/b.png");

        List<ModelLoader<String, ?>> modelLoaders = loaderRegistry.getModelLoaders(String.class);
        ModelLoader<String, ?> modelLoader = modelLoaders.get(0);
        //HttpUriFetcher
        final ModelLoader.LoadData<InputStream> loadData = (ModelLoader.LoadData<InputStream>) modelLoader.buildData("https://ss1.bdstatic" +
                ".com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2669567003," +
                "3609261574&fm=27&gp=0.jpg22222222asads");

        new Thread() {
            @Override
            public void run() {
                loadData.fetcher.loadData(new DataFetcher.DataFetcherCallback<InputStream>() {
                    @Override
                    public void onFetcherReady(InputStream o) {
                        try {
                            Log.e(TAG, "ready:" + o.available());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onLoadFaled(Exception e) {
                        e.printStackTrace();
                    }
                });
            }
        }.start();
//        modelLoader.buildData("/a/b.png");
    }


    public void test(Context context) {
        Uri uri = Uri.parse("http://www.xxx.xxx");
        HttpUriLoader httpUriLoader = new HttpUriLoader();
        ModelLoader.LoadData<InputStream> loadData = httpUriLoader.buildData(uri);
        //
        loadData.fetcher.loadData(new DataFetcher.DataFetcherCallback<InputStream>() {

            @Override
            public void onFetcherReady(InputStream is) {
                //
                BitmapFactory.decodeStream(is);
            }

            @Override
            public void onLoadFaled(Exception e) {
                e.printStackTrace();
            }
        });


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

推荐阅读更多精彩内容