Android架构那些事之第三方库的隔离

作者: 五月花凋谢 | 来源:发表于2017-03-03 23:58 被阅读2568次

    在进入主题之前我们先说一下app客户端为什么需要有一个好的架构。

      我们都知道一个好的架构会使我们的开发变得事半功倍。
      设计架构的目的在于使我们的客户端易于扩展、方便单元测试、可复用。
      做到使模块之间低耦合,模块内部高内聚。
    

    我们在开发的过程中会不可避免的引用一些第三方库,比如网络请求库、图片加载库等等。就拿我们的图片加载库来说,程序中不会只有一个地方来引用到此库,可能有N个类会用到此库来显示图片。比如我们现在使用Universal-Image-Loader来展示客户端需要的图片,客户端总共有10个类使用该来显示图片。迭代了两个版本后老板突然说APP经常出现显示图片出现OOM的问题,我们需要将Universal-Image-Loader换成更高效的Picasso来显示图片。

    听到这个需求我们的第一反应是"天哪,鬼知道项目里有多少个地方引用了ImageLoader库,我们得改多少代码,万一改代码的时候引发了其他的bug怎么办"。

      问题来了我们应该如何避免这种“牵一发而动全身”的囧况。
    

    好了,我们先来看下我们平常是怎么在项目中引用图片加载库的。

    before.png

    上图是我们大多数人的做法。那么我们怎么才能做到换了一个第三方加载库而是这四个引用类不做改动呢。

    下面看下我们重新新设计之后的引用流程图


    after.png

    从上图我们能看到我们通过一个中间层来引用“第三方图片加载库”。这样做的好处是不管第三方图片加载库你换成Picasso还是Glide我们改变的只是这个中间层,其他的我们一行代码都不需要改动。

    如果当时你是这样设计的当老板让你把Universal-Image-Loader换成Picasso时你一行代码也不用改动只需要扩展一个类就可以了。好了话不多说 我们来看代码如何设计。

    我这里是使用代理模式来实现代码与图片加载库的隔离的。

    首先抽象一个ImageLoader接口

    **
     * 图片加载器功能接口;
     * 添加或者替换新的图片加载器实现该接口即可
     */
    public interface ImageLoader {
        /**
         * Init ImageLoader
         */
        void init(Context context);
    
        /**
         * Show Image
         *
         * @param imageUrl
         * @param imageView
         * @param defaultImage
         */
        void displayImage(String imageUrl, ImageView imageView, int defaultImage);
     }
    

    我们当前是使用UniversalImageLoader来展示项目中的图片我们就建一个UniversalImageLoader类来实现上面的接口

    public class UniversalImageLoader implements ImageLoader {
        private final long discCacheLimitTime = 3600 * 24 * 15L;
        private com.nostra13.universalimageloader.core.ImageLoader imageLoader = com.nostra13.universalimageloader.core.ImageLoader.getInstance();
    
        @Override
        public void init(Context context) {
            if (!imageLoader.isInited()) {
                ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(
                        context)
                        .threadPriority(Thread.NORM_PRIORITY)
                        .denyCacheImageMultipleSizesInMemory()
                        .memoryCache(new WeakMemoryCache())
                        .memoryCacheSize((2 * 1024 * 1024))
                        .memoryCacheSizePercentage(13)
                        .discCacheFileNameGenerator(new Md5FileNameGenerator())
                        .discCache(
                                new LimitedAgeDiskCache(StorageUtils
                                        .getCacheDirectory(context),
                                        discCacheLimitTime))
                        .tasksProcessingOrder(QueueProcessingType.LIFO).build();
                com.nostra13.universalimageloader.core.ImageLoader.getInstance().init(config);
            }
        }
    
    
        @Override
        public void displayImage(String uri, ImageView img, int default_pic) {
            DisplayImageOptions options = new DisplayImageOptions.Builder()
                    .showImageOnLoading(default_pic)
                    .showImageForEmptyUri(default_pic).showImageOnFail(default_pic)
                    .cacheInMemory(true).cacheOnDisc(true)
                    .bitmapConfig(Bitmap.Config.RGB_565)
                    .displayer(new SimpleBitmapDisplayer()).build();
            imageLoader.displayImage(uri, img, options);
        }
    
    

    接下来我们写一个代理类来帮我们实现图片加载的任务

    /**
     * 图片加载代理类,所有的图片操作都通过该代理类去实现;
     * 如果要改变图片加载框架,只需要在该类里替换相应的图片加载框架即可,客户端所有引用的图片操作都不需要修改
     
     */
    public class ImageLoaderProxy implements ImageLoader {
        private ImageLoader imageLoader;//代理对象
        private static ImageLoaderProxy imageLoaderProxy;
    
        public static ImageLoaderProxy getInstance() {
            if (imageLoaderProxy == null) {
                imageLoaderProxy = new ImageLoaderProxy();
            }
            return imageLoaderProxy;
        }
    
        public ImageLoaderProxy() {
            imageLoader= new UniversalImageLoader();
        }
    
    
        @Override
        public void init(Context context) {
            imageLoader.init(context);
        }
    
        @Override
        public void displayImage(String imageUrl, ImageView imageView, int defaultImage) {
            imageLoader.displayImage(imageUrl, imageView, defaultImage);
        }
     }
    
    
        这样客户端所有需要显示图片的地方只需要调用代理类的图片显示方法即可
        ImageLoaderProxy.getInstance().displayImage(); 
    

    当老板让我们换成Picasso来完成图片加载时 ,我们只需增加一个 PicassoImageLoader类然后将代理类中的这行代码 imageLoaderProxy = new UniversalImageLoader();换成imageLoaderProxy = new PicassoImageLoader()即可。

    怎么样我们只改动一行代码就替换了一个图片加载库方便吧。

    相关文章

      网友评论

      • cornflower10: public static ImageLoaderProxy getInstance() {
        if (imageLoaderProxy == null) {
        imageLoaderProxy = new ImageLoaderProxy();
        }
        return imageLoaderProxy;
        }

        public ImageLoaderProxy() {
        imageLoader= new UniversalImageLoader();
        }
        为啥要这样写,是为了使用单例模式嘛
      • 朔野:我们项目也是这么做的,但是这么做也有问题。就是中间层只能表现出不同图片加载框架的共有特性,而有时需要用到某个图片框架特有的功能就会不好处理了。就比如glide有按照fragment生命周期加载图片的方法,但是picasso就没有这个方法。使用中间层方便了扩展,但也不可避免的要丢失不同框架的特性
        五月花凋谢:@朔野 恩 会有这个问题 中间层使用策略模式的话 对于你说的问题 好很多
      • 时间仍在o是我们飞逝:这用的是多态性吧,不是真正的代理模式吧?
        五月花凋谢: @darkengine 是的
        darkengine:其实根据imageLoader= new UniversalImageLoader();以及后续可以改成imageLoaderProxy = new PicassoImageLoader()来看,这个应该算是template pattern,也就是名字而已,能解决问题的就是好模式。
      • 开发者头条_程序员必装的App:感谢分享!已推荐到《开发者头条》:https://toutiao.io/posts/s84b6n 欢迎点赞支持!
        欢迎订阅《向Android高手出发》https://toutiao.io/subjects/67303
      • 简简单单的过完这一生:设计模式用的好
        五月花凋谢: @简简单单的过完这一生 多谢 相互学习
      • 顶级工程师闯天涯:不错。。。。
      • code小生:不错不错👏👏
      • _bright:很有用,受教了,谢谢大神。
        五月花凋谢: @_bright 相互学习😀

      本文标题:Android架构那些事之第三方库的隔离

      本文链接:https://www.haomeiwen.com/subject/qjzrgttx.html