Lrucache类 内存缓存
LruCache<Key,Values>以键-值对的形式存储(内部定义了一个LinkedHashMap<Key,Values>)数据,通过new LruCache(int size)实例化,参数使指定分配给LruCache的缓存大小。
LruCache缓存大小设置
对于分配给LruCache的缓存大小,可以直接指定固定的数值,但是更好的做法应该是通过获取最大内存(int)Runtime.getRuntime.maxMemory,然后通过返回的最大内存/int n的大小动态分配给LruCache。
LruCache的存储和读取
LruCache是以为键值对形式存储数据,所以它的读写方法都和HashMap<Key,Values>一样,都可以通过key操作。
存储
LruCache.put(Key,Values)
读取
LruCache.get(Key)
通过LruCache缓存从网络读取的图片资源
在开发中,经常会遇到请求网络,从网络读取图片资源的时候,如果每次都去请求读取,很浪费资源(都会开辟子线程进行耗时操作),这样也会影响用户的体验。那么我们应该想到的是,启动app后,只从网络中读取一次资源,之后如果还需要同样的操作,就直接通过缓存读取,这样就可以通过LruCache类来进行操作。
构造一个工具类,用来存储图片到缓存和从缓存中读取图片从网络读取图片,存储到缓存用昨天学过的AsyncTask异步任务类来处理逻辑public class CustomLruCache { private LruCache<String, Bitmap> stringBitmapLruCache; int maxMemory = (int) Runtime.getRuntime().maxMemory();//获取最大内存 int cacheSize = maxMemory / 16;//大小为最大内存的1/16 private static CustomLruCache customLruCache; /** * 私有化构造方法 */ private CustomLruCache() { stringBitmapLruCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } }; } /** * 单例模式获取实例,保证只有一个CustomLruCache对象,同时保证只有一个CustomLruCache.stringBitmapLruCache * * @return */ public static CustomLruCache getInstance() { if (customLruCache == null) { customLruCache = new CustomLruCache(); } return customLruCache; } public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemoryCache(key) != bitmap)//如果缓存中不存在bitmap,就存入缓存 stringBitmapLruCache.put(key, bitmap); } public Bitmap getBitmapFromMemoryCache(String key) { return stringBitmapLruCache.get(key); } }
AsyncTask<String, Void, Bitmap> stringVoidBitmapAsyncTask = new AsyncTask<String, Void, Bitmap>() { @Override protected Bitmap doInBackground(String... params) { Bitmap bitmap = null; try { CustomLruCache customLruCache = CustomLruCache.getInstance(); bitmap = customLruCache.getBitmapFromMemoryCache(params[0]); //先从缓存中读取图片,如果缓存中不存在,再请求网络,从网络读取图片添加至LruCache中 //启动app后第一次bitmap为null,会先从网络中读取添加至LruCache,如果app没销毁,再执行读取图片操作时 //就会优先从缓存中读取 if (bitmap == null) { //从网络中读取图片数据 URL url = new URL(params[0]); bitmap = BitmapFactory.decodeStream(url.openStream()); //添加图片数据至LruCache customLruCache.addBitmapToMemoryCache(params[0], bitmap); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); imageView.setImageBitmap(bitmap); } }; stringVoidBitmapAsyncTask.execute(imageURL);
文件缓存-第三方类DiskLruCache
利用DiskLruCache从网络上获取到之后都会存入到本地缓存中,因此即使手机在没有网络的情况下依然能够加载显示图片数据。DiskLruCache存储的位置没有限制,但是一般选择存储在context.ExternolStorageCacheDir(),即这个手机的外部存储这个app的私有区域,即/sdcard/Android/data/应用包名/cache,因为是存储在外部存储私有区域,当app被卸载时,这部分的内容会被一起清除。
1、使用DiskLruCache
实例化DiskLruCache是通过 DiskLruCache.open(File directory, int appVersion, int valueCount, long maxSize),四个参数分别:为directory缓存的路径;appVersion 应用版本;alueCount 指定同一个key可以对应多少个缓存文件,一般指定为1;maxSize 指定可以缓存多少字节的数据。
(1)directory 缓存路径
public File getDiskCacheDir(Context context, String uniqueName) { String cachePath; if (isExternalStorageWritable()) { cachePath = context.getExternalCacheDir().getPath();//如果挂载了sdcard,获取外部存储私有区域路径 } else { cachePath = context.getCacheDir().getPath();//如果没有挂载sdcard,则获取内部存储缓存区域 } return new File(cachePath + File.separator + uniqueName); }
其中isExternalStorageWritable()是检测手机是否挂在sdcard。
private boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); if (state.equals(Environment.MEDIA_MOUNTED)) { return true;//挂载了sdcard,返回真 } else { return false;//否则返回假 } }
因为要对外部存储区域进行读写操作,所以要在androidManifest中添加相应的权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
(2)appVersion 应用版本
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; }
当版本号变更时,缓存路径下所有数据会被清除,因为DiskLruCache认为当版本更新时,所有数据应从网络重新获取(3)alueCount Key对应的缓存文件个数
DiskLruCache从缓存读取文件和写入文件到缓存时是通过 key来识别的,所以一般是指定alueCountKey为1。
(4)maxSize 缓存存储内容的大小
可以自己指定
通过上面的方法可以,这下可以完整的实例化一个DiskLruCache
private DiskLruCacheHelper(Context context) { try { File cacheDir = getDiskCacheDir(context, "bitmap"); //如果文件不存在,则创建 if (!cacheDir.exists()) { cacheDir.mkdirs(); } mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024); } catch (IOException e) { e.printStackTrace(); } }
2、通过DiskLruCache存储和读取图片
为了方便操作,我们可以封装一个类来进行操作
下面是从网络获取一张图片显示到ImageView上,第一次进入app时从网络获取,并存储至外部存储私有区域,以后(即使没有网络的情况)进入若相同的缓存文件存在,则直接从缓存读取。public class DiskLruCacheHelper { DiskLruCache mDiskLruCache = null; static DiskLruCacheHelper diskLruCacheHelper; private DiskLruCacheHelper(Context context) { try { File cacheDir = getDiskCacheDir(context, "bitmap"); //如果文件不存在,则创建 if (!cacheDir.exists()) { cacheDir.mkdirs(); } mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024); } catch (IOException e) { e.printStackTrace(); } } public static DiskLruCacheHelper getInstance(Context context) { if (diskLruCacheHelper == null) diskLruCacheHelper = new DiskLruCacheHelper(context); return diskLruCacheHelper; } public File getDiskCacheDir(Context context, String uniqueName) { String cachePath; if (isExternalStorageWritable()) { cachePath = context.getExternalCacheDir().getPath();//如果挂载了sdcard,获取外部存储私有区域路径 } else { cachePath = context.getCacheDir().getPath();//如果没有挂载sdcard,则获取内部存储缓存区域 } return new File(cachePath + File.separator + uniqueName); } /** * 检查外部存储是否可用 * * @return */ private boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); if (state.equals(Environment.MEDIA_MOUNTED)) { return true;//挂载了sdcard,返回真 } else { return false;//否则返回假 } } /** * 获取应用版本号 * 当版本号改变,缓存路径下存储的所有数据都会被清除掉,因为DiskLruCache认为 * 当应用程序有版本更新的时候,所有的数据都应该从网上重新获取。 * * @param context * @return */ 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; } /** * 写入图片数据到文件缓存 * * @param imageUrl * @param bitmap */ public void writeToCache(String imageUrl, Bitmap bitmap) { try { String key = hashKeyForDisk(imageUrl); DiskLruCache.Editor editor = mDiskLruCache.edit(key); if (editor != null) { OutputStream outputStream = editor.newOutputStream(0); if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)) { editor.commit(); } else { editor.abort(); } } mDiskLruCache.flush(); } catch (IOException e) { e.printStackTrace(); } } /** * 从缓存读取数据 * * @param imageUrl * @return */ public Bitmap readFromCache(String imageUrl) { Bitmap bitmap = null; try { String key = hashKeyForDisk(imageUrl); DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); if (snapShot != null) {//如果文件存在,读取数据转换为Bitmap对象 InputStream is = snapShot.getInputStream(0); bitmap = BitmapFactory.decodeStream(is); } } catch (IOException e) { e.printStackTrace(); } return bitmap; } /** * 将文件名转换成"MD5"编码 * * @param key * @return */ 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(); } }
注:涉及到网络操作,需要添加网络操作的相关权限
<uses-permission android:name="android.permission.INTERNET" />
public class MainActivity extends AppCompatActivity { ImageView mImageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mImageView = (ImageView) findViewById(R.id.get_image_from_network_img); new AsyncTask<String, Void, Bitmap>() { @Override protected Bitmap doInBackground(String... strings) { Bitmap bitmap = DiskLruCacheHelper.getInstance(MainActivity.this).readFromCache(strings[0]); if (bitmap == null) { try { URL url = new URL(strings[0]); bitmap = BitmapFactory.decodeStream(url.openStream()); DiskLruCacheHelper.getInstance(MainActivity.this).writeToCache(strings[0], bitmap); return bitmap; } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); mImageView.setImageBitmap(bitmap); } }.execute("http://file3.u148.net/2011/4/images/1302139153715.jpg"); } }
运行过后,我们可以到相应的缓存区域查看是否有文件生成
多级缓存处理
上面提到了内存缓存和文件缓存,可以将这两者一起使用,形成二级缓存,第一层是内存缓存,如果内存缓存中没有,则从文件缓存中读取,之后如果本地也没有相关缓存文件,再从网络获取。