zoukankan      html  css  js  c++  java
  • 应用图片加载服务与第三方实现库的解耦

    统一图片加载库接口设计

    本文背景

    云课堂android端,目前使用的图片加载库是UniversalImageLoader(简称UIL)。在5.4.0迭代版本中,因首页又增加了几个页面,发现启动app后,内存暴增,在排查问题后发现,是图片加载库的使用方式存在问题,以及该加载库对内存并不友好。因此在对比Glide后,发下启动app后,内存有很大改善,决定使用Glide。因此需要将原有的图片加载库替换成新的。这套新的方案,需要在未来的几年中使用,并且能够灵活替换图片加载库。

    问题分析

    在工程中发现原来使用UIL尽管被封装在EduImageLoaderUtil类中,包括UIL的初始配置以及默认选项。但还是有些UIL包内的类如ImageLoadingListenerDisplayImageOptions暴露在公用方法中,在外部调用的时候传入。这个严重影响了封装性,不方便后期替换图片加载库。

    需求分析

    • 接口与实现隔离开来,对于业务上层有一个统一的管理类以及统一的Api,不关心具体的第三方实现。当有更好的图片加载库出现时,可以灵活切换。业务上层只关心图片加载的服务接口,不关心真正的实现者。
    • 增加全局配置类,如想实现在不同网络环境显示不同清晰度的图片,以及常见的内存缓存配置,磁盘缓存配置等等。
    • 增加每次加载图片的配置类,因为每次加载图片可能需求不一样,比如轮播图的地方它的优先级要高一些,延迟性要低一些,质量要高一些,要做到可配置。
    • 增加对图片转换的处理配置,因为有些地方的图可能想要圆角,有些地方又需要裁剪。

    解决方案

    • 定义一个ImageLoader接口,里面有上层业务需要使用的api。具体的实现分别放在glide包、uil包等。定义一个ImageLoaderManager类,负责管理ImageLoader。
    • 一个全局的配置项,具体的实现类,通过ImageLoaderManager类注入进来。如在wifi下高清大图的功能开关,默认的初始图片质量,(图片Url可以拼接quality)
    • 每次图片加载的配置项,有一个默认的全局配置,在类ImageLoaderManager中。在ImageLoader接口中应当提供通过配置类,决定展示图片的方案。
    • 通过一个枚举值,标识每次图片展示的转换配置。然后各个图片加载库参照枚举描述,做出对应的转换。

    设计方案

    下面是类图:

    下面是代码实现:

    ImageLoaderManager 由于图片库在一个应用中只会选择一种实现方案,所以这里的ImageLoader管理类,简单处理,配有一个默认的实现,一个默认的全局配置,一个默认的图片加载配置。提供了接口去修改默认的。

    package com.netease.framework.imagemodule;
    
    import com.netease.framework.annotation.NonNull;
    import com.netease.framework.imagemodule.glide.GlideImageLoader;
    
    /**
     * ImageLoader管理类,默认的ImageLoader实现是GlideImageLoader。
     * 提供一些注入接口,来修改默认实现以及默认配置
     * Created by hzchenboning on 2017/10/8.
     */
    
    public class ImageLoaderManager {
    
        private static ImageLoader sImageLoader = new GlideImageLoader();     //默认的ImageLoader实现,Glide
    
        private static DisplayImageConfig sDefaultDisPlayImageConfig = new DisplayImageConfig.Builder().build();
    
        private static GlobalImageConfig sGlobalImageConfig = new GlobalImageConfig.Builder().build();
    
        public static ImageLoader getImageLoader() {
            return sImageLoader;
        }
    
        public static @NonNull GlobalImageConfig getGlobalImageConfig() {
            return sGlobalImageConfig;
        }
    
        public static @NonNull DisplayImageConfig getDefaultDisPlayImageConfig() {
            return sDefaultDisPlayImageConfig;
        }
    
        /**
         * 修改默认的ImageLoader实现类
         * @param imageLoader
         */
        public static void setImageLoader(@NonNull ImageLoader imageLoader) {
            sImageLoader = imageLoader;
        }
    
        /**
         * 修改默认的每次图片加载配置项
         * @param sDefaultDisPlayImageConfig
         */
        public static void setDefaultDisPlayImageConfig(@NonNull DisplayImageConfig sDefaultDisPlayImageConfig) {
            ImageLoaderManager.sDefaultDisPlayImageConfig = sDefaultDisPlayImageConfig;
        }
    
        /**
         * 修改默认的全局配置项
         * @param sGlobalImageConfig
         */
        public static void setGlobalImageConfig(@NonNull GlobalImageConfig sGlobalImageConfig) {
            ImageLoaderManager.sGlobalImageConfig = sGlobalImageConfig;
        }
    }
    

    ImageLoader

    import android.content.Context;
    import android.graphics.Bitmap;
    import android.widget.ImageView;
    
    /**
     * 图片加载器对外提供的服务接口
     * Created by hzchenboning on 17/9/28.
     */
    
    public interface ImageLoader {
    
        /**
         * 展示图片
         */
        void displayImage(Context context, String imageUrl, ImageView imageView);
    
        /**
         * 展示指定尺寸
         */
        void displayImage(Context context, String imageUrl, ImageView imageView, int width, int height);
    
        /**
         * 根据配置展示图片
         */
        void displayImage(Context context, String imageUrl, ImageView imageView, DisplayImageConfig config);
    
        /**
         * 根据配置展示指定大小图片
         */
        void displayImage(Context context, String imageUrl, ImageView imageView, DisplayImageConfig config, int width, int height);
    
        /**
         * 展示图片,并且监听图片加载回调
         */
        <R> void displayImage(Context context, String imageUrl, ImageView imageView, ResourceListener<R> listener);
    
        /**
         * 根据配置展示图片,并且监听图片加载回调
         */
        <R> void displayImage(Context context, String imageUrl, ImageView imageView, DisplayImageConfig config, ResourceListener<R> listener);
    
        /**
         * 展示高斯模糊图片
         * @param radius 高斯模糊半径(像素),不包含中心点的像素,取值范围[1, 50]
         * @param sigma 高斯模糊标准差
         */
        void displayBlurImage(Context context, String imageUrl, ImageView imageView, int radius, int sigma);
    
        /**
         * 展示圆形图片
         * 圆形的半径为图片的Math.min(width, height)/2
         */
        void displayCircleImage(Context context, String imageUrl, ImageView imageView);
    
        /**
         * 下载图片
         */
        <R> void loadImage(Context context, String imageUrl, ResourceListener<R> resourceListener);
    
        /**
         * 根据配置下载图片
         */
        <R> void loadImage(Context context, String imageUrl, DisplayImageConfig config, ResourceListener<R> resourceListener);
    
        /**
         * 从缓存中(内存、磁盘)获取图片
         */
        Bitmap getBitmapFromCache(String url);
    
        interface ResourceListener<R> {
            void onResourceReady(R resouce);
        }
    
    }
    

    DisplayImageConfig

    import com.netease.edu.framework.R;
    
    /**
     * 每次图片加载的配置项
     * Created by hzchenboning on 17/10/9.
     */
    
    public class DisplayImageConfig {
        int imageResOnLoading;
        int imageResOnFail;
        Priority priority;
        boolean cacheOnDisk;
        boolean cacheOnMemory;
        boolean needThumbnail;
        float thumbnail;
        BitmapTransformation transformation;
    
        private DisplayImageConfig(Builder builder) {
            this.imageResOnLoading = builder.imageResOnLoading;
            this.imageResOnFail = builder.imageResOnFail;
            this.priority = builder.priority;
            this.cacheOnDisk = builder.cacheOnDisk;
            this.cacheOnMemory = builder.cacheOnMemory;
            this.needThumbnail = builder.needThumbnail;
            this.thumbnail = builder.thumbnail;
            this.transformation = builder.transformation;
        }
    
        public int getImageResOnLoading() {
            return imageResOnLoading;
        }
    
        public int getImageResOnFail() {
            return imageResOnFail;
        }
    
        public Priority getPriority() {
            return priority;
        }
    
        public boolean isCacheOnDisk() {
            return cacheOnDisk;
        }
    
        public boolean isCacheOnMemory() {
            return cacheOnMemory;
        }
    
        public boolean isNeedThumbnail() {
            return needThumbnail;
        }
    
        public float getThumbnail() {
            return thumbnail;
        }
    
        public static class Builder {
            int imageResOnLoading = R.drawable.default_img;//加载中显示的图片
            int imageResOnFail = R.drawable.default_img;//加载失败后显示的图片
            Priority priority = Priority.NORMAL;//加载优先级
            boolean cacheOnDisk = true;
            boolean cacheOnMemory = true;
            boolean needThumbnail = true;//是否先显示缩略图
            float thumbnail = 0.1f;//缩略图为原图的十分之一
    
            BitmapTransformation transformation = BitmapTransformation.none;
    
            public Builder setImageResOnLoading(int imageResOnLoading) {
                this.imageResOnLoading = imageResOnLoading;
                return this;
            }
    
            public Builder setImageResOnFail(int imageResOnFail) {
                this.imageResOnFail = imageResOnFail;
                return this;
            }
    
            public Builder setPriority(Priority priority) {
                this.priority = priority;
                return this;
            }
    
            public Builder setCacheOnDisk(boolean cacheOnDisk) {
                this.cacheOnDisk = cacheOnDisk;
                return this;
            }
    
            public Builder setCacheOnMemory(boolean cacheOnMemory) {
                this.cacheOnMemory = cacheOnMemory;
                return this;
            }
    
            public Builder setNeedThumbnail(boolean needThumbnail) {
                this.needThumbnail = needThumbnail;
                return this;
            }
    
            public Builder setThumbnail(float thumbnail) {
                this.thumbnail = thumbnail;
                return this;
            }
    
            public Builder setTransformation(BitmapTransformation transformation) {
                this.transformation = transformation;
                return this;
            }
    
            public DisplayImageConfig build() {
                return new DisplayImageConfig(this);
            }
        }
    
        public enum Priority {
            IMMEDIATE,  //0ms
            LOW,        //300ms
            NORMAL,     //100ms
            HIGH        //50ms
        }
    
        /**
         * 每个新增的转换,需要增加对应的描述
         * 新增的命名就按照circleCrop、roundCrop
         */
        public enum BitmapTransformation {
            none,           //(无变化)
        }
    
    }
    

    GlobalImageConfig 

    import android.support.annotation.IntDef;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    /**
     * 全局的图片加载配置
     * Created by hzchenboning on 17/10/9.
     */
    
    public class GlobalImageConfig {
        //--------- 以下是接口及常量 -------------
        @Retention(RetentionPolicy.SOURCE)
        @IntDef({HIGH_IMAGE_QUALITY, NORMAL_IMAGE_QUALITY, LOW_IMAGE_QUALITY})
        private @interface ImageQualityMode {}
    
        public static final int HIGH_IMAGE_QUALITY = 100;
        public static final int NORMAL_IMAGE_QUALITY = 80;
        public static final int LOW_IMAGE_QUALITY = 50;
    
        //磁盘缓存文件 250MB
        private static final String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";
        private static final int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
        //--------- 以上是接口及常量 -------------
    
    
        public static boolean NEED_ADJUST_IMAGE_QUALITY = false;
        private static int sImageQuality = HIGH_IMAGE_QUALITY;
    
        private final boolean useExternalDiskCacheDir;
        private final String cacheFolderName;
        private final int diskCacheSize;
        private final int memoryCacheSize;
    
        public GlobalImageConfig(boolean useExternalDiskCacheDir, String cacheFolderName, int diskCacheSize, int memoryCacheSize) {
            this.useExternalDiskCacheDir = useExternalDiskCacheDir;
            this.cacheFolderName = cacheFolderName;
            this.diskCacheSize = diskCacheSize;
            this.memoryCacheSize = memoryCacheSize;
        }
    
        public static int getImageQuality() {
            return sImageQuality;
        }
    
        public static void setImageQuality(@ImageQualityMode int quality) {
            sImageQuality = quality;
        }
    
        public boolean isUseExternalDiskCacheDir() {
            return useExternalDiskCacheDir;
        }
    
        public String getCacheFolderName() {
            return cacheFolderName;
        }
    
        public int getDiskCacheSize() {
            return diskCacheSize;
        }
    
        public int getMemoryCacheSize() {
            return memoryCacheSize;
        }
    
        public static class Builder {
            boolean useExternalDiskCacheDir = true; // 默认使用外部存储卡,false的话使用内部
            String cacheFolderName = DEFAULT_DISK_CACHE_DIR;
            int diskCacheSize = DEFAULT_DISK_CACHE_SIZE;
    
            int memoryCacheSize = 0;//如果为0,交给第三方去计算最合适的大小
    
            public Builder setUseExternalDiskCacheDir(boolean useExternalDiskCacheDir) {
                this.useExternalDiskCacheDir = useExternalDiskCacheDir;
                return this;
            }
    
            public Builder setCacheFolderName(String cacheFolderName) {
                this.cacheFolderName = cacheFolderName;
                return this;
            }
    
            public Builder setDiskCacheSize(int diskCacheSize) {
                this.diskCacheSize = diskCacheSize;
                return this;
            }
    
            public Builder setMemoryCacheSize(int memoryCacheSize) {
                this.memoryCacheSize = memoryCacheSize;
                return this;
            }
    
            public GlobalImageConfig build() {
                return new GlobalImageConfig(useExternalDiskCacheDir, cacheFolderName, diskCacheSize, memoryCacheSize);
            }
        }
    
    
    }

    本文来自网易云社区,经作者陈柏宁授权发布。

    原文地址:应用图片加载服务与第三方实现库的解耦

    更多网易研发、产品、运营经验分享请访问网易云社区。 

  • 相关阅读:
    DEDECMS之五 单页
    DEDECMS之六 网站地图、RSS地图
    DEDECMS之四 栏目调用
    DEDECMS之三 首页、列表页怎么调用文章内容
    DEDECMS之七 如何实现文章推荐排行榜
    centos6下安装dedecms
    C# 自动部署之附加数据库
    产品经理技能之BRD的笔记之菜鸟入门
    产品经理技能之MRD的笔记之一
    产品需求文档(PRD)的写作方法之笔记一
  • 原文地址:https://www.cnblogs.com/163yun/p/9284278.html
Copyright © 2011-2022 走看看