应用图片加载服务与第三方实现库的解耦

统一图片加载库接口设计

本文背景

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

 

问题分析

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

 

需求分析

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

 

解决方案

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

设计方案

下面是类图:api

下面是代码实现:缓存

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;
    }
}

ImageLoaderapp

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);
    }

}

DisplayImageConfigide

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 ui

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); } } }

本文来自网易云社区,经做者陈柏宁受权发布。this

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

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

相关文章
相关标签/搜索