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 {
    //ModelLoader工厂
    interface ModelLoaderFactory {
        //参数modelLoaderRegistry为注册机,具体左后后面会讲到
        ModelLoader build(ModelLoaderRegistry modelLoaderRegistry);
    }


    //我们最终由Model经过转换之后,最终输出数据类型即为LoadData,
    //LoadData内的fetcher封装了对数据的操作
    class LoadData {
        //key Glide缓存会用到Key
        public final Key sourceKey;
        //数据加载器
        public final DataFetcher fetcher;
        public LoadData(Key sourceKey, DataFetcher fetcher) {
            this.sourceKey = sourceKey;
            this.fetcher = fetcher;
        }
    }
    /**
     * 加载结构,将输入的model转换为目标数据类型,即Data
     * @param model
     * @return
     */
    LoadData 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 {
    interface DataFetcherCallback {
        /**
         * 数据加载完成
         * @param data
         */
        void onFetcherReady(T data);
        /**
         * 数据加载失败
         * @param e
         */
        void onLoadFailed(Exception e);
    }
    /**
     * 加载数据
     * @param callback
     */
    void loadData(DataFetcherCallback callback);
    /**
     * 取消
     */
    void cancel();
    /**
     * 数据类型
     * @return
     */
    Class getDataClass();
}

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

public class HttpUriLoader implements ModelLoader {
    /**
     * 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 buildData(Uri uri) {
        return new LoadData(new ObjectKey(uri), new HttpUriFetcher(uri));
    }

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

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

public class HttpUriFetcher implements DataFetcher {
    private final Uri uri;
    private boolean isCanceled;
    public HttpUriFetcher(Uri uri) {
        this.uri = uri;
    }
    @Override
    public void loadData(DataFetcherCallback 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来源于文件,所以需要通过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 buildData(Uri uri) {
        //生成LoadData的时候需要传入对应的Key,缓存的时候需要用到
        return new LoadData<>(new ObjectKey(uri), new FileUriFetcher(uri, contentResolver));
    }

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

}

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

public class FileUriFetcher implements DataFetcher {
    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 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 implements ModelLoader {

    //FileUriModelLoader HttpUriModelLoader
    private final List> modelLoaders;

    public MultiModelLoader(List> modelLoaders) {
        this.modelLoaders = modelLoaders;
    }

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

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

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

public class StringModelLoader implements ModelLoader {
    private final ModelLoader loader;

    public StringModelLoader(ModelLoader loader) {
        this.loader = loader;
    }

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

    /**
    * 判断当前model是否为文件路径,并且将其转换为对应的Uri,然后再将其设置给相应的ModelLoader进行处理
    * 有可能你会有疑问,StringModelLoader为什么自己不进行处理还有交给其他的loader进行处理呢,
    * 在这里我们是为了实现更好的插拔,不然的话就得写一堆的逻辑判断语句了
    */
    @Override
    public LoadData 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的构建方式和其他的构建方式有点不同,在这里我们是通过注册机进行注册的,因为针对
    * 我们可以有多种modelLoader与其对应,所以我们这边是通过registry.build()获取到这个modelLoader
    */
    public static class StreamFactory implements ModelLoaderFactory {

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

注册机--ModelLoaderRegistry

ModelLoaderRegister负责注册所有的ModelLoader

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

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

    /**
     * 获得 对应 model与data类型的 modelloader
     *
     * @param modelClass
     * @param dataClass
     * @param 
     * @param 
     * @return
     */
    public  ModelLoader build(Class modelClass, Class dataClass) {
        List> loaders = new ArrayList<>();
        for (Entry entry : entries) {
            //找到我们需要的Model与Data类型的Loader
            if (entry.handles(modelClass, dataClass)) {
                loaders.add((ModelLoader) 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 
     * @return
     */
    public  List> getModelLoaders(Class modelClass) {
        List> loaders = new ArrayList<>();
        for (Entry entry : entries) {
            if (entry.handles(modelClass)) {
                loaders.add((ModelLoader) entry.factory.build(this));
            }
        }
        return loaders;
    }

    private static class Entry {
        Class modelClass;
        Class dataClass;
        ModelLoader.ModelLoaderFactory factory;

        public Entry(Class modelClass, Class dataClass, ModelLoader.ModelLoaderFactory 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> modelLoaders = loaderRegistry.getModelLoaders(String.class);
        ModelLoader modelLoader = modelLoaders.get(0);
        //HttpUriFetcher
        final ModelLoader.LoadData loadData = (ModelLoader.LoadData) 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() {
                    @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 loadData = httpUriLoader.buildData(uri);
        //
        loadData.fetcher.loadData(new DataFetcher.DataFetcherCallback() {

            @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 loadData1 = loader.buildData(uri);
//        loadData1.fetcher.loadDta();
    }
}

你可能感兴趣的:(Glide拆解2-ModelLoader模型加载器与其注册机)