安卓缓存之DiskLruCache及设计(异步+缓存)图片加载器DiskCacheImageLoader
来源:互联网 发布:淘宝茶叶排名 编辑:程序博客网 时间:2024/05/22 16:46
DiskLruCache
DiskLruCache是一套硬盘缓存的解决方案,算法同LruCache基于LRU算法。
DiskLruCache不是由Google官方编写的,这个类没有被包含在Android API当中。这个类需要从网上下载
可以到作者的Github这里获取
一、DiskLruCache的基本使用
1. 初始化DiskLruCache
调用它的open()静态方法创建一个DiskLruCache的实例
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
参数说明:
File directory:指定的是数据的缓存地址,一般为应用的内部/外部缓存文件夹,这样当应用卸载时,里面的数据也会被清除。
int appVersion:指定当前应用程序的版本号。
int valueCount :指定同一个key可以对应多少个缓存文件,一般都是传1,这样key和value一一对应,方便查找。
long maxsize:指定最多可以缓存数据的总字节数。
获取DiskLruCache的缓存目录directory
当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取手机内部缓存路径,否则就调用getCacheDir()方法来获取手机外部缓存路径。前者获取到的就是 /sdcard/Android/data//cache 这个路径,后者获取到的是 /data/data//cache 这个路径。
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(); } cacheDir = new File(cachePath + File.separator + uniqueName); // 判断缓存文件夹是否存在,不存在则创建 if (!cacheDir.exists()) { cacheDir.mkdirs(); } return cacheDir;}
获取应用版本号appVersion
每当版本号改变,缓存路径下存储的所有数据都会被清除掉。
public int getAppVersion(Context context) { try { PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); return info.versionCode; } catch (NameNotFoundException e) { e.printStackTrace(); } return 1; }
创建DiskLruCache对象方式如下
private DiskLruCache mDiskCache;//指定磁盘缓存大小private long DISK_CACHE_SIZE = 1024 * 1024 * 10;//10MB//得到缓存牡蛎File cacheDir = getDiskCacheDir(mContext, "Bitmap");mDiskCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1,DISK_CACHE_SIZE);
2. 将文件写入DiskLruCache缓存
写入操作是借助DiskLruCache.Editor这个类完成,需要调用DiskLruCache的edit()方法来获取实例。
public Editor edit(String key) throws IOException
edit()方法接收一个参数key,该key将会成为缓存文件的文件名,并且需要和文件的URL是一 一对应。
直接使用URL来作为key不太合适,因为文件的URL中可能包含一些特殊字符,这些字符有可能在命名文件时是不合法的。
最简单的做法就是将图片的URL进行MD5编码,编码后的字符串肯定是唯一的,并且只会包含0-F这样的字符,完全符合文件的命名规则。
将文件的URL字符串进行MD5编码:
public 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 (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();}
获取DiskLruCache.Editor的实例
String imageUrl = "http://.....jpg";// 转化为对应的keyString key = hashKeyForDisk(imageUrl);DiskLruCache.Editor editor = mDiskLruCache.edit(key);
通过editor获取输出流并写入数据
if (editor != null) { OutputStream outputStream = editor.newOutputStream(0); if (downloadUrlToStream(url, outputStream)) { editor.commit(); // 提交编辑 } else { editor.abort(); // 放弃编辑 } mDiskCache.flush(); // 刷新缓存}private boolean downloadUrlToStream(String urlString, OutputStream outputStream) { HttpURLConnection urlConnection = null; BufferedOutputStream out = null; BufferedInputStream in = null; try { final URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024); out = new BufferedOutputStream(outputStream, 8 * 1024); int b; while ((b = in.read()) != -1) { out.write(b); } return true; } catch (final IOException e) { e.printStackTrace(); } finally { if (urlConnection != null) { urlConnection.disconnect(); } try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (final IOException e) { e.printStackTrace(); } } return false;}
注:newOutputStream()方法接收一个index参数,由于前面在设置valueCount的时候指定的是1,这里index传0就可以了。
在写入操作执行完之后,我们还需要调用一下commit()方法进行提交才能使写入生效,调用abort()方法的话则表示放弃此次写入。
3. 获取缓存文件:
调用DiskLruCache的get方法来得到Snapshot对象
get()方法要求传入一个key来获取到相应的缓存数据,而这个key就是将文件URL进行MD5编码后的值
public synchronized Snapshot get(String key) throws IOExceptionString imageUrl = "http://.....jpg";// 转化为keyString key = hashKeyForDisk(imageUrl);DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
通过Snapshot得到文件输入流
InputStream is = snapShot.getInputStream(0);
getInputStream()方法也需要传一个index参数,这里传入0。这是因为我们在open方法中参数设置为一个key对应一个文件
通过文件输入流得到文件对象(以获取bitmap为例)
Bitmap bitmap = BitmapFactory.decodeStream(is);
4. 移除某个key的缓存
借助DiskLruCache的remove()方法移除缓存
public synchronized boolean remove(String key) throws IOException
这个方法不经常被调用。因为DiskLruCache会根据我们在调用open()方法时设定的缓存最大值来自动删除多余的缓存。
5. 获取缓存的总字节数和删除全部缓存
借助DiskLruCache的size()方法获取缓存的总字节数
public synchronized long size()
这个方法会返回当前缓存路径下所有缓存数据的总字节数,以byte为单位。如果应用程序中需要在界面上显示当前缓存数据的总大小,就可以通过调用这个方法计算出。
public void delete() throws IOException
这个方法用于将所有的缓存数据全部删除,可以实现手动清理缓存功能。
6. 刷新缓存状态
public synchronized void flush() throws IOException { checkNotClosed(); trimToSize(); journalWriter.flush(); }
这个方法用于将内存中的操作记录同步到日志文件(journal文件)当中。这个方法非常重要,因为DiskLruCache能够正常工作的前提就是要依赖于journal文件中的内容。可在Activity的onPause()方法中去调用一次flush()方法。
二、使用DiskLruCache封装图片加载器
根据DiskLruCache,我们可以将它封装在DiskCacheImageLoader来对图片进行缓存。设计模式为使用单例模式来设计:
在Logcat中打印异步任务个数:
package com.cxmscb.cxm.cacheproject;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.os.Build;import android.os.Environment;import android.os.Looper;import android.os.StatFs;import android.provider.ContactsContract;import android.util.Log;import android.widget.ImageView;import android.widget.ListView;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileDescriptor;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.HttpURLConnection;import java.net.URL;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.util.HashSet;import java.util.Set;import libcore.io.DiskLruCache;/** * 利用DiskLruCache来缓存图片 */public class DiskCacheImageLoader { private Context mContext; private Set<DiskCacheAsyncTask> mTaskSet; //DiskLruCache private DiskLruCache mDiskCache; private static DiskCacheImageLoader mDiskCacheImageLoader; public static DiskCacheImageLoader getInstance(Context context){ if(mDiskCacheImageLoader==null){ synchronized (DiskCacheImageLoader.class){ if(mDiskCacheImageLoader==null){ mDiskCacheImageLoader = new DiskCacheImageLoader(context); } } } return mDiskCacheImageLoader; } private DiskCacheImageLoader(Context context) { mTaskSet = new HashSet<>(); mContext = context.getApplicationContext(); //得到缓存文件 File diskCacheDir = getDiskCacheDir(mContext, "Bitmap"); //如果文件不存在 直接创建 if (!diskCacheDir.exists()) { diskCacheDir.mkdirs(); } try { mDiskCache = DiskLruCache.open(diskCacheDir, 1, 1, 1024*1024*20); } catch (IOException e) { e.printStackTrace(); } } /** * 将一个URL转换成bitmap对象 * */ public Bitmap getBitmapFromURL(String urlStr) { Bitmap bitmap; InputStream is = null; try { URL url = new URL(urlStr); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); is = new BufferedInputStream(connection.getInputStream(), 1024*8); bitmap = BitmapFactory.decodeStream(is); connection.disconnect(); return bitmap; } catch (IOException e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); } } catch (IOException e) { e.printStackTrace(); } } return null; } /** * 将URL中的图片保存到输出流中 * */ private boolean downloadUrlToStream(String urlString, OutputStream outputStream) { HttpURLConnection urlConnection = null; BufferedOutputStream out = null; BufferedInputStream in = null; try { final URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); in = new BufferedInputStream(urlConnection.getInputStream(), 1024*8); out = new BufferedOutputStream(outputStream, 1024*8); int b; while ((b = in.read()) != -1) { out.write(b); } return true; } catch (final IOException e) { e.printStackTrace(); } finally { if (urlConnection != null) { urlConnection.disconnect(); } try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (final IOException e) { e.printStackTrace(); } } return false; } /** * 普通地加载url * */ public void loadImage(ImageView imageView,String url){ //从缓存中取出图片 Bitmap bitmap = null; try { bitmap = getBitmapFromDiskCache(url); } catch (IOException e) { e.printStackTrace(); } //如果缓存中没有,则需要从网络中下载 if (bitmap == null) { DiskCacheAsyncTask task = new DiskCacheAsyncTask(imageView); task.execute(url); mTaskSet.add(task); } else { //如果缓存中有 直接设置 imageView.setImageBitmap(bitmap); } } /** * 为listview加载从start到end的所有的Image * */ public void loadTagedImagesInListView(int start, int end,String[] urls,ListView mListView) { for (int i = start; i < end; i++) { String url = urls[i]; ImageView imageView = (ImageView) mListView.findViewWithTag(url); loadImage(imageView,url); } Log.i("num of task"," "+mTaskSet.size()); } /** * 创建缓存文件 * */ public File getDiskCacheDir(Context context, String filePath) { boolean externalStorageAvailable = Environment .getExternalStorageState().equals(Environment.MEDIA_MOUNTED); final String cachePath; if (externalStorageAvailable) { cachePath = context.getExternalCacheDir().getPath(); } else { cachePath = context.getCacheDir().getPath(); } return new File(cachePath + File.separator + filePath); } /** * 将URL转换成key * */ private String hashKeyFormUrl(String url) { String cacheKey; try { final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(url.getBytes()); cacheKey = bytesToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) { cacheKey = String.valueOf(url.hashCode()); } return cacheKey; } /** * 将Url的字节数组转换成哈希字符串 * */ private 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写入缓存 * */ private Bitmap addBitmapToDiskCache(String url) throws IOException { if (mDiskCache == null) { return null; } //设置key,并根据URL保存输出流的返回值决定是否提交至缓存 String key = hashKeyFormUrl(url); //得到Editor对象 DiskLruCache.Editor editor = mDiskCache.edit(key); if (editor != null) { OutputStream outputStream = editor.newOutputStream(0); if (downloadUrlToStream(url, outputStream)) { //提交写入操作 editor.commit(); } else { //撤销写入操作 editor.abort(); } mDiskCache.flush(); } return getBitmapFromDiskCache(url); } /** * 从缓存中取出Bitmap * */ private Bitmap getBitmapFromDiskCache(String url) throws IOException { //如果缓存中为空 直接返回为空 if (mDiskCache == null) { return null; } //通过key值在缓存中找到对应的Bitmap Bitmap bitmap = null; String key = hashKeyFormUrl(url); //通过key得到Snapshot对象 DiskLruCache.Snapshot snapShot = mDiskCache.get(key); if (snapShot != null) { //得到文件输入流 InputStream ins = snapShot.getInputStream(0); bitmap = BitmapFactory.decodeStream(ins); } return bitmap; } /** * 异步任务类 */ private class DiskCacheAsyncTask extends AsyncTask<String, Void, Bitmap> { private ImageView imageView; public DiskCacheAsyncTask(ImageView imageView){ this.imageView = imageView; } @Override protected Bitmap doInBackground(String... params) { Bitmap bitmap = getBitmapFromURL(params[0]); //保存到缓存中 if (bitmap != null) { try { //写入缓存 addBitmapToDiskCache(params[0]); } catch (IOException e) { e.printStackTrace(); } } return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); if (imageView != null && bitmap != null) { imageView.setImageBitmap(bitmap); } mTaskSet.remove(this); Log.i("num of task"," "+mTaskSet.size()); } } /** * 停止所有当前正在运行的任务 */ public void cancelAllTask() { if (mTaskSet != null) { for (DiskCacheAsyncTask task : mTaskSet) { task.cancel(false); } mTaskSet.clear(); Log.i("num of task"," "+mTaskSet.size()); } }}
三、 使用LruCacheImageLoader来加载网络图片:
一、项目效果图:
开始ListView从网络上加载图片,关闭网络退出应用后,再打开应用,从本地DiskLruCache中加载图片
二、对ListView加载图片的优化:
listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView absListView, int scrollState) { if(scrollState==SCROLL_STATE_IDLE){ mDiskImageLoader.loadTagedImagesInListView(mStart,mEnd,urls,listView); }else { mDiskImageLoader.cancelAllTask(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mStart = firstVisibleItem; mEnd = firstVisbleItem + visibleItemCount; if(mFirstIn && visibleItemCount > 0){ mDiskImageLoader.loadTagedImagesInListView(mStart,mEnd,urls,listView); mFirstIn = false; } }});
完整项目地址:Github地址
参考
Android DiskLruCache完全解析,硬盘缓存的最佳方案
- 安卓缓存之DiskLruCache及设计(异步+缓存)图片加载器DiskCacheImageLoader
- 安卓缓存之LruCache及设计(异步+缓存)图片加载器LruCacheImageLoader
- 【安卓中的缓存策略系列】安卓缓存策略之磁盘缓存DiskLruCache
- 图片的异步加载及缓存
- SDWebImage异步加载图片及缓存
- android 之图片异步加载,带缓存。
- 安卓硬盘缓存技术DiskLruCache详解
- 图片缓存-LruCache、DiskLruCache
- 安卓缓存LruCache、DiskLruCache、SharedPreference、SqlLite-2DiskLruCache
- ListView异步加载图片(双缓存)
- 安卓图片的缓存与加载
- android listview分页异步加载图片及图片缓存
- android listview分页异步加载图片及图片缓存
- android listview分页异步加载图片及图片缓存
- iOS异步加载缓存图片
- (BUG已修改,最优化)安卓ListView异步加载网络图片与缓存软引用图片,线程池,只加载当前屏之说明
- 安卓开发——关于图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)
- 安卓开发笔记——关于图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)
- 暑期工作日志-Day9
- 【JavaScript】JavaScript中如何实现面向对象编程 之 类的实现
- JAVASE总结--折半查找
- JAVASE总结--Object
- 全栈开发--基础认识
- 安卓缓存之DiskLruCache及设计(异步+缓存)图片加载器DiskCacheImageLoader
- JAVASE总结--Proterties
- 260.[LeetCode]Single Number III
- JAVASE总结--内部类
- 对象转换成JSON
- python pandas.DataFrame选取、修改数据最好用.loc,.iloc,.ix
- hdu 5795 最长公共子序列的应用-----公共子序列的个数
- swift流程控制
- JAVASE总结--线程