Android圖片緩存策略

前言

在Android開發中,如果圖片過多,而我們又沒有對圖片進行有效的緩存,就很容易導致OOM(Out Of Memory)錯誤。因此,圖片的緩存是非常重要的,尤其是對圖片非常多的應用。現在很多框架都做了很好的圖片緩存處理。

一直想寫一個自己的圖片緩存框架,之前一直用Glide、Fresco等一些主流的圖片框架。這些框架對處理的處理都做的非常好,也查閱了這些框架的一些源碼,整體思路是使用三級緩存策略,通過網絡、本地、內存三級緩存圖片,來減少不必要的網絡交互,避免浪費流量。

什麼是三級緩存

  • 網絡緩存, 不優先加載, 速度慢,浪費流量
  • 本地緩存, 次優先加載, 速度快
  • 內存緩存, 優先加載, 速度最快(包括強引用(LruCache)和軟引用(SoftReference))

三級緩存原理

加載圖片時,判斷內存緩存中是否有該圖片,沒有則到本地SD卡中查找,SD卡中沒有則通過網絡請求獲取圖片。

因爲Android內存空間是很珍貴的,我們不能發過多的圖片在緩存中,容易導致OOM,這個時候我們就要使用軟引用(SoftReference)。軟引用的一個好處是當系統空間緊張的時候,軟引用可以隨時銷燬,因此軟引用是不會影響系統運行的,換句話說,如果系統因爲某個原因OOM了,那麼這個原因肯定不是軟引用引起的。

LruCache創建LruCache時,我們需要設置它的大小,一般是系統最大存儲空間的八分之一。LruCache的機制是存儲最近、最後使用的圖片,如果LruCache中的圖片大小超過了其默認大小,則會將最老、最遠使用的圖片移除出去。當圖片被LruCache移除的時候,我們需要手動將這張圖片添加到軟引用(SoftReference)中。我們需要在項目中維護一個由SoftReference組成的集合,其中存儲被LruCache移除出來的圖片

流程

實現

1、自定義圖片加載工具類(VImageUtils)

通過 VImageUtils.disPlay(ImageView ivPic, String url)提供給外部方法進行圖片加載。

/**
 * 自定義圖片緩存工具類
 *
 * @author Veer
 * @email [email protected]
 * @date 18/12/11
 */
public class VImageUtils {
    private static final String TAG = "VImageUtils";
    public static void disPlay(ImageView ivPic, String url) {
        Bitmap bitmap;
        //內存緩存
        bitmap=MemoryCacheUtils.getInstance().getBitmapFromMemory(url);
        if (bitmap!=null){
            ivPic.setImageBitmap(bitmap);
            Log.d(TAG,"從內存獲取圖片啦.....");
            return;
        }

        //本地緩存
        bitmap = LocalCacheUtils.getInstance().getBitmapFromLocal(url);
        if(bitmap !=null){
            ivPic.setImageBitmap(bitmap);
            Log.d(TAG,"從本地獲取圖片啦.....");
            //從本地獲取圖片後,保存至內存中
            MemoryCacheUtils.getInstance().setBitmapToMemory(url,bitmap);
            return;
        }
        //網絡緩存
        NetCacheUtils.getInstance().getBitmapFromNet(ivPic,url);
    }

}

2、網絡緩存(NetCacheUtils)

  • 網絡緩存中主要用到了AsyncTask來進行異步數據的加載
  • 簡單來說,AsyncTask可以看作是一個對handler和線程池的封裝,通常,AsyncTask主要用於數據簡單時,handler+thread主要用於數據量多且複雜時,當然這也不是必須的,仁者見仁智者見智。
  • 同時,爲了避免內存溢出的問題,我們可以在獲取網絡圖片後。對其進行圖片壓縮。
/**
 * 網絡緩存
 *
 * @author Veer
 * @email [email protected]
 * @date 18/12/11
 */
public class NetCacheUtils {
    private static  NetCacheUtils mInstance;
    private static final String TAG = "NetCacheUtils";
    private LocalCacheUtils mLocalCacheUtils;
    private MemoryCacheUtils mMemoryCacheUtils;

    private NetCacheUtils() {
        mMemoryCacheUtils = MemoryCacheUtils.getInstance();
        mLocalCacheUtils = LocalCacheUtils.getInstance();
    }
    public static NetCacheUtils getInstance(){
        if(mInstance == null){
            synchronized (NetCacheUtils.class) {
                if (mInstance == null) {
                    mInstance = new NetCacheUtils();
                }
            }
        }
        return mInstance;
    }
    /**
     * 從網絡下載圖片
     * @param ivPic 顯示圖片的imageview
     * @param url   下載圖片的網絡地址
     */
    public void getBitmapFromNet(ImageView ivPic, String url) {
        new BitmapTask().execute(ivPic, url);//啓動AsyncTask

    }

    /**
     * AsyncTask就是對handler和線程池的封裝
     * 第一個泛型:參數類型
     * 第二個泛型:更新進度的泛型
     * 第三個泛型:onPostExecute的返回結果
     */
    class BitmapTask extends AsyncTask<Object, Void, Bitmap> {

        private ImageView ivPic;
        private String url;

        /**
         * 後臺耗時操作,存在於子線程中
         * @param params
         * @return
         */
        @Override
        protected Bitmap doInBackground(Object[] params) {
            ivPic = (ImageView) params[0];
            url = (String) params[1];

            return downLoadBitmap(url);
        }

        /**
         * 更新進度,在主線程中
         * @param values
         */
        @Override
        protected void onProgressUpdate(Void[] values) {
            super.onProgressUpdate(values);
        }

        /**
         * 耗時方法結束後執行該方法,主線程中
         * @param result
         */
        @Override
        protected void onPostExecute(Bitmap result) {
            if (result != null) {
                ivPic.setImageBitmap(result);
                Log.d(TAG,"從網絡緩存圖片啦.....");

                //從網絡獲取圖片後,保存至本地緩存
                mLocalCacheUtils.setBitmapToLocal(url, result);
                //保存至內存中
                mMemoryCacheUtils.setBitmapToMemory(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;
    }
}



3、本地緩存(LocalCacheUtils)

  • 在初次通過網絡獲取圖片後,我們可以在本地SD卡中將圖片保存起來
  • 可以使用MD5加密圖片的網絡地址,來作爲圖片的名稱保存
/**
 * 本地緩存
 *
 * @author Veer
 * @email [email protected]
 * @date 18/12/11
 */
public class LocalCacheUtils {
    private static final String CACHE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/VImageUtils";
    private static  LocalCacheUtils mInstance;
    private LocalCacheUtils(){

    }
    public static LocalCacheUtils getInstance(){
        if(mInstance == null){
            synchronized (LocalCacheUtils.class) {
                if (mInstance == null) {
                    mInstance = new LocalCacheUtils();
                }
            }
        }
        return mInstance;
    }
    /**
     * 從本地讀取圖片
     * @param url
     * @return Bitmap
     */
    public Bitmap getBitmapFromLocal(String url) {
        String fileName = null;//把圖片的url當做文件名,並進行MD5加密
        try {
            fileName = MD5Encoder.encode(url);
            File file = new File(CACHE_PATH, fileName);

            Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file));

            return bitmap;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
    /**
     * 從網絡獲取圖片後,保存至本地緩存
     * @param url
     * @param bitmap
     */
    public void setBitmapToLocal(String url, Bitmap bitmap) {
        try {
            String fileName = MD5Encoder.encode(url);//把圖片的url當做文件名,並進行MD5加密
            File file = new File(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();
        }

    }
}

4、內存緩存(MemoryCacheUtils)

  • 這塊東西比較重要,也是圖片緩存的關鍵一步
  • 通過LruCache<String,Bitmap> 來緩存我們的圖片,由於我們LruCache的空間有限,我們把剩餘的圖片放入
    Map<String, SoftReference<Bitmap>> 軟引用中,這樣不會造成內存泄露。
  • 在向緩存中取圖片時,通過最少最近使用算法,先從LruCache中查找,在LruCache中沒有再從SoftReference軟引用中查找。在SoftReference查到然後重新放入LruCache中。
/**
 * 內存緩存
 *
 * @author Veer
 * @email [email protected]
 * @date 18/12/11
 */
public class MemoryCacheUtils {
    private static  MemoryCacheUtils mInstance;
    private ImageCache mImageCache;

    private MemoryCacheUtils(){
        Map<String, SoftReference<Bitmap>> cacheMap = new HashMap<>();
        mImageCache = new ImageCache(cacheMap);

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

    /**
     * 從內存中讀圖片
     * @param url
     */
    public Bitmap getBitmapFromMemory(String url) {
        Bitmap bitmap = mImageCache.get(url);
        // 如果圖片不存在強引用中,則去軟引用(SoftReference)中查找
        if(bitmap == null){
            Map<String, SoftReference<Bitmap>> cacheMap = mImageCache.getCacheMap();
            SoftReference<Bitmap> softReference = cacheMap.get(url);
            if(softReference!=null){
                bitmap = softReference.get();
                //重新放入強引用緩存中
                mImageCache.put(url,bitmap);
            }
        }
        return bitmap;

    }

    /**
     * 往內存中寫圖片
     * @param url
     * @param bitmap
     */
    public void setBitmapToMemory(String url, Bitmap bitmap) {
        mImageCache.put(url,bitmap);
    }

}
ImageCache代碼:
/**
 * 圖片緩存
 *
 * @author Veer
 * @email [email protected]
 * @date 18/12/11
 */

public class ImageCache extends LruCache<String,Bitmap> {

    private Map<String, SoftReference<Bitmap>> cacheMap;

    public ImageCache(Map<String, SoftReference<Bitmap>> cacheMap) {
        super((int) (Runtime.getRuntime().maxMemory() / 8));
        this.cacheMap = cacheMap;
    }

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

    // 當有圖片從LruCache中移除時,將其放進軟引用集合中
    @Override
    protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
        if (oldValue != null) {
            SoftReference<Bitmap> softReference = new SoftReference<Bitmap>(oldValue);
            cacheMap.put(key, softReference);
        }
    }

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

總結

以上就是圖片緩存的策略,後期我會完善我的這個圖片框架,爭取做到向那些主流框架一樣。

會一直在我的github上更新,希望多多關注

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章