安卓图片三级缓存的集装类

图片的三级缓存相信大家都了解过了,废话不多说,直接入主题:

缓存最主要的类:

package com.text.imgcachetext;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *  @项目名: ImgCacheText
 *  @包名: com.text.imgcachetext
 *  @文件名: ImageCacheUtils
 *  @创建者: wxr
 *  @创建时间: 2016/11/2 13:59
 *  @描述: 图片缓存的工具类,虚拟内存缓存为可用内存的1/4,磁盘缓存为30M,网络连接失败默认3次重连
 *  @使用方式: 简单的链式调用ImageCacheUtils.with().load(String).into(ImageView);
 */
public class ImageCacheUtils {
    private static final String TAG               = "ImageCacheUtils";
    private static final String DISK_CACHE_SUBDIR = File.separator + "PictureCache";//磁盘缓存的文件夹名称
    //没有网络连接
    private static final int    NETWORN_NONE      = 0;
    //wifi连接
    private static final int    NETWORN_WIFI      = 1;
    //手机网络数据连接类型
    private static final int    NETWORN_2G        = 2;
    private static final int    NETWORN_3G        = 3;
    private static final int    NETWORN_4G        = 4;
    private static final int    NETWORN_MOBILE    = 5;


    private static ImageCacheUtils          mUtils       = null;
    private static int                      mBeats       = 3;//重连的总次数
    private        Context                  mContext     = null;
    private        LruCache<String, Bitmap> mLruCache    = null;
    private        Resources                mRes         = null;
    private        String                   mImgUrl      = null;//图片地址
    private        Bitmap                   mBitmap      = null;
    private        Handler                  mHandler     = null;
    private        ExecutorService          mCacheThread = null;//线程池
    private        File                     mDiskPath    = null;
    private static int                      mLruSize     = 4;//可用虚拟内存的大小 4-->1/4
    private static long                     mDiskSize    = 30 * 1024 * 1024;//磁盘缓存的大小,这里给30M
    private static boolean                  mIsLoadImg   = true;//是否加载图片

    /**构造函数*/
    private ImageCacheUtils(Context context) {
        this.mContext = context;
        if (this.mRes == null) { this.mRes = context.getResources(); }
        int maxMemory = (int) (Runtime.getRuntime().maxMemory());
        int cacheSize = maxMemory / this.mLruSize;
        this.mLruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
        this.mHandler = new Handler();
        this.mCacheThread = Executors.newCachedThreadPool();
        this.mDiskPath = getDiskPath();
    }
    //------------------>start  虚拟内存缓存方法<----------------

    /***
     * 读取缓存中的图片
     * @return
     */
    private Bitmap getMemoryBitmap(String url) {
        String fileName = this.getFileMD5(url);
        Bitmap bitmap   = mLruCache.get(fileName);
        return bitmap != null ?
               bitmap :
               null;
    }

    /**
     * 缓存中存储图片
     * @param bitmap
     */
    private void addMemoryBitmap(Bitmap bitmap, String url) {
        if (getMemoryBitmap(url) == null) {
            mLruCache.put(this.getFileMD5(url), bitmap);
        }
    }
    //------------------>end  虚拟内存缓存方法<------------------

    //------------------>start  磁盘图片存储和获取<----------------

    /**获取磁盘中的图片*/
    private Bitmap getDiskCacheBitmap(String url) {
        try {
            File file      = new File(mDiskPath, DISK_CACHE_SUBDIR);
            File cacheFile = new File(file, this.getFileMD5(url));
            if (!cacheFile.exists()) {return null;}
            Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(cacheFile));
            return bitmap != null ?
                   bitmap :
                   null;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /***
     * 把图片存放在本地
     * @param bitmap
     */
    private void addDiskCacheBitmap(Bitmap bitmap, String url) {
        if (this.getDiskCacheBitmap(url) != null) {return;}
        try {
            String fileName = this.getFileMD5(url);
            long   size     = 0;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                size = bitmap.getAllocationByteCount();
            }
            File path = this.getDiskDir(size);
            if (path == null) { return; }
            File file = new File(path, fileName);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(file));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 判断外置sd卡是否已满,
     * 不满就判断文件夹是否存在,不存在就创建文件夹,存在就删除文件
     * <code>{@link #deleteFile(File filePath, long fileSize)}</code>方法
     * @return 存储图片文件的路径对象
     */
    private File getDiskDir(long fileSize) {
        File cacheDir = mContext.getCacheDir();
        File file     = mDiskPath;
        File path     = null;
        if (diskIsFull(file)) {
            if (!file.getAbsolutePath().equals(cacheDir.getAbsolutePath())) {
                //外置SD卡内存不足
                path = new File(file, DISK_CACHE_SUBDIR);
                if (path.exists()) {deleteFile(path, fileSize);} else {
                    path = new File(cacheDir, DISK_CACHE_SUBDIR);
                    if (diskIsFull(cacheDir)) {
                        if (path.exists()) {deleteFile(path, fileSize);} else {
                            //走到这里就说明外置sd卡和内置内存都满了
                            return null;
                        }
                    }
                }
            } else {
                //内置内存满了
                path = new File(cacheDir, DISK_CACHE_SUBDIR);
                if (diskIsFull(cacheDir)) {
                    if (path.exists()) {deleteFile(path, fileSize);} else {
                        //走到这里就说明内置内存满了
                        return null;
                    }
                }
            }
        } else {
            //内存足够
            path = new File(file, DISK_CACHE_SUBDIR);
            //如果子文件夹存在时,删除文件。子文件夹不存在时,创建文件夹
            if (path.exists()) {
                deleteFile(path, fileSize);
            } else {path.mkdirs();}
        }
        return path;
    }

    /**
     * 获取磁盘路径
     * @return 当外置sd卡存在时返回外置sd卡的路径,不存在是返回内置内存的路径
     */
    private File getDiskPath() {
        //判断sd卡是否存在
        boolean SDIsExist = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
        return SDIsExist ?
               Environment.getExternalStorageDirectory() :
               mContext.getCacheDir();
    }

    /**
     * 判断磁盘内存是否已满
     * @param file 磁盘路径
     * @return true已满,false未满
     */
    private boolean diskIsFull(File file) {
        //计算磁盘可用大小
        long usableSpace = file.getUsableSpace();
        if (usableSpace < mDiskSize) {
            Log.e(TAG, "外置SD卡内存不足,无法做外置缓存,自动缓存到内置内存");
            return true;
        }
        return false;
    }

    /**
     * 根据修改时间删除文件,首先删除时间最久的。删除文件后重新计算大小
     * @param filePath 文件夹路径
     * @param fileSize 需要存放的文件大小
     */
    private void deleteFile(File filePath, long fileSize) {
        //判断文件夹大小
        long cacheSize = getCacheDirSize(filePath);
        if ((mDiskSize - cacheSize) > fileSize) {return;}

        File[] files   = filePath.listFiles();
        File   delFile = null;
        //获取第一个文件
        for (File fil : files) {
            if (fil.isFile()) { delFile = fil; }
        }
        //根据时间判断时间,保存时间最长的
        for (File fil : files) {
            if (fil.isFile()) { if (fil.lastModified() < delFile.lastModified()) {delFile = fil;} }
        }
        if (delFile != null && delFile.isFile()) { delFile.delete(); }
        //递归
        deleteFile(filePath, fileSize);
    }

    /**递归方式 计算缓存文件的大小*/
    private long getCacheDirSize(final File file) {
        if (file.isFile()) { return file.length(); }
        final File[] children = file.listFiles();
        long         total    = 0;
        if (children != null) {
            for (final File child : children) { total += getCacheDirSize(child); }
        }
        return total;
    }
    //------------------>end  磁盘图片存储和获取<------------------


    //------------------>start  公共方法,对外方法<----------------

    /**
     * 初始化对象
     * @param context 上下文
     */
    public static ImageCacheUtils init(Context context) {
        if (mUtils == null) {
            mUtils = new ImageCacheUtils(context);
        }
        return mUtils;
    }

    /**
     * 获取对象
     * @return 本类对象,链式调用
     */
    public static ImageCacheUtils with() {

        if (mUtils == null) {
            throw new RuntimeException(
                    "ImageCacheUtils初始化失败,请在Application的onCreate方法中调用init(Context context)方法进行初始化");
        }
        return mUtils;
    }

    /**
     * 设置可用虚拟内存的缓存大小
     * @param size 最少为2(当等于2,表示占可用虚拟内存的1/2,如此类推),不设置默认为4(就是1/4)
     * @return 本类对象,链式调用
     */
    public ImageCacheUtils setMemorySize(int size) {
        if (size < 2) {
            this.mLruSize = 2;
            return this;
        }
        this.mLruSize = size;
        return this;
    }

    /**
     * 设置磁盘可用空间的缓存大小
     * @param size 单位M(默认是30M)
     * @return 本类对象,链式调用
     */
    public ImageCacheUtils setDiskSize(int size) {
        if (size < 0) {return this;}
        mDiskSize = size * 1024 * 1024;
        return this;
    }

    /**
     * 设置失败重连的次数
     * @param beats 默认是3次
     * @return 本类对象,链式调用
     */
    public ImageCacheUtils setRelinkBeats(int beats) {
        if (beats < 0) {return this;}
        this.mBeats = beats;
        return this;
    }

    /**
     * 设置是否在所有网络下加载图片,先加载磁盘或者是缓存中的,如果都没有才选择是否请求网络
     * @param isOnAllNet 默认true在所有网络下都加载,false不加载(在wifi网络下加载,2/3/4G网络下不加载显示默认图片)
     * @return 本类对象,链式调用
     */
    public ImageCacheUtils setLoadImgOnAllNet(boolean isOnAllNet) {
        this.mIsLoadImg = isOnAllNet;
        return this;
    }

    /**
     * 设置图片的链接地址
     * @param url 图片地址
     * @return 本类对象,链式调用
     */
    public ImageCacheUtils load(String url) {
        mImgUrl = url;
        return this;
    }

    /**
     * 默认或网络请求失败后显示的图片
     * @param resourceId 图片的资源ID
     * @return 本类对象,链式调用
     */
    public ImageCacheUtils placeholder(int resourceId) {
        mBitmap = BitmapFactory.decodeResource(mRes, resourceId);
        return this;
    }

    /**
     * <br>设置图片(必须是ImageView的子类或者 有setImageBitmap(Bitmap bm)这个方法)
     * ,调用这个方法之前请先调用<code>{@link #placeholder(int resourceId)}</code>方法,
     * 标记已经设置好,不会造成图片错位。(如果有调用<code>{@link #load(String url)}</code>这个方法,
     * 在加载前会先显示该图片)
     * <br>1.从缓存里面读取图片
     * <br>2.从磁盘读取
     * <br>3.从网络加载
     * <br><b>注:该方法应当在最后调用
     * @param img 显示图片的控件
     */
    public void into(final ImageView img) {
        if (mBitmap != null) { img.setImageBitmap(mBitmap); }

        if (TextUtils.isEmpty(mImgUrl)) {
            throw new NullPointerException("网络地址为空,请先调用load(String url)方法!");
        }

        img.setTag(mImgUrl);

        mCacheThread.execute(new Runnable() {
            @Override
            public void run() {
                //子线程
                new SyncNetWork(img).netWork(mImgUrl);
            }
        });
    }
    //------------------>end  公共方法,对外方法<------------------

    //------------------>start  其他方法<----------------

    /**获取MD5码*/
    private String getFileMD5(String info) {
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(info.getBytes("UTF-8"));
            byte[] encryption = md5.digest();

            StringBuffer strBuf = new StringBuffer();
            for (int i = 0; i < encryption.length; i++) {
                if (Integer.toHexString(0xff & encryption[i]).length() == 1) {
                    strBuf.append("0").append(Integer.toHexString(0xff & encryption[i]));
                } else {
                    strBuf.append(Integer.toHexString(0xff & encryption[i]));
                }
            }
            return strBuf.toString();
        } catch (NoSuchAlgorithmException e) {
            return "";
        } catch (UnsupportedEncodingException e) {
            return "";
        }
    }

    /**是否是wifi状态true是*/
    private boolean isWifi() {
        return getNetworkState() == NETWORN_WIFI;
    }

    /**
     * 获取当前网络连接类型
     * @return
     */
    private int getNetworkState() {
        //获取系统的网络服务
        ConnectivityManager connManager = (ConnectivityManager) this.mContext.getSystemService(
                Context.CONNECTIVITY_SERVICE);
        //如果当前没有网络
        if (null == connManager) { return NETWORN_NONE; }
        //获取当前网络类型,如果为空,返回无网络
        NetworkInfo activeNetInfo = connManager.getActiveNetworkInfo();
        if (activeNetInfo == null || !activeNetInfo.isAvailable()) {
            return NETWORN_NONE;
        }
        // 判断是不是连接的是不是wifi
        NetworkInfo wifiInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
        if (null != wifiInfo) {
            NetworkInfo.State state = wifiInfo.getState();
            if (null != state) {
                if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) {
                    return NETWORN_WIFI;
                }
            }
        }
        // 如果不是wifi,则判断当前连接的是运营商的哪种网络2g、3g、4g等
        NetworkInfo networkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
        if (null != networkInfo) {
            NetworkInfo.State state          = networkInfo.getState();
            String            strSubTypeName = networkInfo.getSubtypeName();
            if (null != state) {
                if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) {
                    switch (activeNetInfo.getSubtype()) {
                        //如果是2g类型
                        case TelephonyManager.NETWORK_TYPE_GPRS: // 联通2g
                        case TelephonyManager.NETWORK_TYPE_CDMA: // 电信2g
                        case TelephonyManager.NETWORK_TYPE_EDGE: // 移动2g
                        case TelephonyManager.NETWORK_TYPE_1xRTT:
                        case TelephonyManager.NETWORK_TYPE_IDEN:
                            return NETWORN_2G;
                        //如果是3g类型
                        case TelephonyManager.NETWORK_TYPE_EVDO_A: // 电信3g
                        case TelephonyManager.NETWORK_TYPE_UMTS:
                        case TelephonyManager.NETWORK_TYPE_EVDO_0:
                        case TelephonyManager.NETWORK_TYPE_HSDPA:
                        case TelephonyManager.NETWORK_TYPE_HSUPA:
                        case TelephonyManager.NETWORK_TYPE_HSPA:
                        case TelephonyManager.NETWORK_TYPE_EVDO_B:
                        case TelephonyManager.NETWORK_TYPE_EHRPD:
                        case TelephonyManager.NETWORK_TYPE_HSPAP:
                            return NETWORN_3G;
                        //如果是4g类型
                        case TelephonyManager.NETWORK_TYPE_LTE:
                            return NETWORN_4G;
                        default:
                            //中国移动 联通 电信 三种3G制式
                            if (strSubTypeName.equalsIgnoreCase(
                                    "TD-SCDMA") || strSubTypeName.equalsIgnoreCase(
                                    "WCDMA") || strSubTypeName.equalsIgnoreCase("CDMA2000"))
                            {
                                return NETWORN_3G;
                            } else {
                                return NETWORN_MOBILE;
                            }
                    }
                }
            }
        }
        return NETWORN_NONE;
    }

    //------------------>end  其他方法<------------------
    private class SyncNetWork {

        private int       mFlag        = 0;//重连标记
        private ImageView mImg         = null;
        private Bitmap    mCacheBitmap = null;

        public SyncNetWork(ImageView img) {
            this.mImg = img;
        }

        /**
         * 连接网络加载图片
         * @param imgUrl 图片的URL
         */
        public void netWork(final String imgUrl) {
            //读取缓存中的图片
            mCacheBitmap = getMemoryBitmap(imgUrl);
            if (mCacheBitmap != null) {
                runOnUiThread(imgUrl);
                return;
            }

            //读取磁盘中的图片
            mCacheBitmap = getDiskCacheBitmap(imgUrl);
            if (mCacheBitmap != null) {
                runOnUiThread(imgUrl);
                return;
            }
            if (!mIsLoadImg) {
                //只在wifi下加载
                if (isWifi()) {
                    //加载
                    loadImg(imgUrl);
                }
                return;
            }
            loadImg(imgUrl);
        }

        /**
         * 加载网络图片
         * @param imgUrl 图片网络地址
         */
        private void loadImg(final String imgUrl) {
            InputStream is = null;
            try {
                URL               url  = new URL(imgUrl);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(5000);
                conn.setRequestMethod("GET");
                if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                    mFlag = 0;//清零
                    is = conn.getInputStream();
                    if (is == null) {
                        this.reLink(imgUrl);
                        return;
                    }
                    final Bitmap bitmap = BitmapFactory.decodeStream(is);
                    if (bitmap == null) {return;}

                    addMemoryBitmap(bitmap, imgUrl);
                    addDiskCacheBitmap(bitmap, imgUrl);

                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            //主线程
                            if (mImg != null && mImg.getTag().equals(imgUrl)) {
                                mImg.setImageBitmap(bitmap);
                            } else {
                                if (mBitmap != null) { mImg.setImageBitmap(mBitmap); }
                                reLink(imgUrl);
                            }
                        }
                    });
                } else {
                    //需要重新连接
                    this.reLink(imgUrl);
                }
                if (is != null) {is.close();}
            } catch (Exception e) {
                //需要重新连接
                this.reLink(imgUrl);
            }
        }

        /**失败重连*/
        private void reLink(String imgUrl) {
            if (mFlag < mBeats) {
                SystemClock.sleep(1000);
                Log.d(TAG, "正在重新连接...(" + mFlag + ")" + ",当前线程" + Thread.currentThread().getName());
                mFlag++;
                this.netWork(imgUrl);
            }
        }

        /**
         * 主线程运行
         * @param imgUrl 图片的URL 用来做比较比较
         */
        private void runOnUiThread(String imgUrl) {
            if (mImg != null && imgUrl.equals(mImg.getTag())) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mImg.setImageBitmap(mCacheBitmap);
                    }
                });
            }
        }
    }
}


Applocation类中的onCreate方法中添加:

        ImageCacheUtils init = ImageCacheUtils.init(getApplicationContext());
        init.setDiskSize(20);//设置磁盘大小,按需要  默认30M
        init.setLoadImgOnAllNet(false);//设置网络加载图片,按需要  默认true,所有网络都加载
        init.setMemorySize(2);//设置缓存大小,按需要 这里1/2默认1/4
        init.setRelinkBeats(3);//设置重连次数,按需要 默认是3次

AndroidManifest.xml清单文件:

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
      也别忘了在applocation节点加上:android:name="xxxx"这句。


调用方式:

ImageCacheUtils.with().load(图片地址).placeholder(默认的图片).into(ImageView);
有点类似于Grild,是的,用了Grild的变量名

其他公开的方法上面都有注释,这里就不说了。说说这流程吧


获取对象(单例)-->设置属性-->设置URL-->设置图片

反复调用:设置URL-->设置图片------>内部类执行,调用一次创建一个内部类对象

内部类对象:

1.检查内存,内存有,用内存,如果没有↓

2.检查磁盘,磁盘有,用磁盘,如果没有↓

3.网络请求,获取图片,先存到内存,再存到磁盘


磁盘的储存流程请看简单的流程图:




好了,就说到这里吧,如有问题,请发邮件到:binary_56@foxmail.com


转载时请标明出处,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值