16.手寫圖片加載框架ImageLoader

概述

第三方開源的圖片框架很多审丘,這里自己去寫一個(gè)的目的是通過這樣一個(gè)寫的過程,拓展自己對(duì)架構(gòu)設(shè)計(jì)的理解勾给,包括設(shè)計(jì)模式滩报,線程,策略播急,緩存等等渣锦。另外大型的框架例如Glide顽决,代碼很完善,擴(kuò)展性很高,但是閱讀起來有難度仓坞,而實(shí)際上,這些框架底層實(shí)現(xiàn)原理都是類似的顷扩,所以通過構(gòu)建一個(gè)簡(jiǎn)單框架的過程更加有助于對(duì)其原理的理解火邓,算是為閱讀復(fù)雜的第三方源碼打下一個(gè)基礎(chǔ)。

github地址:https://github.com/renzhenming/ImageLoader.git

今天的框架要實(shí)現(xiàn)一下的功能:

1.根據(jù)用戶需求可以靈活配置(建造者模式)
2.支持高并發(fā)烂叔,圖片加載的優(yōu)先級(jí)
3.支持可以選擇不同的加載策略谨胞,對(duì)加載策略進(jìn)行擴(kuò)展
4.二級(jí)緩存 加載圖片時(shí)內(nèi)存中已經(jīng)加載了,則從內(nèi)存中加載蒜鸡,不存在去外置卡中5.加載胯努,外置還不存在則從網(wǎng)絡(luò)下載
6.并對(duì)緩存策略可以擴(kuò)展
7.支持從加載過程中顯示默認(rèn)加載圖片
8.支持加載失敗時(shí) 顯示默認(rèn)錯(cuò)誤圖片
9.圖片顯示自適應(yīng)昼牛。從網(wǎng)絡(luò)加載下來的圖片經(jīng)最佳比例壓縮后顯示不能失真變形
10.支持請(qǐng)求轉(zhuǎn)發(fā),下載

用到的模式:
1.生產(chǎn)者 消費(fèi)者模式
2.建造者模式
3.單例模式
4.模板方法模式
5.策略模式

用到的知識(shí)點(diǎn)
1.內(nèi)存緩存 LruCache技術(shù)
2.硬盤緩存技術(shù)DiskLruCache技術(shù)
3.圖片下載時(shí)請(qǐng)求轉(zhuǎn)發(fā)

ImageLoader類圖.png
框架構(gòu)建流程

如上圖康聂,首先使用Builder設(shè)計(jì)模式構(gòu)建ImageLoaderConfig贰健,這個(gè)類處理圖片加載框架的全局配置信息,包括加載策略恬汁,緩存策略伶椿,線程數(shù),以及加載中一些圖片的配置氓侧,封裝成了DisplayConfig對(duì)象脊另;SimpleImageLoader是對(duì)外暴露的主類,它持有配置對(duì)象的引用约巷,所以它可以調(diào)用所以圖片加載中所涉及的配置偎痛,然后將這些配置封裝成BitmapRequest對(duì)象,一個(gè)BitmapRequest對(duì)象對(duì)應(yīng)一次網(wǎng)絡(luò)請(qǐng)求独郎,在SimpleImageLoader初始化的同時(shí)還會(huì)初始化全局的請(qǐng)求隊(duì)列RequestQueue踩麦,BitmapRequest對(duì)象會(huì)被加入隊(duì)列中,這時(shí)候分發(fā)器開始工作氓癌,將BitmapRequest按照一定協(xié)議分發(fā)給不同的加載器谓谦,加載器拿到請(qǐng)求后先從緩存BitmapCache中獲取,有緩存則直接展示然后再去加載網(wǎng)絡(luò)圖片贪婉,加載網(wǎng)絡(luò)圖片的時(shí)候又涉及到加載策略的選擇反粥,根據(jù)不同策略進(jìn)行不同的加載。

關(guān)鍵代碼
1.配置管理

ImageLoaderConfig ,配置管理類疲迂,我們把一些關(guān)鍵的配置信息單獨(dú)封裝起來才顿,以Builder模式構(gòu)建,用戶可以自由的配置自己需要的尤蒿,同時(shí)提高擴(kuò)展性郑气,這正是Builder設(shè)計(jì)模式的優(yōu)點(diǎn).ImageLoaderConfig 負(fù)責(zé)處理整個(gè)框架的配置信息,目前包括緩存策略优质,加載策略竣贪,加載中顯示的占位圖,加載失敗展示的占位圖等等

package com.rzm.imageloader.config;

import com.rzm.imageloader.cache.BitmapCache;
import com.rzm.imageloader.policy.LoadPolicy;

/**
 * Author:renzhenming
 * Time:2018/6/13 7:21
 * Description:This is ImageLoaderConfig
 * 以建造者模式實(shí)現(xiàn)巩螃,管理ImageLoader配置信息
 */
public class ImageLoaderConfig {

    /**
     * 圖片顯示配置 TODO 初始化
     */
    private DisplayConfig displayConfig;

    /**
     * 緩存策略
     */
    private BitmapCache bitmapCache;

    /**
     * 加載策略
     */
    private LoadPolicy loadPolicy;

    /**
     * 默認(rèn)線程數(shù)
     */
    private int threadCount = Runtime.getRuntime().availableProcessors();

    private ImageLoaderConfig(){}

    /**
     * 建造者模式
     */
    public static class Builder{

        /**
         * Builder持有外部類的引用演怎,在new的時(shí)候創(chuàng)建出來
         */
        private ImageLoaderConfig config;

        public Builder(){
            config = new ImageLoaderConfig();
        }

        /**
         * 設(shè)置緩存策略
         * @param bitmapCache
         * @return
         */
        public Builder setCachePolicy(BitmapCache bitmapCache){
            config.bitmapCache = bitmapCache;
            return this;
        }

        /**
         * 設(shè)置加載策略
         * @param loadPolicy
         * @return
         */
        public Builder setLoadPolicy(LoadPolicy loadPolicy){
            config.loadPolicy = loadPolicy;
            return this;
        }

        /**
         * 設(shè)置線程數(shù)
         * @param threadCount
         * @return
         */
        public Builder setThreadCount(int threadCount){
            config.threadCount = threadCount;
            return this;
        }

        /**
         * 設(shè)置加載過程中的圖片
         * @param resId
         * @return
         */
        public Builder setLoadingImage(int resId){
            if (config.displayConfig == null){
                throw new NullPointerException("you have not set DisplayConfig,DisplayConfig is null");
            }
            config.displayConfig.loadingImage = resId;
            return this;
        }

        /**
         * 設(shè)置加載失敗顯示的圖片
         * @param resId
         * @return
         */
        public Builder setErrorImage(int resId){
            if (config.displayConfig == null){
                throw new NullPointerException("you have not set DisplayConfig,DisplayConfig is null");
            }
            config.displayConfig.errorImage = resId;
            return this;
        }



        /**
         * 構(gòu)建
         * @return
         */
        public ImageLoaderConfig build(){
            return config;
        }
    }

    public DisplayConfig getDisplayConfig() {
        return displayConfig;
    }

    public BitmapCache getBitmapCache() {
        return bitmapCache;
    }

    public LoadPolicy getLoadPolicy() {
        return loadPolicy;
    }

    public int getThreadCount() {
        return threadCount;
    }
}

package com.rzm.commonlibrary.general.imageloader.config;

/**
 * Author:renzhenming
 * Time:2018/6/13 7:24
 * Description:This is DisplayConfig
 * 圖片顯示配置類,單獨(dú)拿出來作為一個(gè)單獨(dú)類有利于擴(kuò)展避乏,仿Glide
 */
public class DisplayConfig {

    /**
     * 加載過程中的占位圖片
     */
    public int loadingImage = -1;

    /**
     * 加載失敗顯示的圖片
     */
    public int errorImage = -1;
}
2.SimpleImageLoader

以單例形式構(gòu)建的交互類爷耀,持有ImageLoaderConfig 配置引用,負(fù)責(zé)將每一個(gè)網(wǎng)絡(luò)請(qǐng)求封裝成BitmapRequest對(duì)象拍皮,SimpleImageLoader初始化的時(shí)候會(huì)構(gòu)建出一個(gè)全局的阻塞式隊(duì)列歹叮,BitmapRequest會(huì)被加入這個(gè)隊(duì)列中跑杭,此時(shí)分發(fā)器開始工作,分發(fā)器的構(gòu)建啟發(fā)于Android消息隊(duì)列中的looper咆耿,負(fù)責(zé)將每個(gè)Request請(qǐng)求從隊(duì)列中取出交給負(fù)責(zé)處理這個(gè)請(qǐng)求的加載器

package com.rzm.commonlibrary.general.imageloader.loader;

import android.graphics.Bitmap;
import android.widget.ImageView;

import com.rzm.commonlibrary.general.imageloader.config.DisplayConfig;
import com.rzm.commonlibrary.general.imageloader.config.ImageLoaderConfig;
import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
import com.rzm.commonlibrary.general.imageloader.request.RequestQueue;

/**
 * Author:renzhenming
 * Time:2018/6/13 7:22
 * Description:This is SimpleImageLoader
 */
public class SimpleImageLoader {

    /**
     * 持有配置信息對(duì)象的引用
     */
    private ImageLoaderConfig config;

    private static volatile SimpleImageLoader instance;

    /**
     * 請(qǐng)求隊(duì)列
     */
    private RequestQueue requestQueue;

    private SimpleImageLoader(){}

    private SimpleImageLoader(ImageLoaderConfig config){
        this.config = config;
        //初始化請(qǐng)求隊(duì)列
        requestQueue = new RequestQueue(config.getDispatcherCount());
        //開啟請(qǐng)求隊(duì)列
        requestQueue.start();
    }

    /**
     * 用于在Application中初始化ImageLoader 設(shè)置配置信息
     * 必須德谅,否則調(diào)用空參getInstance()會(huì)拋異常
     * @param config
     * @return
     */
    public static SimpleImageLoader init(ImageLoaderConfig config){
        if (instance == null){
            synchronized (SimpleImageLoader.class){
                if (instance == null){
                    instance = new SimpleImageLoader(config);
                }
            }
        }
        return instance;
    }

    /**
     * 用于在代碼中獲取單例對(duì)象
     * @return
     */
    public static SimpleImageLoader getInstance(){
        if (instance == null){
            throw new UnsupportedOperationException("SimpleImageLoader haven't been init with ImageLoaderConfig,call init(ImageLoaderConfig config) in your application");
        }
        return instance;
    }

    /**
     * 顯示圖片
     * @param imageView
     * @param url
     */
    public void display(ImageView imageView,String url){
        display(imageView,url,null,null);
    }
    /**
     * 顯示圖片
     * @param imageView
     * @param url
     */
    public void display(ImageView imageView,String url,DisplayConfig displayConfig){
        display(imageView,url,displayConfig,null);
    }

    /**
     * 顯示圖片
     * @param imageView
     * @param url
     */
    public void display(ImageView imageView,String url,ImageListener listener){
        display(imageView,url,null,listener);
    }
    /**
     * 顯示圖片,用于針對(duì)特殊圖片配置特殊的配置信息
     * @param imageView
     * @param url
     * @param displayConfig
     * @param listener
     */
    public void display(ImageView imageView,String url,DisplayConfig displayConfig,ImageListener listener){
        if (imageView  == null){
            throw new NullPointerException("ImageView cannot be null");
        }
        //封裝成一個(gè)請(qǐng)求對(duì)象
        BitmapRequest request= new BitmapRequest(imageView,url,displayConfig,listener);
        //加入請(qǐng)求隊(duì)列
        requestQueue.addRequest(request);
    }

    /**
     * 監(jiān)聽圖片萨螺,設(shè)置后期處理窄做,仿Glide
     */
    public static interface ImageListener{
        void onComplete(ImageView imageView, Bitmap bitmap,String url);
    }

    /**
     * 獲取全局配置
     * @return
     */
    public ImageLoaderConfig getConfig() {
        return config;
    }
}

3.BitmapRequest請(qǐng)求對(duì)象

一個(gè)BitmapRequest對(duì)象中封裝了這次請(qǐng)求的所有相關(guān)信息,包括這個(gè)請(qǐng)求的圖片顯示邏輯的配置DisplayConfig慰技,當(dāng)前全局的加載策略LoadPolicy椭盏,請(qǐng)求圖片的網(wǎng)絡(luò)地址和這個(gè)地址需要展示的控件對(duì)象ImageView,另外我對(duì)BitmapRequest對(duì)象重寫了hashCode和equals方法,實(shí)現(xiàn)了Comparable接口吻商,以實(shí)現(xiàn)這個(gè)對(duì)象的比較邏輯掏颊,為加載策略服務(wù)

package com.rzm.commonlibrary.general.imageloader.request;

import android.widget.ImageView;

import com.rzm.commonlibrary.general.imageloader.config.DisplayConfig;
import com.rzm.commonlibrary.general.imageloader.loader.SimpleImageLoader;
import com.rzm.commonlibrary.general.imageloader.policy.LoadPolicy;
import com.rzm.commonlibrary.general.imageloader.utils.Md5Util;

import java.lang.ref.SoftReference;

/**
 * Author:renzhenming
 * Time:2018/6/13 7:23
 * Description:This is BitmapRequest
 */
public class BitmapRequest implements Comparable<BitmapRequest>{

    /**
     * 展示配置
     */
    private DisplayConfig disPlayConfig;
    /**
     * 加載策略
     */
    private LoadPolicy loadPolicy = SimpleImageLoader.getInstance().getConfig().getLoadPolicy();

    /**
     * 序列號(hào),用于順序比較
     */
    private int serialNum;

    /**
     * 持有ImageView的軟引用
     */
    private SoftReference<ImageView> imageViewSoftReference;

    /**
     * 圖片路徑
     */
    private String imageUrl;

    /**
     * 圖片路徑的md5值
     */
    private String imageUrlMd5;

    /**
     * 下載完成的監(jiān)聽
     */
    private SimpleImageLoader.ImageListener imageListener;

    public BitmapRequest() {

    }

    public BitmapRequest(ImageView imageView, String imageUrl) {
        this(imageView,imageUrl,null,null);
    }

    public BitmapRequest(ImageView imageView,String imageUrl,DisplayConfig displayConfig) {
        this(imageView,imageUrl,displayConfig,null);
    }

    public BitmapRequest(ImageView imageView,String imageUrl,SimpleImageLoader.ImageListener imageListener) {
        this(imageView,imageUrl,null,imageListener);
    }

    public BitmapRequest(ImageView imageView, String imageUrl, DisplayConfig displayConfig,
                         SimpleImageLoader.ImageListener imageListener){
        this.imageViewSoftReference = new SoftReference<ImageView>(imageView);

        if (imageUrl != null) {
            imageView.setTag(imageUrl);
            imageUrlMd5 = Md5Util.toMD5(imageUrl);
        }
        this.imageUrl = imageUrl;

        if (displayConfig != null){
            this.disPlayConfig = displayConfig;
        }
        if (imageListener != null) {
            this.imageListener = imageListener;
        }
    }

    /**
     * 請(qǐng)求的先后順序是根據(jù)加載的策略進(jìn)行的艾帐,不同的策略比較的條件也不同乌叶,所以
     * 這里要把比較的邏輯交割具體的策略去做,策略有多種掩蛤,所以通過接口調(diào)用枉昏,增強(qiáng)擴(kuò)展性
     * @return
     */
    @Override
    public int compareTo(BitmapRequest o) {
        return loadPolicy.compareTo(o,this);
    }

    public int getSerialNum() {
        return serialNum;
    }

    public void setSerialNum(int serialNum) {
        this.serialNum = serialNum;
    }

    /**
     * 獲取這個(gè)請(qǐng)求對(duì)應(yīng)的ImageView
     * @return
     */
    public ImageView getImageView(){
        if (imageViewSoftReference == null)
            return null;
        return imageViewSoftReference.get();
    }

    public DisplayConfig getDisPlayConfig() {
        return disPlayConfig;
    }

    public LoadPolicy getLoadPolicy() {
        return loadPolicy;
    }

    public SoftReference<ImageView> getImageViewSoftReference() {
        return imageViewSoftReference;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public String getImageUrlMd5() {
        return imageUrlMd5;
    }

    public SimpleImageLoader.ImageListener getImageListener() {
        return imageListener;
    }

    /**
     * BitmapRequest會(huì)被加入請(qǐng)求隊(duì)列中陈肛,在隊(duì)列中有需要做判斷當(dāng)前請(qǐng)求是否存在
     * 那么就涉及到這個(gè)對(duì)象的比較揍鸟,所以需要重寫hashCode和equals方法
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BitmapRequest request = (BitmapRequest) o;
        return serialNum == request.serialNum &&
                loadPolicy.equals(request.loadPolicy);
    }

    @Override
    public int hashCode() {
        int result = loadPolicy != null ? loadPolicy.hashCode():0;
        result = 66*result+serialNum;
        return result;
    }

}

4.分發(fā)器實(shí)現(xiàn)原理

RequestDispatcher是一個(gè)繼承了Thread的線程對(duì)象,隊(duì)列創(chuàng)建后會(huì)根據(jù)配置創(chuàng)建出指定數(shù)量的分發(fā)器句旱,當(dāng)隊(duì)列中有請(qǐng)求對(duì)象后就從隊(duì)列中取出對(duì)象交給加載器阳藻,根據(jù)一定的協(xié)議選擇合適的加載器進(jìn)行網(wǎng)絡(luò)請(qǐng)求,當(dāng)隊(duì)列中沒有對(duì)象時(shí)谈撒,會(huì)進(jìn)入休眠狀態(tài)

package com.rzm.commonlibrary.general.imageloader.request;

import android.text.TextUtils;
import android.util.Log;

import com.rzm.commonlibrary.general.imageloader.loader.Loader;
import com.rzm.commonlibrary.general.imageloader.loader.LoaderManager;

import java.util.concurrent.BlockingQueue;

/**
 * Author:renzhenming
 * Time:2018/6/13 7:24
 * Description:This is RequestDispatcher
 */
public class RequestDispatcher extends Thread{

    /**
     * 從隊(duì)列中轉(zhuǎn)發(fā)請(qǐng)求需要持有隊(duì)列的引用
     */
    private BlockingQueue<BitmapRequest> blockingQueue;

    public RequestDispatcher(BlockingQueue<BitmapRequest> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    /**
     * 阻塞式隊(duì)列腥泥,轉(zhuǎn)發(fā)器開啟,從隊(duì)列中取請(qǐng)求隊(duì)列啃匿,如果沒有則會(huì)阻塞當(dāng)前線程蛔外,所以這里
     * 是在子線程開啟的
     */
    @Override
    public void run() {
        while(!isInterrupted()){
            try {
                BitmapRequest request = blockingQueue.take();
                //處理請(qǐng)求對(duì)象,交給loader
                String schema = parseSchema(request.getImageUrl());
                //獲取加載器
                Loader loader = LoaderManager.getInstance().getLoader(schema);
                if (loader == null){
                    Log.d("TAG",request.getImageUrl() + "沒有找到對(duì)應(yīng)的加載器");
                    return;
                }
                loader.load(request);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 根據(jù)圖片url判斷加載類型
     * @param imageUrl
     * @return
     */
    private String parseSchema(String imageUrl) {
        if (TextUtils.isEmpty(imageUrl)){
            return null;
        }
        if (imageUrl.contains("://")){
            //形如 http://xxx 或者file://xxx溯乒,這樣截取后
            //可以獲得http file等前綴夹厌,根據(jù)這個(gè)前綴獲取相應(yīng)
            //的加載器
            return imageUrl.split("://")[0];
        }else{
            Log.d("TAG","不持支的圖片類型");
        }
        return null;
    }
}

5.加載器實(shí)現(xiàn)原理

目前該框架支持網(wǎng)絡(luò)圖片和本地圖片加載,不同的加載器根據(jù)不同的url進(jìn)行選擇裆悄,為了提高擴(kuò)展性矛纹,設(shè)置接口和抽象類的實(shí)現(xiàn)方式,下面是頂層接口

package com.rzm.commonlibrary.general.imageloader.loader;


import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;

/**
 * Author:renzhenming
 * Time:2018/6/13 7:21
 * Description:This is Loader
 */
public interface Loader {

    /**
     * 加載圖片
     * @param request
     */
    void load(BitmapRequest request);
}

網(wǎng)絡(luò)加載器和本地加載器實(shí)現(xiàn)邏輯有所不同光稼,但是有一些公共的操作存在或南,比如加載前顯示加載中占位圖孩等,加載失敗顯示失敗圖片等等,這些操作可以放在一個(gè)公共的基類中實(shí)現(xiàn)采够,所以這里創(chuàng)建了一個(gè)抽象類作為上層類處理公共邏輯

package com.rzm.commonlibrary.general.imageloader.loader;

import android.graphics.Bitmap;
import android.widget.ImageView;


import com.rzm.commonlibrary.general.imageloader.cache.BitmapCache;
import com.rzm.commonlibrary.general.imageloader.config.DisplayConfig;
import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Author:renzhenming
 * Time:2018/6/13 7:22
 * Description:This is AbstractLoader
 * 加載器策略不同肄方,則不同的加載器實(shí)現(xiàn)方式不同,但是他們有相同的操作蹬癌,比如顯示
 * 占位圖等扒秸,所以這些相同操作在抽象一層出來
 */
public abstract class AbstractLoader implements Loader {

    private static final String TAG = "AbstractLoader";

    private AtomicInteger integer = new AtomicInteger(0);
    /**
     * 加載器加載圖片的邏輯是先緩存后網(wǎng)絡(luò),所以需要持有緩存對(duì)象的引用
     */
    private BitmapCache bitmapCache = SimpleImageLoader.getInstance().getConfig().getBitmapCache();

    /**
     * 同樣因?yàn)橐幚盹@示時(shí)的邏輯冀瓦,所以需要持有顯示配置對(duì)象的引用
     */
    private DisplayConfig displayConfig = SimpleImageLoader.getInstance().getConfig().getDisplayConfig();
    @Override
    public void load(BitmapRequest request) {
        //從緩存中獲取Bitmap
        Bitmap bitmap= null;
        if (bitmapCache != null) {
            bitmap = bitmapCache.get(request);
        }
        if (bitmap == null){
            //顯示加載中圖片
            showLoadingImg(request);
            //開始加載網(wǎng)絡(luò)圖伴奥,加載的邏輯不同加載器有所不同,所以交給各自
            //加載器實(shí)現(xiàn)翼闽,抽象
            bitmap = onLoad(request);
            if (bitmap == null){
                //加載失敗重試三次
                while(integer.incrementAndGet() <=3){
                    bitmap = onLoad(request);
                    if (bitmap != null){
                        break;
                    }
                }
                integer.set(0);
            }
            if (bitmap == null){
            }
            //加入緩存
            if (bitmapCache != null && bitmap != null)
                cacheBitmap(request,bitmap);
        }else{
            //有緩存
        }
        deliveryToUIThread(request,bitmap);
    }

    public abstract Bitmap onLoad(BitmapRequest request);

    protected void deliveryToUIThread(final BitmapRequest request, final Bitmap bitmap) {
        ImageView imageView = request.getImageView();
        if(imageView!=null) {
            imageView.post(new Runnable() {
                @Override
                public void run() {
                    updateImageView(request, bitmap);
                }

            });
        }

    }

    private void updateImageView(final BitmapRequest request, final Bitmap bitmap) {
        ImageView imageView = request.getImageView();
        //加載正常  防止圖片錯(cuò)位
        if(bitmap != null && imageView.getTag().equals(request.getImageUrl())){
            imageView.setImageBitmap(bitmap);
        }
        //有可能加載失敗
        if(bitmap == null && displayConfig!=null&&displayConfig.errorImage!=-1){
            imageView.setImageResource(displayConfig.errorImage);
        }
        //監(jiān)聽
        //回調(diào) 給圓角圖片  特殊圖片進(jìn)行擴(kuò)展
        if(request.getImageListener() != null){
            request.getImageListener().onComplete(imageView, bitmap, request.getImageUrl());
        }
    }
    /**
     * 緩存圖片
     * @param request
     * @param bitmap
     */
    private void cacheBitmap(BitmapRequest request, Bitmap bitmap) {
        if (request != null && bitmap != null){
            synchronized (AbstractLoader.class){
                bitmapCache.put(request,bitmap);
            }
        }
    }

    /**
     * 顯示加載中占位圖,需要判斷用戶有沒有配置
     * @param request
     */
    private void showLoadingImg(BitmapRequest request) {
        if (hasLoadingPlaceHolder()){
            final ImageView imageView = request.getImageView();
            if (imageView != null){
                imageView.post(new Runnable() {
                    @Override
                    public void run() {
                        imageView.setImageResource(displayConfig.loadingImage);
                    }
                });
            }
        }
    }

    /**
     * 是否設(shè)置了加載中圖片
     * @return
     */
    private boolean hasLoadingPlaceHolder() {
        return displayConfig != null && displayConfig.loadingImage > 0;
    }
}

接下來是具體的加載器實(shí)現(xiàn)

package com.rzm.commonlibrary.general.imageloader.loader;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.TextUtils;

import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
import com.rzm.commonlibrary.general.imageloader.utils.BitmapDecoder;
import com.rzm.commonlibrary.general.imageloader.utils.ImageViewHelper;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * Author:renzhenming
 * Time:2018/6/13 7:22
 * Description:This is UrlLoader
 * 網(wǎng)絡(luò)圖片加載器
 */
public class UrlLoader extends AbstractLoader {

    @Override
    public Bitmap onLoad(BitmapRequest request) {
        try {
            String imageUrl = request.getImageUrl();
            if (TextUtils.isEmpty(imageUrl)){
                return null;
            }
            URL url = new URL(imageUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            if (conn.getResponseCode() != 200){
                return null;
            }
            InputStream inputStream = conn.getInputStream();
            //轉(zhuǎn)化成BufferedInputStream
            final BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
            //標(biāo)記一下拾徙,reset后會(huì)重置到這個(gè)位置
            bufferedInputStream.mark(inputStream.available());
            BitmapDecoder decoder = new BitmapDecoder() {
                @Override
                public Bitmap decodeBitmapWithOptions(BitmapFactory.Options options) {
                    //第一次讀取,因?yàn)樵O(shè)置了inJustDecodeBounds為true感局,所以尼啡,這里decodeStream之后,會(huì)將寬高
                    //信息存儲(chǔ)在options中询微;第二次讀取崖瞭,因?yàn)樵O(shè)置了inJustDecodeBounds為false.所以會(huì)將流全部讀取
                    Bitmap bitmap = BitmapFactory.decodeStream(bufferedInputStream,null,options);
                   if (options.inJustDecodeBounds){
                       //表示時(shí)第一次執(zhí)行,此時(shí)只是為了獲取Bounds
                       try {
                           //第一次讀取圖片寬高信息撑毛,讀完之后书聚,要為第二次讀取做準(zhǔn)備,將流重置
                           bufferedInputStream.reset();
                       } catch (IOException e) {
                           e.printStackTrace();
                       }
                   }else{
                       try {
                           bufferedInputStream.close();
                       } catch (IOException e) {
                           e.printStackTrace();
                       }
                   }
                   return bitmap;
                }
            };
            //傳入控件的寬高藻雌,設(shè)置圖片適應(yīng)控件
            return decoder.decodeBitmap(ImageViewHelper.getImageViewWidth(request.getImageView()),
                    ImageViewHelper.getImageViewHeight(request.getImageView()));
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


        return null;
    }
}

package com.rzm.commonlibrary.general.imageloader.loader;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;

import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
import com.rzm.commonlibrary.general.imageloader.utils.BitmapDecoder;
import com.rzm.commonlibrary.general.imageloader.utils.ImageViewHelper;

import java.io.File;

/**
 * Author:renzhenming
 * Time:2018/6/13 7:22
 * Description:This is LocalLoader
 * 本地圖片加載器
 */
public class LocalLoader extends AbstractLoader {

    @Override
    public Bitmap onLoad(BitmapRequest request) {
        //得到本地圖片的路徑
        final String path = Uri.parse(request.getImageUrl()).getPath();
        File file = new File(path);
        if (!file.exists() || !file.isFile()){
            return null;
        }
        BitmapDecoder decoder = new BitmapDecoder() {
            @Override
            public Bitmap decodeBitmapWithOptions(BitmapFactory.Options options) {
                return BitmapFactory.decodeFile(path,options);
            }
        };
        return decoder.decodeBitmap(ImageViewHelper.getImageViewWidth(request.getImageView()),
                ImageViewHelper.getImageViewHeight(request.getImageView()));
    }
}

package com.rzm.commonlibrary.general.imageloader.utils;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * 圖片解碼器
 */
public abstract class BitmapDecoder {

    /**
     * 壓縮圖片
     * @param width imageView的寬度
     * @param height imageView的高度
     * @return
     */
    public Bitmap decodeBitmap(int width,int height){
        BitmapFactory.Options options = new BitmapFactory.Options();
        //設(shè)置為true 只讀取圖片的寬高雌续,不需要將整個(gè)圖片都加載到內(nèi)存
        options.inJustDecodeBounds = true;
        decodeBitmapWithOptions(options);
        //經(jīng)過上面一次操作,此時(shí)options中已經(jīng)有了寬高信息
        calculateSampleSizeWithOptions(options,width,height);
        //第二次就可以得到縮放后的bitmap了
        return decodeBitmapWithOptions(options);
    }

    /**
     * 將圖片寬高和控件寬高進(jìn)行比較胯杭,得到縮放值驯杜,信息仍然存儲(chǔ)在options中
     * @param options
     * @param viewWidth
     * @param viewHeight
     */
    private void calculateSampleSizeWithOptions(BitmapFactory.Options options,int viewWidth,int viewHeight) {
        //計(jì)算縮放比例

        //圖片的原始寬高
        int width = options.outWidth;
        int height = options.outHeight;

        int inSampleSize = 1;

        //當(dāng)圖片的寬高大于控件的寬高時(shí)才需要壓縮
        if (width > viewWidth || height > viewHeight){
            //計(jì)算出寬高的縮放比例
            int widthRatio = Math.round((float) width/(float)viewWidth);
            int heightRatio = Math.round((float)height/(float)viewHeight);

            //取寬高縮放比較大的值為圖片的縮放比
            inSampleSize = Math.max(widthRatio,heightRatio);
        }
        //設(shè)置到options中做个,options保存的是配置信息
        //當(dāng)inSampleSize為2鸽心,圖片的寬高會(huì)縮放為原來的1/2
        options.inSampleSize = inSampleSize;

        //每個(gè)像素2個(gè)字節(jié)
        options.inPreferredConfig = Bitmap.Config.RGB_565;

        //寬高已經(jīng)計(jì)算出來了,inJustDecodeBounds值可以復(fù)位了
        options.inJustDecodeBounds = false;

        //當(dāng)系統(tǒng)內(nèi)存不足時(shí).可以回收bitmap
        options.inPurgeable = true;
        options.inInputShareable = true;

    }

    /**
     * 將流的處理通過抽象方法暴露出來居暖,降低解碼器和外部的耦合
     * @param options
     */
    public abstract Bitmap decodeBitmapWithOptions(BitmapFactory.Options options);
}
6.本地緩存
package com.rzm.commonlibrary.general.imageloader.cache;

import android.graphics.Bitmap;
import android.util.LruCache;

import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;


/**
 * Author:renzhenming
 * Time:2018/6/13 7:20
 * Description:This is MemoryCache
 */
public class MemoryCache implements BitmapCache{

    private LruCache<String,Bitmap> mLruCache;

    public MemoryCache(){
        int maxSize = (int) (Runtime.getRuntime().maxMemory()/1024/8);
        mLruCache = new LruCache<String,Bitmap>(maxSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes()*value.getHeight();
            }
        };
    }
    @Override
    public Bitmap get(BitmapRequest request) {
        if (mLruCache == null) return null;
        return mLruCache.get(request.getImageUrlMd5());
    }

    @Override
    public void put(BitmapRequest request, Bitmap bitmap) {
        if (mLruCache == null) return;
        mLruCache.put(request.getImageUrlMd5(),bitmap);
    }

    @Override
    public void remove(BitmapRequest request) {
        if (mLruCache == null) return;
        mLruCache.remove(request.getImageUrlMd5());
    }
}

7.硬盤緩存
package com.rzm.commonlibrary.general.imageloader.disk;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import com.rzm.commonlibrary.general.imageloader.cache.BitmapCache;
import com.rzm.commonlibrary.general.imageloader.request.BitmapRequest;
import com.rzm.commonlibrary.general.imageloader.utils.IOUtil;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;


/**
 * Author:renzhenming
 * Time:2018/6/13 7:20
 * Description:This is DiskCache
 */
public class DiskCache implements BitmapCache {
    private static volatile DiskCache mDiskCache;
    //緩存路徑
    private String mCacheDir = "Image";
    //MB
    private static final int MB = 1024 * 1024;
    //jackwharton的杰作
    private DiskLruCache mDiskLruCache;

    private DiskCache(Context context)
    {
        iniDiskCache(context);
    }
    public static DiskCache getInstance(Context context) {
        if(mDiskCache==null)
        {
            synchronized (DiskCache.class)
            {
                if(mDiskCache==null)
                {
                    mDiskCache=new DiskCache(context);
                }
            }
        }
        return mDiskCache;
    }
    private void iniDiskCache(Context context) {
        //得到緩存的目錄  android/data/data/com.xxx/cache/Image
        File directory=getDiskCache(mCacheDir,context);
        if(!directory.exists())
        {
            directory.mkdirs();
        }
        try {
            //最后一個(gè)參數(shù) 指定緩存容量
            mDiskLruCache=DiskLruCache.open(directory,1,1,50*MB);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private File getDiskCache(String mCacheDir, Context context) {
        //默認(rèn)緩存路徑
        return new File(context.getCacheDir(),mCacheDir);
        //return new File(Environment.getExternalStorageDirectory(),mCacheDir);
    }

    @Override
    public void put(BitmapRequest request, Bitmap bitmap) {
        if (mDiskLruCache == null) return;
        DiskLruCache.Editor edtor=null;
        OutputStream os=null;
        try {
            //路徑必須是合法字符
            edtor=mDiskLruCache.edit(request.getImageUrlMd5());
            os=edtor.newOutputStream(0);
            if(persistBitmap2Disk(bitmap,os))
            {
                edtor.commit();
            }else {
                edtor.abort();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private boolean persistBitmap2Disk(Bitmap bitmap, OutputStream os) {
        BufferedOutputStream bos=new BufferedOutputStream(os);

        bitmap.compress(Bitmap.CompressFormat.JPEG,100,bos);
        try {
            bos.flush();
        } catch (IOException e) {
            e.printStackTrace();

        }finally {
            IOUtil.closeQuietly(bos);
        }
        return true;

    }

    @Override
    public Bitmap get(BitmapRequest request) {
        if (mDiskLruCache == null) return null;
        try {
            DiskLruCache.Snapshot snapshot=mDiskLruCache.get(request.getImageUrlMd5());
            if(snapshot!=null)
            {
                InputStream inputStream=snapshot.getInputStream(0);
                return BitmapFactory.decodeStream(inputStream);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void remove(BitmapRequest request) {
        if (mDiskLruCache == null) return;
        try {
            mDiskLruCache.remove(request.getImageUrlMd5());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末顽频,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子膝但,更是在濱河造成了極大的恐慌冲九,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異莺奸,居然都是意外死亡丑孩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門灭贷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來温学,“玉大人,你說我怎么就攤上這事甚疟≌提” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵览妖,是天一觀的道長(zhǎng)轧拄。 經(jīng)常有香客問我,道長(zhǎng)讽膏,這世上最難降的妖魔是什么檩电? 我笑而不...
    開封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮府树,結(jié)果婚禮上俐末,老公的妹妹穿的比我還像新娘。我一直安慰自己奄侠,他們只是感情好卓箫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著垄潮,像睡著了一般烹卒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上魂挂,一...
    開封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天甫题,我揣著相機(jī)與錄音,去河邊找鬼涂召。 笑死,一個(gè)胖子當(dāng)著我的面吹牛敏沉,可吹牛的內(nèi)容都是我干的果正。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盟迟,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼秋泳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起攒菠,我...
    開封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤迫皱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體田晚,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡生闲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年裳瘪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昼弟。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖奕筐,靈堂內(nèi)的尸體忽然破棺而出舱痘,到底是詐尸還是另有隱情,我是刑警寧澤离赫,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布芭逝,位于F島的核電站,受9級(jí)特大地震影響渊胸,放射性物質(zhì)發(fā)生泄漏铝耻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一蹬刷、第九天 我趴在偏房一處隱蔽的房頂上張望瓢捉。 院中可真熱鬧,春花似錦办成、人聲如沸泡态。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)某弦。三九已至,卻和暖如春而克,著一層夾襖步出監(jiān)牢的瞬間靶壮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工员萍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腾降,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓碎绎,卻偏偏與公主長(zhǎng)得像螃壤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子筋帖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容