DiskLruCache、LruCache和Valley三级缓存精炼详解

一、前期基础知识储备

在前面的两篇文章《LruCache内存缓存图片技术精炼详解》、《DiskLruCache本地缓存图片技术精炼详解》笔者分别讲解了利用LruCache实现内存缓存和利用DiskLruCache本地缓存两种缓存技术,那么今天本节文章,就和大家一起实现一次三级缓存加载图片。

(1)三级缓存的定义

现在大部分的缓存框架,比如图片加载框架,网络请求框架等都使用三级缓存来提高效率,即内存-文件(SD卡或手机)-网络。对于图片加载来说,就是加载图片的时候首先从内存缓存中取,如果没有再从文件缓存中取,如果文件缓存没有取到,就从网络下载图片并且加入内存和文件缓存。

(2)三级缓存的优势

①网络缓存, 不优先加载, 速度慢,浪费流量

②本地缓存, 次优先加载, 速度快

③内存缓存, 优先加载, 速度最快

我们可以看到,对一张图片使用三种了三种加载方式,每种加载方式优势互补,既弥补了单一缓存的缺点,又融合了三种缓存方式的优势,最终可以很快的实现一次网络图片的加载:第一次从网络加载,保存到内存和本地;下次加载图片,先从内存中找,找到了直接加载,没有找到就再去本地中找,找到了直接加载,两个地方都没有找到,再从网络中下载,层层递进。

(3)三级缓存使用对象

①内存缓存使用LruCache;

②本地缓存使用DiskLruCache;

③网络加载图片使用Volley框架。

二、上代码,具体实现

(1)Volley-使用ImageLoader加载网络图片

①创建一个RequestQueue对象;

② 创建一个ImageLoader对象;

③ 获取一个ImageListener对象;

④ 调用ImageLoader的get()方法加载网络上的图片

我们在第二步中,复写一个新的ImageCache接口对象,在其中实现三级缓存的逻辑。

实现接口对象时,需要实现该接口中的两个方法,方法中定义好存取逻辑顺序:

  • getBitmap() -Volley请求的时候会先回调getBitmap看缓存是否有图片,没有的话才会去网络请求;
  • putBitmap() -Volley下载完图片的回调,实现该方法可以进行图片缓存。

有需要的读者,可参考笔者的《网络通讯库Volley精炼详解第(二)课:使用Volley加载网络图片》,里面有使用ImageLoader加载网络图片的详细介绍。

(2)LruCache-内存缓存

// 创建LruCache实例,获取应用可占内存的1/8作为缓存  
int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);  
// 实例化LruCaceh对象  
mLruCache = new LruCache(maxSize) {  
        @Override  
        protected int sizeOf(String key, Bitmap bitmap) {  
        return bitmap.getRowBytes() * bitmap.getHeight();  
    }  
}; 

(3)DiskLruCache—本地缓存

 1)创建DiskLruCache实例

//创建DiskLruCache实例 
try {  
    mDiskLruCache = DiskLruCache.open(getDiskCacheDir(MyApplication.getContext(),CACHE_FOLDER_NAME),  
            getAppVersion(MyApplication.getContext()) , 1, DISKMAXSIZE);  
} catch (IOException e) {  
    e.printStackTrace();  
}  
2)三级缓存读取图片逻辑代码
try {  
    if(mDiskLruCache.get(diskKey) != null){ //文件中有  
        //从文件中取  
        Log.d(TAG,"从文件中取");  
        DiskLruCache.Snapshot snapshot = mDiskLruCache.get(diskKey);  
        Bitmap bitmap = null;  
        if(snapshot != null){  
            bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));  
            //存入内存  
            mLruCache.put(s,bitmap);  
        }  
        return bitmap;  
    }  
} catch (IOException e) {  
    e.printStackTrace();  
} 

3)三级缓存存储图片逻辑代码

try {  
    if(mDiskLruCache.get(diskKey) == null){  
        Log.d(TAG,"存入文件");  
        DiskLruCache.Editor editor = mDiskLruCache.edit(diskKey);  
        if(editor != null){  
            OutputStream outputStream = editor.newOutputStream(0);  
            if(bitmap.compress(Bitmap.CompressFormat.JPEG,100,outputStream)){  
                editor.commit();  
            }else{  
                editor.abort();  
            }  
        }  
        mDiskLruCache.flush();  
    }  
} catch (IOException e) {  
    e.printStackTrace();  
} 
下面,是完整的三级缓存代码段—创建了一个 ImageCacheUtil类,用以实现ImageCache接口,获得对象之后,传入Volley的ImageLoade中,作为其第二个参数,从而完美实现三级缓存。
public class ImageCacheUtil implements ImageLoader.ImageCache {  
    //缓存类  
    private static LruCache mLruCache;  
    private static DiskLruCache mDiskLruCache;  
  
    //磁盘缓存大小  
    private static final int DISKMAXSIZE = 10 * 1024 * 1024;  
  
    //路径  
    private static String CACHE_FOLDER_NAME = "YR_ImageCache";  
  
    private String TAG = ImageCacheUtil.class.getSimpleName();  
  
    public ImageCacheUtil() {  
        // 获取应用可占内存的1/8作为缓存  
        int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);  
        // 实例化LruCaceh对象  
        mLruCache = new LruCache(maxSize) {  
                @Override  
                protected int sizeOf(String key, Bitmap bitmap) {  
                return bitmap.getRowBytes() * bitmap.getHeight();  
            }  
        };  
  
        //DiskLruCache实例,它的构造方法是私有的,所以我们需要通过它提供的open方法来生成。  
  
        try {  
            mDiskLruCache = DiskLruCache.open(getDiskCacheDir(MyApplication.getContext(),CACHE_FOLDER_NAME),  
                    getAppVersion(MyApplication.getContext()) , 1, DISKMAXSIZE);  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
  
    /** 
     * volley请求的时候会先回调getBitmap查看缓存中是否有图片,没有再去请求 
     * @param s 
     * @return 
     */  
    @Override  
    public Bitmap getBitmap(String s) {  
        if(mLruCache.get(s) != null){ //内存中有  
            //从内存获取  
            Log.d(TAG,"从内存获取");  
            return mLruCache.get(s);  
        }else {  
            String diskKey = MD5Utils.md5(s);  
            try {  
                if(mDiskLruCache.get(diskKey) != null){ //文件中有  
                    //从文件中取  
                    Log.d(TAG,"从文件中取");  
                    DiskLruCache.Snapshot snapshot = mDiskLruCache.get(diskKey);  
                    Bitmap bitmap = null;  
                    if(snapshot != null){  
                        bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));  
                        //存入内存  
                        mLruCache.put(s,bitmap);  
                    }  
                    return bitmap;  
                }  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
        Log.d(TAG,"从网络中取");  
        return null;  
    }  
  
    /** 
     * 当Volley下载完图片后会来回调putBitmap方法来将图片进行缓存 
     * @param s 
     * @param bitmap 
     */  
    @Override  
    public void putBitmap(String s, Bitmap bitmap) {  
        //存入内存  
        Log.d(TAG,"存入内存");  
        mLruCache.put(s,bitmap);  
        //存入文件  
        String diskKey = MD5Utils.md5(s);  
        try {  
            if(mDiskLruCache.get(diskKey) == null){  
                Log.d(TAG,"存入文件");  
                DiskLruCache.Editor editor = mDiskLruCache.edit(diskKey);  
                if(editor != null){  
                    OutputStream outputStream = editor.newOutputStream(0);  
                    if(bitmap.compress(Bitmap.CompressFormat.JPEG,100,outputStream)){  
                        editor.commit();  
                    }else{  
                        editor.abort();  
                    }  
                }  
                mDiskLruCache.flush();  
            }  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
  
    //该方法会判断当前sd卡是否存在,然后选择缓存地址  
    public File getDiskCacheDir(Context context, String uniqueName) {  
        String cachePath;  
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())  
                || !Environment.isExternalStorageRemovable()) {  
            cachePath = context.getExternalCacheDir().getPath();  
        } else {  
            cachePath = context.getCacheDir().getPath();  
        }  
        Log.d(TAG,cachePath + File.separator + uniqueName);  
        return new File(cachePath + File.separator + uniqueName);  
    }  
  
    //获得应用version号码  
    public int getAppVersion(Context context) {  
        try {  
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);  
            return info.versionCode;  
        } catch (PackageManager.NameNotFoundException e) {  
            e.printStackTrace();  
        }  
        return 1;  
    }  
}


总结:本节文章,主要在笔者前面DisLruCache、LruCache、Volley的基础上综合,实现了一个现在比较流行的运用于常见应用程序的三级缓存的技术,这也是笔者第一次将多个知识点进行综合。整段代码,初次看起来或许难以入手,但是实际上,每段代码的逻辑非常清晰,都起到了对应的效果,然后通过一个逻辑控制,将所有代码整合在一起。不了解相关内容的读者或许很难相信,这么一长段的代码,只是实现了一个ImageCache接口,然后获取其对象之后,只是传入ImageLoader中作为第二个参数。但是,我们在这个逻辑中,实现了本地缓存和内存缓存的读写操作逻辑,这是实实在在的知识点。建议初次接触三级缓存的读者,可以参考笔者的前面两篇关于DisLruCache本地缓存和LruCache内存缓存的文章,相信会帮助到你的。



你可能感兴趣的:(高级要求)