安卓图片三级缓存策略与实现

本文详细介绍了安卓应用中的三级缓存策略,包括内存缓存LruCache、文件缓存DiskLruCache和网络请求。通过这种方式,首次加载图片会从网络获取并存储到内存和文件中,后续加载优先从内存读取,内存中没有再查文件,以提高效率和节省流量。文章还提及Volley框架用于网络图片加载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

这里说的三级缓存,分别指的是:内存缓存、文件缓存和网络这三个层面。

一般来说,我们首次加载图片,内存和文件是没有缓存的,这样我们需要从网络加载,加载完成后,我们会存到内存和文件中去;当再次加载图片的时候,我们会先查找内存有没有,如果有就直接显示内存中的图片,如果没有,我们会接着查找文件中是否有,如果文件中有,我们会显示文件中的图片,并且把它存到内存中去,这样下次我们在内存中就能找到它了。

我们之所以要做缓存,主要是为了提高效率,节省流量。但是为什么要做三级呢?为什么不只存在内存或者只存在文件中呢?这是因为内存的读取速度快,但是容易被回收,容量小,文件的读取速度次之,不过容量大,不到不得已不会被回收。

有了以上的介绍,我们已经知道了三级缓存的必要性和实施步骤,接下来,我们就要选择在每级缓存的缓存策略了。

内存缓存,最开始大家推崇的是用SoftRefrence(软引用),它只有在内存不够的情况下才会被GC回收。但是高版本的安卓系统更倾向于回收SoftRefrence,这使得SoftRefrence不那么好用了。不过,安卓在3.0之后提供了LRUCache,它采用了最近最少使用的淘汰策略。本篇文章我们的内存缓存使用的就是LruCache. 

文件缓存,我们使用的是DiskLruCache 点击这里下载

网络请求,这里我们使用Volley网络请求框架 点击这里下载。不懂的可以看这里 Android Volley入门到精通:定制自己的Request

点击这里下载本文的源代码

内存缓存LruCache

我们定义ImageCacheUtil类来进行图片的缓存,它实现了Volley的ImageLoader.ImageCache接口,改接口需要实现两个方法:
1.getBitmap : Volley请求的时候会先回调getBitmap看缓存是否有图片,没有的话才会去网络请求
2.putBitmap : Volley下载完图片的回调,实现该方法可以进行图片缓存

使用LruCache需要以下步骤
1.通过new LruCache得到LruCache的实例
        // 获取应用可占内存的1/8作为缓存
        int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);
        // 实例化LruCaceh对象
        mLruCache = new LruCache<String, Bitmap>(maxSize) {
                @Override
                protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight();
            }
        };
2.在getBitmap函数中通过mLruCache.get(url)得到内存的图片,没有时返回空。
3.在putBitmap函数中通过mLruCache.put(url,bitmap)把图片存入内存。
具体用法可以看下面我贴出的代码。

文件缓存DiskLruCache

使用DiskLruCache需要以下步骤:
1.通过DiskLruCache.open(...)得到DiskLruCache的实例
        //DiskLruCache实例,它的构造方法是私有的,所以我们需要通过它提供的open方法来生成。

        try {
            mDiskLruCache = DiskLruCache.open(getDiskCacheDir(MyApplication.getContext(),CACHE_FOLDER_NAME),
                    getAppVersion(MyApplication.getContext()) , 1, DISKMAXSIZE);
        } catch (IOException e) {
            e.printStackTrace();
        }
2.在getBitmap函数中如果mLruCache.get(url)返回空,通过mDiskLruCache.get(key)得到DiskLruCache.Snapshot,通过BitmapFratory(snapshot.getInputStream(0))得到图片,没有时返回空
            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();
            }
3.在putBitmap函数中如果mDiskLruCache.get(key)==null则把图片存入文件。
        //存入文件
        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();
        }

下面是这个ImageCacheUtil类的全部

public class ImageCacheUtil implements ImageLoader.ImageCache {
    //缓存类
    private static LruCache<String, Bitmap> 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<String, Bitmap>(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;
    }
}

Volley下载图片

不知道Volley可以看这篇博客:  Android Volley入门到精通:定制自己的Request
关于Volley进行图片下载的具体可以看这篇博客:  

Android Volley入门到精通:使用Volley加载网络图片

Volley需要我们声明一个RequestQueue来维持请求队列,我们定义RequestQueueManager来进行管理。

public class RequestQueueManager {
    public static RequestQueue mRequestQueue = Volley.newRequestQueue(MyApplication.getContext());
    public static void addRequest(Request<?> request, Object object){
        if (object != null){
            request.setTag(object);
        }
        mRequestQueue.add(request);
    }
    public static void cancelAll(Object tag) {
        mRequestQueue.cancelAll(tag);
    }
}
Volley给我们提供了ImageLoader类和ImageCache类用于图片下载。我们可以用ImageLoader的get(url,ImageLoader.ImageListeer,width,height)方法来下载图片
ImageLoader的使用步骤如下:
1.用new ImageLoader方法得到ImageLoader的一个实例。其中构造方法需要传入requestQueue和ImageCache(我们在介绍内存的时候的ImageCaheUtil类实现了ImageCache)
  private static ImageCacheUtil mImagetCache = new ImageCacheUtil();

  public static ImageLoader mImageLoader = new ImageLoader(RequestQueueManager.mRequestQueue,mImagetCache);
2.用get方法进行图片下载
    public static void loadImage(String url,ImageLoader.ImageListener imageListener){
        mImageLoader.get(url,imageListener,0,0);
    }

    public static void loadImage(String url,ImageLoader.ImageListener imageListener,int maxWidth,int maxHeight){
        mImageLoader.get(url,imageListener,maxWidth,maxHeight);
    }
3.在Activity中调用
        ImageCacheManager.loadImage("http://img0.bdstatic.com/img/image/shouye/xiaoxiao/%E5%AE%A0%E7%89%A983.jpg",
                new ImageLoader.ImageListener() {
                    @Override
                    public void onResponse(ImageLoader.ImageContainer imageContainer, boolean b) {
                        progressLy.setVisibility(View.GONE);
                        if(imageContainer.getBitmap() != null){
                            imageView.setImageBitmap(imageContainer.getBitmap());
                        }
                    }

                    @Override
                    public void onErrorResponse(VolleyError volleyError) {
                        progressLy.setVisibility(View.GONE);
                    }
                });
好了,以上就是安卓三级缓存的实现。 下载源码点击这里





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值