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图片缓存简单实现

 

posted on 2016-08-02 08:46  总李写代码  阅读(9319)  评论(0编辑  收藏  举报