1. 程式人生 > >圖片的快取和LruCache用法

圖片的快取和LruCache用法

為了保證記憶體的使用始終維持在一個合理的範圍,通常會把被移除螢幕的圖片進行回收處理。此時垃圾回收器也會認為你不再持有這些圖片的引用,從而對這些圖片進行GC操作(garbage collection:垃圾回收)。用這種思路來解決問題是非常好的,可是為了能讓程式快速執行,在介面上迅速地載入圖片,你又必須要考慮到某些圖片被回收之後,使用者又將它重新滑入螢幕這種情況。這時重新去載入一遍剛剛載入過的圖片無疑是效能的瓶頸,你需要想辦法去避免這個情況的發生。

這個時候,使用記憶體快取技術可以很好的解決這個問題,它可以讓元件快速地重新載入和處理圖片。下面我們就來看一看如何使用記憶體快取技術來對圖片進行快取,從而讓你的應用程式在載入很多圖片的時候可以提高響應速度和流暢性。

記憶體快取技術對那些大量佔用應用程式寶貴記憶體的圖片提供了快速訪問的方法。其中最核心的類是LruCache (此類在android-support-v4的包中提供) 。這個類非常適合用來快取圖片,它的主要演算法原理是把最近使用的物件用強引用儲存在 LinkedHashMap 中,並且把最近最少使用的物件在快取值達到預設定值之前從記憶體中移除。

在過去,開發者會經常使用一種非常流行的記憶體快取技術的實現,即軟引用或弱引用 (SoftReference or WeakReference)。但是現在已經不再推薦使用這種方式了,因為從 Android 2.3 (API Level 9)開始,垃圾回收器會更傾向於回收持有軟引用或弱引用的物件,這讓軟引用和弱引用變得不再可靠。另外,Android 3.0 (API Level 11)中,圖片的資料會儲存在本地的記憶體當中,因而無法用一種可預見的方式將其釋放,這就有潛在的風險造成應用程式的記憶體溢位並崩潰。

強引用:

    平時我們程式設計的時候例如:Object object=new Object();那object就是一個強引用了。如果一個物件具有強引用,
那就類似於必不可少的生活用品,垃圾回收器絕不會回收它。當記憶體空間不足,Java虛擬機器寧願丟擲OOM異常,
使程式異常終止,也不會回收具有強引用的物件來解決記憶體不足問題。

軟引用:

軟引用類似於可有可無的生活用品。如果記憶體空間足夠,垃圾回收器就不會回收它,如果記憶體空間不足了,就會回收這些物件的
記憶體。只要垃圾回收器沒有回收它,該物件就可以被程式使用。軟引用可用來實現記憶體敏感的快取記憶體。 軟引用可以和一個引
用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被垃圾回收,Java虛擬機器就會把這個軟引用加入到與之關聯
的引用佇列中。

使用軟引用能防止記憶體洩露,增強程式的健壯性。

弱引用:

記憶體不足時,會被回收。

虛引用:

最優先回收

強引用快取圖片:

//強引用:容易造成記憶體溢位,因為不會被回收,該物件的引用一直都在

private HashMap<String, Bitmap> mMemoryCache = new HashMap<String, Bitmap>();

  /**
     * 寫記憶體快取
     *
     * @param url
     * @param bitmap
     */
    public void setCache(String url, Bitmap bitmap) {
        mMemoryCache.put(url, bitmap);
    }


    /**
     * 讀記憶體快取
     *
     * @param url
     */
    public Bitmap getCache(String url) {
       return mMemoryCache.get(url);
    }

軟引用快取圖片:

//軟應用:使用軟引用:記憶體不足時,會被回收。

 private HashMap<String, SoftReference<Bitmap>> mMemoryCache = new HashMap<>();

  /**
     * 寫記憶體快取
     *
     * @param url
     * @param bitmap
     */
    public void setCache(String url, Bitmap bitmap) {
        //使用軟引用將bitmap包裝起來
        SoftReference<Bitmap> soft = new SoftReference<Bitmap>(bitmap);
        mMemoryCache.put(url, soft);
    }


    /**
     * 讀記憶體快取
     *
     * @param url
     */
    public Bitmap getCache(String url) {
       SoftReference<Bitmap> softReference = mMemoryCache.get(url);
        if (softReference != null) {
            Bitmap bitmap = softReference.get();
            return bitmap;
        }
        return null;   
    }

從 Android 2.3 (API Level 9)開始,垃圾回收器會更傾向於回收(記憶體足夠時也會回收)持有軟引用或弱引用的物件,
這讓軟引用和弱引用變得不再可靠。Google建議使用LruCache。

LruCache:記憶體快取技術,這個類非常適合用來快取圖片,它的主要演算法原理是把最近使用的物件用強引用
儲存在 LinkedHashMap 中,並且把最近最少使用的物件在快取值達到預設定值之前從記憶體中移除。
Lru: least recentlly used 最近最少使用演算法
自己可以控制記憶體的大小,可以將最近最少使用的物件回收掉, 從而保證記憶體不會超出範圍

 //LruCache;
    private LruCache<String, Bitmap> mMemoryCache;

    public MemoryCacheUtil() {
        // 獲取分配給app的記憶體大小
        long maxMemory = Runtime.getRuntime().maxMemory();
        // 使用最大可用記憶體值的1/8作為快取的大小。
        mMemoryCache = new LruCache<String, Bitmap>((int) (maxMemory / 8)) {

            //返回每個物件的大小
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //int byteCount = value.getRowBytes() * value.getHeight();// 計算圖片大小:每行位元組數*高度
                int byteCount = value.getByteCount();
                return byteCount;
            }
        };
    }
 /**
     * 寫記憶體快取
     *
     * @param url
     * @param bitmap
     */
    public void setCache(String url, Bitmap bitmap) {
        mMemoryCache.put(url, bitmap);
    }


    /**
     * 讀記憶體快取
     *
     * @param url
     */
    public Bitmap getCache(String url) {
        return mMemoryCache.get(url);
    }

圖片的三級快取:
1.先從記憶體快取中查詢圖片,如果找到,直接載入;
2.本地快取;
3.網路快取。

網路快取:

package com.xiaoyehai.threadpool.util;

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

/**
 * 網路快取工具類
 * Created by xiaoyehai on 2016/11/22.
 */
public class NetCacheUtils {

    private MemoryCacheUtil mMemoryCacheUtil;

    private LocalCacheUtils mLocalCacheUtils;

    public NetCacheUtils(MemoryCacheUtil mMemoryCacheUtil, LocalCacheUtils mLocalCacheUtils) {
        this.mMemoryCacheUtil = mMemoryCacheUtil;
        this.mLocalCacheUtils = mLocalCacheUtils;
    }

    public void getBitmapFromNet(ImageView imageView, String url) {
        new BitmapTask().execute(imageView, url); //啟動AsyncTask
        imageView.setTag(url); //設定標記,處理錯位
    }

    /**
     * AsyncTask:非同步任務類,可以實現非同步請求和主介面更新(Handler+執行緒池)
     * 1.doInBackground裡面的引數型別
     * 2.onProgressUpdate裡面的引數型別
     * 3.onPostExecute裡面的引數型別及doInBackground的返回型別
     */
    class BitmapTask extends AsyncTask<Object, Integer, Bitmap> {

        private ImageView iv;
        private String url;

        @Override
        protected void onPreExecute() {
            // TODO: 2017/1/31 預載入,執行在主執行緒
            super.onPreExecute();
        }

        @Override
        protected Bitmap doInBackground(Object... params) {
            // TODO: 2017/1/31  正在載入,執行在子執行緒,可以直接非同步請求
            iv = (ImageView) params[0];
            url = (String) params[1];

            //開始下載圖片
            Bitmap bitmap = HttpUtils.loadBitmap(url);
            return bitmap;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            // TODO: 2017/1/31 更新進度的方法,執行在主執行緒
            super.onProgressUpdate(values);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            // TODO: 2017/1/31 載入結束,執行在主執行緒,可以直接更新ui
            String str_url = (String) iv.getTag();
            if (bitmap != null && str_url.equals(url)) {
                iv.setImageBitmap(bitmap);

                //快取到本地
                mLocalCacheUtils.setCache(url, bitmap);
                //快取到記憶體
                mMemoryCacheUtil.setCache(url, bitmap);
            }
            super.onPostExecute(bitmap);
        }
    }
}

本地快取:

package com.xiaoyehai.threadpool.util;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
 * 本地(sd卡)快取工具類
 * Created by xiaoyehai on 2016/11/22.
 */
public class LocalCacheUtils {

    //快取的資料夾路徑
    public static final String FILE_PATH = Environment.getExternalStorageDirectory()
            .getAbsolutePath() + "/image_cache";

    /**
     * 寫本地快取
     *
     * @param url
     * @param bitmap
     */
    public void setCache(String url, Bitmap bitmap) {
        File dir = new File(FILE_PATH);
        if (!dir.exists() || dir.isDirectory()) {
            dir.mkdirs();//建立資料夾
        }
        try {
            String fileName = MD5Encoder.encode(url);
            File cacheFile = new File(dir, fileName);
            // 參1:圖片格式;參2:壓縮比例0-100; 參3:輸出流
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(cacheFile));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 讀本地快取
     *
     * @param url
     * @return
     */
    public Bitmap getCache(String url) {
        try {
            File cacheFile = new File(FILE_PATH, MD5Encoder.encode(url));
            if (cacheFile.exists()) {
                Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(cacheFile));
                return bitmap;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class MD5Encoder {

    public static String encode(String string) throws Exception {
        byte[] hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
        StringBuilder hex = new StringBuilder(hash.length * 2);
        for (byte b : hash) {
            if ((b & 0xFF) < 0x10) {
                hex.append("0");
            }
            hex.append(Integer.toHexString(b & 0xFF));
        }
        return hex.toString();
    }
}

記憶體快取:

package com.xiaoyehai.threadpool.util;

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

/**
 * 記憶體快取
 * 從 Android 2.3 (API Level 9)開始,垃圾回收器會更傾向於回收(記憶體足夠時也會回收)持有軟引用或弱引用的物件,
 * 這讓軟引用和弱引用變得不再可靠。Google建議使用LruCache。
 * <p>
 * Psrson p=new Person;強引用:不會被回收
 * 棧:存放物件的引用(p)
 * 堆:存放物件(new Person)
 * <p>
 * 垃圾回收器:只回收沒有引用的物件,不及時
 * 強引用(預設):垃圾回收器不會回收
 * 軟引用:記憶體不足時,會被回收。使用軟引用能防止記憶體洩露,增強程式的健壯性。
 * 弱引用:記憶體不足時,會被回收。
 * 虛引用:最優先回收
 * <p>
 * LruCache:記憶體快取技術,這個類非常適合用來快取圖片,它的主要演算法原理是把最近使用的物件用強引用
 * 儲存在 LinkedHashMap 中,並且把最近最少使用的物件在快取值達到預設定值之前從記憶體中移除。
 * Lru: least recentlly used 最近最少使用演算法
 * 自己可以控制記憶體的大小,可以將最近最少使用的物件回收掉, 從而保證記憶體不會超出範圍
 * <p>
 * Created by xiaoyehai on 2016/11/22.
 */
public class MemoryCacheUtil {


    //強引用:容易造成記憶體溢位,因為不會被回收,該物件的引用一直都在
    //private HashMap<String, Bitmap> mMemoryCache = new HashMap<String, Bitmap>();

    //軟應用:使用軟引用:記憶體不足時,會被回收。
    //private HashMap<String, SoftReference<Bitmap>> mMemoryCache = new HashMap<>();


    //LruCache;
    private LruCache<String, Bitmap> mMemoryCache;

    public MemoryCacheUtil() {
        // 獲取分配給app的記憶體大小
        long maxMemory = Runtime.getRuntime().maxMemory();
        // 使用最大可用記憶體值的1/8作為快取的大小。
        mMemoryCache = new LruCache<String, Bitmap>((int) (maxMemory / 8)) {

            //返回每個物件的大小
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //int byteCount = value.getRowBytes() * value.getHeight();// 計算圖片大小:每行位元組數*高度
                int byteCount = value.getByteCount();
                return byteCount;
            }
        };
    }


    /**
     * 寫記憶體快取
     *
     * @param url
     * @param bitmap
     */
    public void setCache(String url, Bitmap bitmap) {
        //mMemoryCache.put(url, bitmap);

        //使用軟引用將bitmap包裝起來
//        SoftReference<Bitmap> soft = new SoftReference<Bitmap>(bitmap);
//        mMemoryCache.put(url, soft);

        mMemoryCache.put(url, bitmap);
    }


    /**
     * 讀記憶體快取
     *
     * @param url
     */
    public Bitmap getCache(String url) {
        //return mMemoryCache.get(url);

//        SoftReference<Bitmap> softReference = mMemoryCache.get(url);
//        if (softReference != null) {
//            Bitmap bitmap = softReference.get();
//            return bitmap;
//        }
//        return null;

        return mMemoryCache.get(url);
    }
}

BitmapUtils:

package com.xiaoyehai.threadpool.util;

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

import com.xiaoyehai.threadpool.R;


/**
 * 自定義具有三級快取功能的圖片載入工具類
 * <p>
 * 1.記憶體快取:速度最快,不耗流量
 * 2.本地sd卡快取:速度快,不耗流量
 * 3.網路快取:速度慢,浪費流量
 * Created by xiaoyehai on 2016/11/22.
 */
public class BitmapUtils {

    private MemoryCacheUtil mMemoryCacheUtil;

    private LocalCacheUtils mLocalCacheUtils;

    private NetCacheUtils mNetCacheUtils;

    public BitmapUtils() {
        mMemoryCacheUtil = new MemoryCacheUtil();
        mLocalCacheUtils = new LocalCacheUtils();
        mNetCacheUtils = new NetCacheUtils(mMemoryCacheUtil, mLocalCacheUtils);
    }

    public void display(ImageView imageView, String url) {
        //設定預設圖片
        imageView.setImageResource(R.mipmap.ic_launcher);

        //一級快取:記憶體快取中獲取資料
        Bitmap bitmap = mMemoryCacheUtil.getCache(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
            return;
        }

        //二級快取:從本地(sd卡)快取中獲取資料
        bitmap = mLocalCacheUtils.getCache(url);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);

            //寫記憶體快取
            mMemoryCacheUtil.setCache(url, bitmap);
            return;
        }

        //三級快取:網路獲取資料
        mNetCacheUtils.getBitmapFromNet(imageView, url);
    }
}