天天看點

Android 圖檔的三級緩存

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;
    }
}
           

源碼位置,希望大家可以批評指正,好了,今天就到這裡。