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

来源:互联网 发布:淘宝直播达人合作 编辑:程序博客网 时间:2024/06/09 04:17

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

缓存最主要的类:

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


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

1 0
原创粉丝点击