自己写Android图片缓存框架之二级disk缓存

来源:互联网 发布:手机淘宝买东西教程 编辑:程序博客网 时间:2024/06/05 19:35

       上一节中已经运用Lru算法实现了内存缓存,在从桌面回到前台时可以快速的从内存中进行加载图片,但是如果应用被系统回收或人为的主动清除这样还是会从网络加载,所以我们不仅需要缓存在内存中,还要在磁盘中进行缓存,这样如果内存没有就从磁盘中进行读取数据。

      这里我们使用google提供的DiskLruCache来实现disk缓存,由于源码过长就不贴了,所有的代码包括图片加载的demo已经上传到github上。我们将DiskLruCache直接拷贝到项目代码中,并将原来的包名libcore.io更改为我们的包名。

      DiskLruCache提供了一系列方法,先依次分析一下

    private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {        this.directory = directory;        this.appVersion = appVersion;        this.journalFile = new File(directory, JOURNAL_FILE);        this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);        this.valueCount = valueCount;        this.maxSize = maxSize;    }
可以看到将DiskLruCache设为了private,这样我们就不能直接new出对象了,而是直接提供了一个open方法来返回对象

    /**     * 根据地址打开缓存空间,如果不存在就创建一个     *     * @param directory 数据缓存地址     * @param appVersion 当前app版本     * @param valueCount 一个key值对应多少个缓存文件 一般传1     * @param maxSize 最多可以缓存多少字节的数据 一般为10M     * @throws java.io.IOException if reading or writing the cache directory fails     */    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)            throws IOException {        if (maxSize <= 0) {            throw new IllegalArgumentException("maxSize <= 0");        }        if (valueCount <= 0) {            throw new IllegalArgumentException("valueCount <= 0");        }        //调用构造函数得到一个cache对象        DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);        if (cache.journalFile.exists()) {            try {                cache.readJournal();                cache.processJournal();                cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),                        IO_BUFFER_SIZE);                return cache;            } catch (IOException journalIsCorrupt) {                cache.delete();            }        }        directory.mkdirs();        cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);        cache.rebuildJournal();        return cache;    }
DiskLruCache提供了一个flush方法,为了防止频繁的写数据,建议在Activity的onPause时调用一次flush()方法,除此之外我们需要对url进行MD5加密,防止url出现字符不合法的情况,MD5算法在大数据的hash碰撞上性能也比较良好,编码后每个字符都在0到F之间,符合要求。

/** * 使用MD5算法对传入的key进行加密,以免出现url不合法 * @param key * @return */public String hashKeyForDisk(String key){String cacheKey;try {MessageDigest digest = MessageDigest.getInstance("MD5");digest.update(key.getBytes());cacheKey = bytesToHexString(digest.digest());} catch (NoSuchAlgorithmException e) {cacheKey = String.valueOf(key.hashCode());e.printStackTrace();}return cacheKey;}private String bytesToHexString(byte[] bytes){StringBuilder builder = new StringBuilder();for (int i = 0; i < bytes.length; i++) {String hex = Integer.toHexString(0xFF & bytes[i]);if (hex.length() == 1) {builder.append("0");}builder.append(hex);}return builder.toString();}


磁盘的缓存路径一般是在内存卡中,但是现在的一些手机可能并没有内存卡,所以我们需要动态的加载存储路径,

/** * 根据传入的unique返回硬盘缓存地址 *  * @param context * @param uniqueName * @return */public File getDiskCacheDir(Context context, String uniqueName) {String cachePath;if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())|| !Environment.isExternalStorageRemovable()) {//当有SD卡时cachePath = context.getExternalCacheDir().getPath();} else {//当没有SD卡或SD卡被移除cachePath = context.getCacheDir().getPath();}return new File(cachePath + File.separator + uniqueName);}
另外有些图片可能会过大比如远超要显示的大小,这样不仅因为图片过大在下载时比较慢,而且容易造成OOM,所以我们需要对图片进行压缩,让其显示匹配手机显示的大小

/** * 计算目标图片缩放比例 * @param options * @param reqWidth * @param reqHeight * @return */public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){int height = options.outHeight;int width = options.outWidth;int inSampleSize = 1;if (height > reqHeight || width > reqWidth) {//计算高度和宽度对目标高宽的比例int heightRadio = Math.round(height/reqHeight);int widthRadio = Math.round(width/reqWidth);//选择高宽中较小的一个作为压缩比例,保证图片比目标尺寸大inSampleSize = heightRadio < widthRadio ? heightRadio : widthRadio;}return inSampleSize;}public static Bitmap decodeSampledBitmap(FileDescriptor descriptor,int reqWidth,int reqHeight){BitmapFactory.Options options = new BitmapFactory.Options();//加载时将injustdecodebounds设置为true,获取图片大小options.inJustDecodeBounds = true;BitmapFactory.decodeFileDescriptor(descriptor, null, options);//计算压缩比例options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false;return BitmapFactory.decodeFileDescriptor(descriptor, null, options);}
在将图片写入disk缓存中时,我们需要获取一个DiskLruCache.Editor的editor来将流写入缓存中

@Overrideprotected Bitmap doInBackground(String... params) {imageUrl = params[0];FileDescriptor descriptor = null;FileInputStream inputStream = null;Snapshot snapshot = null;try {//生成图片对应的keyString key = imageLoader.hashKeyForDisk(imageUrl);snapshot = imageLoader.diskCache.get(key);if (snapshot == null) {//在磁盘中没有找到缓存文件,去网络下载,并写入到缓存中DiskLruCache.Editor editor = imageLoader.diskCache.edit(key);if (editor != null) {OutputStream outputStream = editor.newOutputStream(0); //因为是1对1,直接设为0,就是取第一个if (downloadUrlToStream(imageUrl, outputStream)) {editor.commit();}else {editor.abort();  //取消本次写入操作}}snapshot = imageLoader.diskCache.get(key);}if (snapshot != null) {inputStream = (FileInputStream) snapshot.getInputStream(0);descriptor = inputStream.getFD();}//将缓存数据解析成bitmap对象Bitmap bitmap = null;if (descriptor != null) {bitmap = BitmapUtil.decodeSampledBitmap(descriptor, reqWidth, reqHeight);}if (bitmap != null) {//将bitmap加入到内存缓存中imageLoader.addBitmapToMemoryCache(params[0], bitmap);}return bitmap;} catch (IOException e) {e.printStackTrace();}// 通过url下载图片Bitmap bitmap = downloadBitmap(params[0]);if (bitmap != null) {// 将图片放入内存缓存中imageLoader.addBitmapToMemoryCache(params[0], bitmap);}return bitmap;}
/** * 根据url从网上获取流,并写入到output流中 * @param imageUrl * @param outputStream * @return */private boolean downloadUrlToStream(String imageUrl,OutputStream outputStream){HttpURLConnection conn = null;BufferedOutputStream out = null;BufferedInputStream  in = null;try {URL url = new URL(imageUrl);conn = (HttpURLConnection) url.openConnection();in = new BufferedInputStream(conn.getInputStream(), 8*1024);out = new BufferedOutputStream(outputStream, 8*1024);int b;while((b = in.read())!= -1){out.write(b);}return true;} catch (Exception e) {e.printStackTrace();}finally{if (conn != null) {conn.disconnect();}try {out.close();} catch (IOException e) {e.printStackTrace();}try {in.close();} catch (IOException e) {e.printStackTrace();}}return false;}
大概步骤就是这样,中间一些细微的非核心代码没有放上来,可以直接去github上进行下载,包含示例demo

地址https://github.com/sheepm/Cache




0 0