为Volley编写一个完整的图片二级缓存扩展

package xxx.xxx.xxx.xxx;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.util.LruCache;
import android.widget.ImageView;

import com.android.volley.toolbox.ImageLoader;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class BitmapCache implements ImageLoader.ImageCache {

    private static final int DISK_CACHE_SIZE = 1024 *1024 * 50;
    private static final int DISK_CACHE_INDEX = 0;

    private LruCache<String, Bitmap> lruCache;
    private DiskLruCache diskLruCache;

    public BitmapCache(Context context) {
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;
        lruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
        File diskCacheDir = getDiskCacheDir(context, "bitmap");
        if (!diskCacheDir.exists()) {
            diskCacheDir.mkdirs();
        }
        if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
            try {
                diskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public Bitmap getBitmap(String key) {
        Bitmap bitmap = lruCache.get(key);
        if (bitmap == null) {
            try {
                DiskLruCache.Snapshot snapshot = diskLruCache.get(hashKeyForDisk(key));
                if (snapshot != null) {
                    FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
                    FileDescriptor fileDescriptor = fileInputStream.getFD();
                    bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
                    if (bitmap != null) {
                        lruCache.put(key, bitmap);
                    }
                    return bitmap;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return bitmap;
    }

    @Override
    public void putBitmap(String key, Bitmap bitmap) {
        lruCache.put(key, bitmap);
        try {
            DiskLruCache.Editor editor = diskLruCache.edit(hashKeyForDisk(key));
            if (editor != null) {
                OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
                if (bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)) {
                    editor.commit();
                } else {
                    editor.abort();
                }
                diskLruCache.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private File getDiskCacheDir(Context context, String uniqueName) {
        final String cachePath;
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    private long getUsableSpace(File path) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            return path.getUsableSpace();
        }
        final StatFs stats = new StatFs(path.getPath());
        return stats.getBlockSizeLong() * stats.getAvailableBlocksLong();
    }

    private 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;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xFF & b);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

    public static String getCacheKey(String url, ImageView imageView) {
        return "#W" + imageView.getWidth()
                + "#H" + imageView.getHeight()
                + "#S" + imageView.getScaleType().ordinal()
                + url;
    }

    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth) {
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }
        return inSampleSize;
    }

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

}

至于我们为什么需要图片缓存,以及什么是内存缓存,什么是磁盘缓存,什么是Volley这些东西我就不想写了,我附上几篇郭神的博客,可以用作参考。
 


Android Volley完全解析(二),使用Volley加载网络图片

 Android高效加载大图、多图解决方案,有效避免程序OOM

 Android DiskLruCache完全解析,硬盘缓存的最佳方案


除了这些博客外,《Android开发艺术探索》一书第十二章对图片缓存这一块也讲的比较详细。

还有题外话就是以上的博客和书中所给的DiskLruCache源码的下载地址,下载下来的源码是不能直接编译成功的,还需要使用者自己做一些修改,比较麻烦。但是Android Developer官网给出的源码是直接可用的,我这里直接给出地址(需要科学上网,大家都懂的):

https://developer.android.com/samples/DisplayingBitmaps/src/com.example.android.displayingbitmaps/util/DiskLruCache.html


然后简单说一下我的代码,如果使用的话,直接复制粘贴就行了,不过最上面类的包名自己要写下,再一个就是如果DiskLruCache和本类如果不在一个包,还需要import一下。

本类实现了Volley的ImageCache接口,绝大部分实现细节都来自以上教程的整合。

重写接口的getBitmap()方法,大致逻辑为,首先从内存缓存中取出bitmap,如果没有,即当返回null的时候,从磁盘缓存读取,如果从硬盘成功读取,根据LRU算法的近期最少使用的原则,我们应该吧从磁盘读取的bitmap重新加入内存缓存。

重写接口的putBitmap()方法,大致逻辑为,拿到从网络加载来的bitmap以后,先写入内存缓存,再写入磁盘缓存。与以上教程不同的地方为,教程中是把网络请求来的数据流直接写入输出流,但是由于我们这个是Volley的扩展,所以我们要考虑使用Volley的情况,NetworkImageView会自动压缩bitmap,所以我们在使用的时候不必考虑压缩的问题,其次,由于Volley已经做过处理,所以我们直接会从此回调方法中得到bitmap,因此就不是直接从网络数据流写入输出流了,而是把bitmap写入输出流,这里使用了bitmap的compress()方法。

关于缓存使用的键值问题:Volley的键值不仅包含url,还包含宽,高,ScaleType三个信息。原本的键值在内存缓存中使用时没有任何问题的,但是使用在磁盘缓存中会出现问题,因为URL中包含的 “ / ”这个斜杠符号会对存储路径造成影响,导致目录出现问题,所以即使Volley有自己的键值生成的方法我们在使用磁盘缓存时仍需把原键MD5化。关于Volley的键是怎么生成的,我在代码中的最下面三个静态方法中的第一个已经给出。后面两个静态方法是从Resource中使用图片的时候进行压缩的两个工具方法,上面给出的博客中有介绍。

此外,bytesToHexString()方法中的for循环改为了更有逼格的foreach循环(好吧,这没什么值得说的)。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值