網絡圖片加載的封裝【從零開始搭建android框架系列(4)】

網絡圖片加載的封裝【從零開始搭建android框架系列(4)】

字數2478 閱讀6562 評論30 

更多及時技術資訊,歡迎關注我的微博 :Anthony

本篇文章項目github地址:MVPCommon


項目效果

項目正在改版,即時通訊功能暫時刪除了。

1 有哪些常用的圖片加載庫?



當下使用的主要有Piccaso、Fresco、Android-Universal-Image-Loader、Glide、Volley這五個圖片加載框架。
關於這些圖片加載框架的對比,網上可以找到很多文章。這裏不做過多贅述。具體請參考5中的參考鏈接,肯定會對你有幫助。


2 爲什麼要封裝?

這個段落的答案,摘抄自Stormzhang的文章 如何正確使用開源項目?

計算機史上有個萬能的解決方案就是,如果原有層面解決不了問題,那麼就請再加一層!

對於開源項目,我們知道有些庫設計的確實很棒,使用者調用起來非常方便,一行代碼直接搞定,拿圖片加載庫 Picasso 舉個例子:

Picasso.with(context).load(imageUrl).into(imageView);

使用起來是不是特簡單?你也許問我,都封裝的這麼好了還用得着再封裝一層麼?那你錯了,哪怕他已經很完美了,我都會這麼做:

public class ImageLoader {
    public static void with(Context context, String imageUrl, ImageView imageView) {
        Picasso.with(context).load(imageUrl).into(imageView); 
    }
}

這樣我所有項目調用的方式直接就是 ImageLoader.with() ,這樣做的好處是:

入口統一,所有圖片加載都在這一個地方管理,一目瞭然,即使有什麼改動我也只需要改這一個類就可以了。

隨着你們業務的需求,發現 Picasso 這個圖片加載庫已經滿足不了你們了,你們需要換成 Fresco ,如果你沒有封裝一層的話,想要替換這個庫那你要崩潰了,要把所有調用 Picasso 的地方都改一遍,而如果你中間封裝了一層,那真的非常輕鬆,三天兩頭的換一次也沒問題。

這就是所謂的外部表現一致,內部靈活處理原則。


3 有哪些需求?

這裏提供我平常開發用到的兩個需求:

3.1 圖片封裝,提供統一入口。封裝成熟的圖片框架,也就解決了這些問題:(內存緩存,磁盤緩存,網絡加載的結合,利用採樣率對圖片進行一定的壓縮,高效加載bitmap,圖片的加載策略,緩存策略(LRU),圖片錯位 ,優化列表的卡頓)

3.2 提供wifi下加載圖片開關,非wifi下顯示佔位圖片。


4 怎麼實現圖片封裝?

4.1 整體目錄

在我的mvp搭建的項目中,imageloader放在和activity,fragment同級的widget下面。當然後續也會不斷的添加widget(小控件),比如這裏的loading(加載),netstatus(網絡狀態監聽),progress(Material 進度條),swipeback(滑動返回)等。


整體目錄結構

4.2 ImageUtil類

作爲整個ImageLoader的公共訪問入口,以後使用的方式,將會是

ImageLoader imageLoader =new ImageLoader.Builder().url("img url").imgView(mImgView).build();
ImageLoaderUtil.getInstance().loadImage(context,imageLoader);

這種形式。可以看到ImageUtil提供的是單例模式,進行了封裝(後期引入Dagger2 之後直接會修改使用ImageLoaderUtil實例的方式)。全局應該只提供一個ImageLoader的實例,因爲圖片加載中又有線程池,緩存系統和網絡請求等,很消耗資源,所以不可能讓它構造多個實例。

package edu.com.base.ui.widget.imageloader;

import android.content.Context;

/**
 * Created by Anthony on 2016/3/3.
 * Class Note:
 * use this class to load image,single instance
 */
public class ImageLoaderUtil {

    public static final int PIC_LARGE = 0;
    public static final int PIC_MEDIUM = 1;
    public static final int PIC_SMALL = 2;

    public static final int LOAD_STRATEGY_NORMAL = 0;
    public static final int LOAD_STRATEGY_ONLY_WIFI = 1;

    private static ImageLoaderUtil mInstance;
    private BaseImageLoaderStrategy mStrategy;

    private ImageLoaderUtil(){
        mStrategy =new GlideImageLoaderStrategy();
    }

//single instance
    public static ImageLoaderUtil getInstance(){
        if(mInstance ==null){
            synchronized (ImageLoaderUtil.class){
                if(mInstance == null){
                    mInstance = new ImageLoaderUtil();
                    return mInstance;
                }
            }
        }
        return mInstance;
    }


    public void loadImage(Context context,ImageLoader img){
        mStrategy.loadImage(context,img);
    }

    public void setLoadImgStrategy(BaseImageLoaderStrategy strategy){
        mStrategy =strategy;
    }
}

4.3 BaseImageLoaderProvider類

可以看到我們ImageUtil中是採用這個類的loadImage方法去加載圖片的。這裏是一個接口。由具體的子類(GlideImageLoaderProvider)去實現loadImage方法。(這裏我也添加了PicassoImageLoaderStrategy方法,但其中的loadImage方法是空的實現)。

package edu.com.base.ui.widget.imageloader;

import android.content.Context;

/**
 * Created by Anthony on 2016/3/3.
 * Class Note:
 * abstract class/interface defined to load image
 * (Strategy Pattern used here)
 */
public interface BaseImageLoaderStrategy {
   void loadImage(Context ctx, ImageLoader img);
}

4.4 GlideImageLoaderProvider類

是BaseImageLoaderProvider的實現類,完成具體的加載圖片操作。這裏面會有wifi下加載圖片的判斷。具體判斷將放在util工具類中進行實現。這裏也是利用圖片加載庫Glide進行實現。後期如果工程項目決定使用其他的圖片加載框架,當然可以採用其他類繼承BaseImageLoaderProvider。

package edu.com.mvplibrary.ui.widget.imageloader;

import android.content.Context;

import com.bumptech.glide.Glide;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.model.stream.StreamModelLoader;


import java.io.IOException;
import java.io.InputStream;

import edu.com.mvplibrary.AbsApplication;
import edu.com.mvplibrary.util.AppUtils;
import edu.com.mvplibrary.util.SettingUtils;

/**
 * Created by Anthony on 2016/3/3.
 * Class Note:
 * provide way to load image
 */
public class GlideImageLoaderProvider implements BaseImageLoaderProvider {
    @Override
    public void loadImage(Context ctx, ImageLoader img) {

        boolean flag= SettingUtils.getOnlyWifiLoadImg(ctx);
        //如果不是在wifi下加載圖片,直接加載
        if(!flag){
            loadNormal(ctx,img);
            return;
        }

        int strategy =img.getStrategy();
        if(strategy == ImageLoaderUtil.LOAD_STRATEGY_ONLY_WIFI){
            int netType = AppUtils.getNetWorkType(AbsApplication.app());
            //如果是在wifi下才加載圖片,並且當前網絡是wifi,直接加載
            if(netType == AppUtils.NETWORKTYPE_WIFI) {
                loadNormal(ctx, img);
            } else {
                //如果是在wifi下才加載圖片,並且當前網絡不是wifi,加載緩存
                loadCache(ctx, img);
            }
        }else{
            //如果不是在wifi下才加載圖片
            loadNormal(ctx,img);
        }

    }


    /**
     * load image with Glide
     */
    private void loadNormal(Context ctx, ImageLoader img) {
        Glide.with(ctx).load(img.getUrl()).placeholder(img.getPlaceHolder()).into(img.getImgView());
    }


    /**
     *load cache image with Glide
     */
    private void loadCache(Context ctx, ImageLoader img) {
        Glide.with(ctx).using(new StreamModelLoader<String>() {
            @Override
            public DataFetcher<InputStream> getResourceFetcher(final String model, int i, int i1) {
                return new DataFetcher<InputStream>() {
                    @Override
                    public InputStream loadData(Priority priority) throws Exception {
                        throw new IOException();
                    }

                    @Override
                    public void cleanup() {

                    }

                    @Override
                    public String getId() {
                        return model;
                    }

                    @Override
                    public void cancel() {

                    }
                };
            }
        }).load(img.getUrl()).placeholder(img.getPlaceHolder()).diskCacheStrategy(DiskCacheStrategy.ALL).into(img.getImgView());
    }
}

4.5 ImageLoader類

在ImageUtil的load方法中進行圖片加載,第一個參數是Context,那麼第二個參數呢?正是這裏的ImageLoader,採用Builder建造者模式。Builder模式可以將一個複雜對象的構建和它的表示分離,使得同樣的構建過程可以構建不同的對象。

why builder pattern?
因爲在圖片加載中,會處理到的數據必定有圖片的url,必定有ImageView的實例,可能有加載策略(是否wifi下加載),可能有圖片加載類型(大圖,中圖,小圖),也會有圖片加載沒有成功時候的佔位符。那麼這麼多數據操作,所以用到了Builder模式,一步一步的創建一個複雜對象的創建者模式,它允許用戶在不知道內部構建細節的情況下,可以更精細的控制對象的構建流程。比如這裏的ImageLoader。

package edu.com.base.ui.widget.imageloader;

import android.widget.ImageView;

import edu.com.mvplibrary.R;


/**
 * Created by Anthony on 2016/3/3.
 * Class Note:
 * encapsulation of ImageView,Build Pattern used
 */
public class ImageLoader {
    private int type;  //類型 (大圖,中圖,小圖)
    private String url; //需要解析的url
    private int placeHolder; //當沒有成功加載的時候顯示的圖片
    private ImageView imgView; //ImageView的實例
    private int wifiStrategy;//加載策略,是否在wifi下才加載

    private ImageLoader(Builder builder) {
        this.type = builder.type;
        this.url = builder.url;
        this.placeHolder = builder.placeHolder;
        this.imgView = builder.imgView;
        this.wifiStrategy = builder.wifiStrategy;
    }
    public int getType() {
        return type;
    }

    public String getUrl() {
        return url;
    }

    public int getPlaceHolder() {
        return placeHolder;
    }

    public ImageView getImgView() {
        return imgView;
    }

    public int getWifiStrategy() {
        return wifiStrategy;
    }

    public static class Builder {
        private int type;
        private String url;
        private int placeHolder;
        private ImageView imgView;
        private int wifiStrategy;

        public Builder() {
            this.type = ImageLoaderUtil.PIC_SMALL;
            this.url = "";
            this.placeHolder = R.drawable.default_pic_big;
            this.imgView = null;
            this.wifiStrategy = ImageLoaderUtil.LOAD_STRATEGY_NORMAL;
        }

        public Builder type(int type) {
            this.type = type;
            return this;
        }

        public Builder url(String url) {
            this.url = url;
            return this;
        }

        public Builder placeHolder(int placeHolder) {
            this.placeHolder = placeHolder;
            return this;
        }

        public Builder imgView(ImageView imgView) {
            this.imgView = imgView;
            return this;
        }

        public Builder strategy(int strategy) {
            this.wifiStrategy = strategy;
            return this;
        }

        public ImageLoader build() {
            return new ImageLoader(this);
        }

    }
}

4.6 策略模式的使用
上面的圖片加載用到了策略模式。

策略模式是指定義了一系列的算法,並將每一個算法封裝起來(比如上面的Picasso和Glide),而且他們還可以互相替換。策略模式讓他的算法獨立於使用它的客戶而獨立變化。

一起看看整個圖片加載封裝的類圖。


這裏真是利用同樣是圖片加載,我們可以將不同的加載方式抽象出來,提供一個統一的接口,不同的算法或者策略有不同的實現方式,這樣我們的客戶端,也就是我們利用ImageLoaderUtil類加載圖片的時候,就可以實現不同的加載策略。我們也可以通過

public void setLoadImgStrategy(BaseImageLoaderStrategy strategy){    
mStrategy =strategy;
}

來傳入不同的加載策略,實現了策略的動態替換。也就提高了後期的可擴展性和可維護性。


5 參考鏈接

Android 幾個圖片緩存原理、特性對比

Introduction to Glide, Image Loader Library for Android, recommended by Google

FaceBook推出的Android圖片加載庫-Fresco

StackOverflow-->Picasso v/s Imageloader v/s Fresco vs Glide

本篇文章項目github地址:MVPCommon

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章