Android圖片緩存之Lru算法


前言:

     上篇我們總結了Bitmap的處理,同時對比了各種處理的效率以及對內存占用大小。我們得知一個應用如果使用大量圖片就會導致OOM(out of memory),那該如何處理才能近可能的降低oom發生的概率呢?之前我們一直在使用SoftReference軟引用,SoftReference是一種現在已經不再推薦使用的方式,因為從 Android 2.3 (API Level 9)開始,垃圾回收器會更傾向於回收持有軟引用或弱引用的對象,這讓軟引用變得不再可靠,所以今天我們來認識一種新的緩存處理算法Lru,然后學習一下基於Lru的Lrucache、DiskLruCache 實現我們的圖片緩存。

 圖片緩存相關博客地址:

Lru:

   LRU是Least Recently Used 的縮寫,翻譯過來就是“最近最少使用”,LRU緩存就是使用這種原理實現,簡單的說就是緩存一定量的數據,當超過設定的閾值時就把一些過期的數據刪除掉,比如我們緩存10000條數據,當數據小於10000時可以隨意添加,當超過10000時就需要把新的數據添加進來,同時要把過期數據刪除,以確保我們最大緩存10000條,那怎么確定刪除哪條過期數據呢,采用LRU算法實現的話就是將最老的數據刪掉。

基於LruCache實現內存緩存:

 1.)初始化MemoryCache

  這里內存緩存的是Drawable 而不是Bitmap 理由是Drawable相對Bitmap來說有很大的內存優勢

        int maxMemory = (int) Runtime.getRuntime().maxMemory();//獲取系統分配給應用的總內存大小
        int mCacheSize = maxMemory / 8;//設置圖片內存緩存占用八分之一
        mMemoryCache = new LruCache<String, Drawable>(mCacheSize) {
            //必須重寫此方法,來測量Bitmap的大小
            @Override
            protected int sizeOf(String key, Drawable value) {
                if (value instanceof BitmapDrawable) {
                    Bitmap bitmap = ((BitmapDrawable) value).getBitmap();
                    return bitmap == null ? 0 : bitmap.getByteCount();
                }
                return super.sizeOf(key, value);
            }
        };

2.)添加一個Drawable到內存緩存

  /**
     * 添加Drawable到內存緩存
     *
     * @param key
     * @param drawable
     */
    private void addDrawableToMemoryCache(String key, Drawable drawable) {
        if (getDrawableFromMemCache(key) == null && drawable != null) {
            mMemoryCache.put(key, drawable);
        }
    }

3.)從內存緩存中獲取一個Drawable

    /**
     * 從內存緩存中獲取一個Drawable
     *
     * @param key
     * @return
     */
    public Drawable getDrawableFromMemCache(String key) {
        return mMemoryCache.get(key);
    }

4.)從內存緩存中移除一個Drawable

   /**
     * 從內存緩存中移除
     *
     * @param key
     */
    public void removeCacheFromMemory(String key) {
        mMemoryCache.remove(key);
    }

5.)清空內存緩存

    /**
     * 清理內存緩存
     */
    public void cleanMemoryCCache() {
        mMemoryCache.evictAll();
    }

其實Lru緩存機制本質上就是存儲在一個LinkedHashMap存儲,為了保障插入的數據順序,方便清理。

基於DiskLruCache實現磁盤緩存:

   DiskLruCache類並不是谷歌官方實現,需要自行下載,下載地址:https://github.com/JakeWharton/DiskLruCache

  1.)初始化DiskLruCache

       File cacheDir = context.getCacheDir();//指定的是數據的緩存地址
        long diskCacheSize = 1024 * 1024 * 30;//最多可以緩存多少字節的數據
        int appVersion = DiskLruUtils.getAppVersion(context);//指定當前應用程序的版本號
        int valueCount = 1;//指定同一個key可以對應多少個緩存文件
        try {
            mDiskCache = DiskLruCache.open(cacheDir, appVersion, valueCount, diskCacheSize);
        } catch (Exception ex) {
        }

2.)寫入一個文件到磁盤緩存

    /**
     * 添加Bitmap到磁盤緩存
     *
     * @param key
     * @param value
     */
    private void addBitmapToDiskCache(String key, byte[] value) {
        OutputStream out = null;
        try {
            DiskLruCache.Editor editor = mDiskCache.edit(key);
            if (editor != null) {
                out = editor.newOutputStream(0);
                if (value != null && value.length > 0) {
                    out.write(value);
                    out.flush();
                    editor.commit();
                } else {
                    editor.abort();
                }
            }
            mDiskCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            DiskLruUtils.closeQuietly(out);
        }
    }

3.)從磁盤緩存中讀取Drawable

    /**
     * 從磁盤緩存中獲取一個Drawable
     *
     * @param key
     * @return
     */
    public Drawable getDrawableFromDiskCache(String key) {
        try {
            DiskLruCache.Snapshot snapShot = mDiskCache.get(key);
            if (snapShot != null) {
                InputStream is = snapShot.getInputStream(0);
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                Drawable drawable = DiskLruUtils.bitmap2Drawable(bitmap);
                //從磁盤中讀取到之后 加入內存緩存
                addDrawableToMemoryCache(key, drawable);
                return drawable;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

4.)從磁盤緩存中移除

    /**
     * 從磁盤緩存中移除
     *
     * @param key
     */
    public void removeCacheFromDisk(String key) {
        try {
            mDiskCache.remove(key);
        } catch (Exception e) {
        }
    }

5.)清空磁盤緩存

    /**
     * 清理磁盤緩存
     */
    public void cleanDiskCache() {
        try {
            mDiskCache.delete();
        } catch (Exception e) {
        }
    }

圖片下載過程:

   接下來實例中用到了一點RxJava的知識有不了解RxJava的請自行了解一下。

  1.)采用異步方式操作磁盤緩存和網絡下載, 內存緩存可以在主線程中操作

   public void disPlay(final ImageView imageView, String imageUrl) {
        //生成唯一key
        final String key = DiskLruUtils.hashKeyForDisk(imageUrl);
        //先從內存中讀取
        Drawable drawableFromMemCache = getDrawableFromMemCache(key);
        if (drawableFromMemCache != null) {
            imageView.setImageDrawable(drawableFromMemCache);
            return;
        }
        Observable.just(imageUrl)
                .map(new Func1<String, Drawable>() {
                    @Override
                    public Drawable call(String imageUrl) { // 參數類型 String
                        //從磁盤中讀取
                        Drawable drawableFromDiskCache = getDrawableFromDiskCache(key);
                        if (drawableFromDiskCache != null) {
                            return drawableFromDiskCache;
                        }
                        //網絡下載
                        return download(imageUrl); // 返回類型 Drawable
                    }
                })
                .subscribeOn(Schedulers.io()) // 指定 subscribe() 發生在 IO 線程
                .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回調發生在主線程
                .subscribe(new Action1<Drawable>() {
                    @Override
                    public void call(Drawable drawable) { // 參數類型 Drawable
                        imageView.setImageDrawable(drawable);
                    }
                });
    }

2.)下載圖片過程以及處理

 private Drawable download(String imageUrl) {
        HttpURLConnection urlConnection = null;
        ByteArrayOutputStream bos = null;
        InputStream ins = null;
        try {
            final URL url = new URL(imageUrl);
            urlConnection = (HttpURLConnection) url.openConnection();
            ins = urlConnection.getInputStream();
            bos = new ByteArrayOutputStream();
            int b;
            while ((b = ins.read()) != -1) {
                bos.write(b);
            }
            bos.flush();
            byte[] bytes = bos.toByteArray();
            Bitmap bitmap = DiskLruUtils.bytes2Bitmap(bytes);
            String key = DiskLruUtils.hashKeyForDisk(imageUrl);
            Drawable drawable = DiskLruUtils.bitmap2Drawable(bitmap);
            //加入內存緩存
            addDrawableToMemoryCache(key, drawable);
            //加入磁盤緩存
            addBitmapToDiskCache(key, bytes);
            return drawable;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            DiskLruUtils.closeQuietly(bos);
            DiskLruUtils.closeQuietly(ins);
        }
        return null;
    }

 

附上最終圖片緩存單例簡單實現全部代碼以及DiskLruUtils工具類代碼

ImageLoadManager.java
public class ImageLoadManager {
    private LruCache<String, Drawable> mMemoryCache;//內存緩存
    private DiskLruCache mDiskCache;//磁盤緩存
    private static ImageLoadManager mInstance;//獲取圖片下載單例引用

    /**
     * 構造器
     *
     * @param context
     */
    private ImageLoadManager(Context context) {
        int maxMemory = (int) Runtime.getRuntime().maxMemory();//獲取系統分配給應用的總內存大小
        int mCacheSize = maxMemory / 8;//設置圖片內存緩存占用八分之一
        mMemoryCache = new LruCache<String, Drawable>(mCacheSize) {
            //必須重寫此方法,來測量Bitmap的大小
            @Override
            protected int sizeOf(String key, Drawable value) {
                if (value instanceof BitmapDrawable) {
                    Bitmap bitmap = ((BitmapDrawable) value).getBitmap();
                    return bitmap == null ? 0 : bitmap.getByteCount();
                }
                return super.sizeOf(key, value);
            }
        };

        File cacheDir = context.getCacheDir();//指定的是數據的緩存地址
        long diskCacheSize = 1024 * 1024 * 30;//最多可以緩存多少字節的數據
        int appVersion = DiskLruUtils.getAppVersion(context);//指定當前應用程序的版本號
        int valueCount = 1;//指定同一個key可以對應多少個緩存文件
        try {
            mDiskCache = DiskLruCache.open(cacheDir, appVersion, valueCount, diskCacheSize);
        } catch (Exception ex) {
        }
    }

    /**
     * 獲取單例引用
     *
     * @return
     */
    public static ImageLoadManager getInstance(Context context) {
        ImageLoadManager inst = mInstance;
        if (inst == null) {
            synchronized (RequestManager.class) {
                inst = mInstance;
                if (inst == null) {
                    inst = new ImageLoadManager(context.getApplicationContext());
                    mInstance = inst;
                }
            }
        }
        return inst;
    }

    public void disPlay(final ImageView imageView, String imageUrl) {
        //生成唯一key
        final String key = DiskLruUtils.hashKeyForDisk(imageUrl);
        //先從內存中讀取
        Drawable drawableFromMemCache = getDrawableFromMemCache(key);
        if (drawableFromMemCache != null) {
            imageView.setImageDrawable(drawableFromMemCache);
            return;
        }
        Observable.just(imageUrl)
                .map(new Func1<String, Drawable>() {
                    @Override
                    public Drawable call(String imageUrl) { // 參數類型 String
                        //從磁盤中讀取
                        Drawable drawableFromDiskCache = getDrawableFromDiskCache(key);
                        if (drawableFromDiskCache != null) {
                            return drawableFromDiskCache;
                        }
                        //網絡下載
                        return download(imageUrl); // 返回類型 Drawable
                    }
                })
                .subscribeOn(Schedulers.io()) // 指定 subscribe() 發生在 IO 線程
                .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回調發生在主線程
                .subscribe(new Action1<Drawable>() {
                    @Override
                    public void call(Drawable drawable) { // 參數類型 Drawable
                        imageView.setImageDrawable(drawable);
                    }
                });
    }


    /**
     * 添加Drawable到內存緩存
     *
     * @param key
     * @param drawable
     */
    private void addDrawableToMemoryCache(String key, Drawable drawable) {
        if (getDrawableFromMemCache(key) == null && drawable != null) {
            mMemoryCache.put(key, drawable);
        }
    }

    /**
     * 從內存緩存中獲取一個Drawable
     *
     * @param key
     * @return
     */
    public Drawable getDrawableFromMemCache(String key) {
        return mMemoryCache.get(key);
    }

    /**
     * 從磁盤緩存中獲取一個Drawable
     *
     * @param key
     * @return
     */
    public Drawable getDrawableFromDiskCache(String key) {
        try {
            DiskLruCache.Snapshot snapShot = mDiskCache.get(key);
            if (snapShot != null) {
                InputStream is = snapShot.getInputStream(0);
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                Drawable drawable = DiskLruUtils.bitmap2Drawable(bitmap);
                //從磁盤中讀取到之后 加入內存緩存
                addDrawableToMemoryCache(key, drawable);
                return drawable;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 添加Bitmap到磁盤緩存
     *
     * @param key
     * @param value
     */
    private void addBitmapToDiskCache(String key, byte[] value) {
        OutputStream out = null;
        try {
            DiskLruCache.Editor editor = mDiskCache.edit(key);
            if (editor != null) {
                out = editor.newOutputStream(0);
                if (value != null && value.length > 0) {
                    out.write(value);
                    out.flush();
                    editor.commit();
                } else {
                    editor.abort();
                }
            }
            mDiskCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            DiskLruUtils.closeQuietly(out);
        }
    }

    private Drawable download(String imageUrl) {
        HttpURLConnection urlConnection = null;
        ByteArrayOutputStream bos = null;
        InputStream ins = null;
        try {
            final URL url = new URL(imageUrl);
            urlConnection = (HttpURLConnection) url.openConnection();
            ins = urlConnection.getInputStream();
            bos = new ByteArrayOutputStream();
            int b;
            while ((b = ins.read()) != -1) {
                bos.write(b);
            }
            bos.flush();
            byte[] bytes = bos.toByteArray();
            Bitmap bitmap = DiskLruUtils.bytes2Bitmap(bytes);
            String key = DiskLruUtils.hashKeyForDisk(imageUrl);
            Drawable drawable = DiskLruUtils.bitmap2Drawable(bitmap);
            //加入內存緩存
            // addDrawableToMemoryCache(key, drawable);
            //加入磁盤緩存
            addBitmapToDiskCache(key, bytes);
            return drawable;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            DiskLruUtils.closeQuietly(bos);
            DiskLruUtils.closeQuietly(ins);
        }
        return null;
    }

    /**
     * 從緩存中移除
     *
     * @param key
     */
    public void removeCache(String key) {
        removeCacheFromMemory(key);
        removeCacheFromDisk(key);
    }

    /**
     * 從內存緩存中移除
     *
     * @param key
     */
    public void removeCacheFromMemory(String key) {
        mMemoryCache.remove(key);
    }

    /**
     * 從磁盤緩存中移除
     *
     * @param key
     */
    public void removeCacheFromDisk(String key) {
        try {
            mDiskCache.remove(key);
        } catch (Exception e) {
        }
    }

    /**
     * 磁盤緩存大小
     *
     * @return
     */
    public long diskCacheSize() {

        return mDiskCache.size();
    }

    /**
     * 內存緩存大小
     *
     * @return
     */
    public long memoryCacheSize() {

        return mMemoryCache.size();
    }

    /**
     * 關閉磁盤緩存
     */
    public void closeDiskCache() {
        try {
            mDiskCache.close();
        } catch (Exception e) {
        }
    }

    /**
     * 清理緩存
     */
    public void cleanCache() {
        cleanMemoryCCache();
        cleanDiskCache();
    }

    /**
     * 清理磁盤緩存
     */
    public void cleanDiskCache() {
        try {
            mDiskCache.delete();
        } catch (Exception e) {
        }
    }

    /**
     * 清理內存緩存
     */
    public void cleanMemoryCCache() {
        mMemoryCache.evictAll();
    }
}
ImageLoadManager.java
DiskLruUtils.java
final class DiskLruUtils {

    /**
     * 關閉輸入輸出流
     */
    public static void closeQuietly(/*Auto*/Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (RuntimeException rethrown) {
                throw rethrown;
            } catch (Exception ignored) {
            }
        }
    }

    /**
     * 獲取versionCode
     */
    public static int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }


    public static String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }

    public static String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

    /**
     * Bitmap → bytes
     */
    public static byte[] bitmap2Bytes(Bitmap bm) {
        if (bm == null) {
            return null;
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
        return baos.toByteArray();
    }

    /**
     * bytes → Bitmap
     */
    public static Bitmap bytes2Bitmap(byte[] bytes) {
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    }


    /**
     * Drawable → Bitmap
     */
    public static Bitmap drawable2Bitmap(Drawable drawable) {
        if (drawable == null) {
            return null;
        }
        // 取 drawable 的長寬
        int w = drawable.getIntrinsicWidth();
        int h = drawable.getIntrinsicHeight();
        // 取 drawable 的顏色格式
        Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
        // 建立對應 bitmap
        Bitmap bitmap = Bitmap.createBitmap(w, h, config);
        // 建立對應 bitmap 的畫布
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, w, h);
        // 把 drawable 內容畫到畫布中
        drawable.draw(canvas);
        return bitmap;
    }

    /*
         * Bitmap → Drawable
         */
    public static Drawable bitmap2Drawable(Bitmap bm) {
        if (bm == null) {
            return null;
        }
        BitmapDrawable bd = new BitmapDrawable(bm);
        bd.setTargetDensity(bm.getDensity());
        return new BitmapDrawable(bm);
    }

}
DiskLruUtils.java

總結:

 以上就是基於Lru圖片緩存簡單實現

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM