應用圖片加載服務與第三方實現庫的解耦

統一圖片加載庫接口設計

本文背景

雲課堂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

更多網易研發、產品、運營經驗分享請訪問網易雲社區。 

相關文章
相關標籤/搜索