欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 图片的三级缓存

程序员文章站 2022-07-02 09:45:42
...

Android 图片的三级缓存

三级缓存,顾名思义是有三个层级的操作:

1、内存缓存

2、本地缓存

3、网络

源码在下边。

首先咱们来说说内存

内存包括:强引用、软引用、弱引用、虚引用。强引用是默认的引用方式, 即使内存溢出,也不会回收。软引用(softReference), 内存不够时, 会考虑回收。 弱引用 (WeakReference)内存不够时, 更会考虑回收。虚引用(PhantomReference) 内存不够时, 最优先考虑回收! 一般我们常用到的引用就是强引用,比如引用创建一个成员变量里面的引用。对于GC来说, SoftReference的强度明显低于 SrongReference。SoftReference修饰的引用,其告诉GC:我是一个 软引用,当内存不足的时候,我指向的这个内存是可以给你释放掉的。一般对于这种占用内存资源比较大的,又不是必要的变量;或者一些占用大量内存资源的一些缓存的变量,就需要考虑 SoftReference。对于GC来说, WeakReference 的强度又明显低于 SoftReference 。 WeakReference 修饰的引用,其告诉GC:我是一个弱引用,对于你的要求我没有话说,我指向的这个内存是可以给你释放掉的。虚引用其实和上面讲到的各种引用不是一回事的,他主要是为跟踪一个对象何时被GC回收。在android里面也是有用到的:FileCleaner.java 。这些避免内存溢出的引用方式在Android 2.3+的版本上已经不再起太大作用, 因为垃圾回收器会频繁回收非强引用的对象, Android官方建议使用LRUCache。所以当我们用软引用进行内存缓存时会发现内存中的资源会被系统频繁回收。最终是从本地进行读数据。

LRUCache(Least Recently Used),就是近期最少使用的算法,核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。当调用put()方法时,就会在结合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即近期最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头。

下边直接上代码

/**
 * 内存缓存
 */
public class MemoryCache extends LruCache<String, Bitmap> {

    private LinkedHashMap<String, SoftReference<Bitmap>> mSoftCacheMap;
    private SoftReference<Bitmap> softReference;

    public MemoryCache() {
        //一般设置内存大小占系统内存的1/8
        super((int) (Runtime.getRuntime().maxMemory() / 8));
        this.mSoftCacheMap = new LinkedHashMap<String, SoftReference<Bitmap>>();
    }


    public Bitmap getBitmapFromMemory(String url) {
        Bitmap bitmap = get(url);
        if (bitmap != null) {
            Log.e("******", "从内存强引用中获取图片....");
            return bitmap;
        } else {
            softReference = mSoftCacheMap.get(url);
            if (softReference != null) {
                bitmap = softReference.get();
                if (bitmap != null) {
                    Log.e("******", "从内存软引用中获取图片.....");
                    this.put(url, bitmap);
                    return bitmap;
                }
            }
        }
        return null;
    }

    @Override // 获取图片大小
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

    @Override // 当有图片从强引用中移除时,将其放进软引用集合中
    protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
        if (oldValue != null) {
            SoftReference<Bitmap> softReference = new SoftReference<Bitmap>(oldValue);
            mSoftCacheMap.put(key, softReference);
        }
    }

    public Map<String, SoftReference<Bitmap>> getCacheMap() {
        return mSoftCacheMap;
    }

}

接着来说本地缓存:

其实就是把图片给缓存到本地,如果内存中没有的话,就从本地缓存中取

/***
 * 本地缓存
 */

public class LocalCache {

    /**
     * 从本地读取图片
     *
     * @param url
     */
    public Bitmap getBitmapFromLocal(String url) {
        String fileName = null;//把图片的url当做文件名,并进行MD5加密
        try {
            fileName = MD5Tools.MD5(url);
            File file = new File(PicCacheUtil.getInstance().CACHE_PATH, fileName);

            if(file.exists()) {
                Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
                return bitmap;
            }else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 从网络获取图片后,保存至本地缓存
     *
     * @param url
     * @param bitmap
     */
    public void setBitmapToLocal(String url, Bitmap bitmap) {
        try {
            String fileName = MD5Tools.MD5(url);//把图片的url当做文件名,并进行MD5加密
            File file = new File(PicCacheUtil.getInstance().CACHE_PATH, fileName);

            File parentFile = file.getParentFile();
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }

            //把图片保存至本地
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(file));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

最后说网络层,只有在第一次加载图片或者内存和本地都不存在的时候才会调用,其实就在线下载图片,频繁的调用,会导致资源浪费和OOM


/**
 * 从网络获取图片
 */
public class NetCache {

    private LocalCache mLocalCache;
    private MemoryCache mMemoryCache;

    public NetCache(LocalCache mLocalCache, MemoryCache mMemoryCache) {
        this.mLocalCache = mLocalCache;
        this.mMemoryCache = mMemoryCache;
    }

    /**
     * 从网络下载图片
     *
     * @param ivPic 显示图片的imageview
     * @param url   下载图片的网络地址
     */
    public void getBitmapFromNet(ImageView ivPic, String url) {
        new BitmapTask().execute(ivPic, url);

    }

    class BitmapTask extends AsyncTask<Object, Void, Bitmap> {

        private ImageView ivPic;
        private String url;

        @Override
        protected Bitmap doInBackground(Object[] params) {
            ivPic = (ImageView) params[0];
            url = (String) params[1];

            return downLoadBitmap(url);
        }
        @Override
        protected void onProgressUpdate(Void[] values) {
            super.onProgressUpdate(values);
        }

        @Override
        protected void onPostExecute(Bitmap result) {
            if (result != null) {
                ivPic.setImageBitmap(result);
                Log.e("******", "从网络缓存图片OK");

                //从网络获取图片后,保存至本地缓存
                mLocalCache.setBitmapToLocal(url, result);
                //保存至内存中
                mMemoryCache.put(url, result);

            }
        }
    }

    /**
     * 网络下载图片
     * @param url
     * @return
     */
    private Bitmap downLoadBitmap(String url) {
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);
            conn.setRequestMethod("GET");

            int responseCode = conn.getResponseCode();
            if (responseCode == 200) {
                //图片压缩
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize=2;//宽高压缩为原来的1/2
                options.inPreferredConfig = Bitmap.Config.ARGB_4444;
                Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream(), null, options);
                return bitmap;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            conn.disconnect();
        }

        return null;
    }
}

最后上个三级缓存工具类吧

public class PicCacheUtil {
    private static PicCacheUtil instance;
    public static String CACHE_PATH;
    public static int DEFAULT_PIC;
    private final MemoryCache mMemoryCache;
    private final LocalCache mLocalCache;
    private final NetCache mNetCache;
    private SoftReference<Bitmap> mSoftReference;

    public void init(String CACHE_PATH, int DEFAULT_PIC) {
        this.CACHE_PATH = CACHE_PATH;
        this.DEFAULT_PIC = DEFAULT_PIC;
    }

    private PicCacheUtil() {
        mMemoryCache = new MemoryCache();
        mLocalCache = new LocalCache();
        mNetCache = new NetCache(mLocalCache, mMemoryCache);
    }

    public static PicCacheUtil getInstance() {
        if (instance == null) {
            synchronized (PicCacheUtil.class) {
                if (instance == null) {
                    instance = new PicCacheUtil();
                }
            }
        }
        return instance;
    }


    public void display(ImageView iv, String url) {

        iv.setImageResource(this.DEFAULT_PIC);

        //先从内存中获取
        Bitmap btp = mMemoryCache.getBitmapFromMemory(url);
        if (btp != null) {
            iv.setImageBitmap(btp);
            return;
        }

        //内存中没有,从本地获取图片
        btp = mLocalCache.getBitmapFromLocal(url);
        if (btp != null) {
            iv.setImageBitmap(btp);
            mMemoryCache.put(url, btp);//放入LruCache中
            Log.e("******", "从本地获取图片.....");
            return;
        }

        //最后才走网络获取图片
        mNetCache.getBitmapFromNet(iv, url);
    }

    /**
     * 清除缓存
     */
    public void clearCache() {
        File file = new File(this.CACHE_PATH);
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            Log.e("*******", "缓存文件夹文件数量" + file.listFiles().length);
            for (int i = 0; i < files.length; i++) {
                File f = files[i];
                f.delete();
            }
        }
        Log.e("*******", "缓存文件夹文件数量" + file.listFiles().length);
    }

    /**
     * 获取缓存大小
     *
     * @return
     */
    public String getCacheSize() {
        String cache = "";
        try {
            long fileSize = 0;
            File file = new File(this.CACHE_PATH);
            if (file.isDirectory()) { // 如果路径是文件夹的时候
                fileSize = GetFileSize.getFileSize(file);
            } else {
                fileSize = GetFileSize.getFileSizes(file);
            }
            cache = GetFileSize.FormetFileSize(fileSize);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cache;
    }
}

源码位置,希望大家可以批评指正,好了,今天就到这里。