安卓二级缓存

二级缓存
前面我们有了一级缓存,为啥还要二级缓存呢?说白了,这就和电脑是一样的,我们电脑有内存和硬盘,内存读取速度快,所以CPU直接读取内存中的数据,但是,内存资源有限,所以我们可以把数据保存到硬盘上,这就是二级缓存,硬盘虽然读取速度慢,但是人家容量大。 Android的缓存技术也是使用了这样一个特性,总的来说,使用二级缓存的方案,就是先从一级缓存——内存中拿,没有的话,再去二级缓存——手机中拿,如果还没有,那就只能去下载了。 有了DiskLruCache,我们就可以很方便的将一部分内容缓存到手机存储中,做暂时的持久化保存,像我们经常用的一些新闻聚合类App、ZARKER等,基本都利用了DiskLruCache,浏览过的网页,即使在没有网络的情况下,也可以浏览。
DiskLruCache

配置

DiskLruCache,听名字就知道是LruCache的兄弟,只不过这个应该是Google的私生子,还没有像LruCache一样添加到API中,所以我们只能去官网上下载DiskLruCache的代码,其实也就一个类。下载地址:https://developer.android.com/samples/DisplayingBitmaps/src/com.example.android.displayingbitmaps/util/DiskLruCache.html#l22

在工程中使用DiskLruCache非常简单,只需要在项目中新建一个libcore.io的包,并将DiskLruCache.java文件copy过去即可。

初始化

在使用DiskLruCache之前,我们需要对缓存的目录进行下配置,DiskLruCache并不需要限定缓存保存的位置,但一般情况下,我们的缓存都保存在缓存目录下: /sdcard/Android/data/package name/cache,当然,如果没有sdcard,那么我们就使用内置存储的缓存区域:/data/data/package name/cache。 在设置好缓存目录后,我们就可以使用DiskLruCache.open方法来创建DiskLruCache:
01. File cacheDir = getFileCache(context, "disk_caches");
02. if (!cacheDir.exists()) {
03. cacheDir.mkdirs();
04. }
05. try {
06. mDiskCaches = DiskLruCache.open(cacheDir, 1110 1024 1024);
07. catch (IOException e) {
08. e.printStackTrace();
09. }
10.  
11. private File getFileCache(Context context, String cacheFileName) {
12. String cachePath;
13. if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
14. || !Environment.isExternalStorageRemovable()) {
15. cachePath = context.getExternalCacheDir().getPath();
16. else {
17. cachePath = context.getCacheDir().getPath();
18. }
19. return new File(cachePath + File.separator + cacheFileName);
20. }
DiskLruCache.open方法有这样几个参数: 缓存目录 程序版本号:版本更新后,缓存清0 valueCount 缓存大小:随意,但也不能太任性,按字节算
应该不用解释了,唯一值得说的是valueCount这个参数,它是说同一个key可以对应Value的个数,一般都是1,基本没用。最后我们来看看最后返回的:
1. return new File(cachePath + File.separator + cacheFileName)

这里通过cacheFileName在缓存目录下再创建一个目录是干嘛呢?这个目录是用来对不同的缓存对象进行区分的,例如images、text等等。我们可以通过size()方法来获取所有缓存数据的大小。也可以使用delete()方法来删除所有缓存。

写入缓存

权限:
1. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

都说了写缓存,那读写权限肯定是不能少了。
DiskLruCache写入缓存与使用SharedPreferences方法类似,需要使用Editor对象:
1. DiskLruCache.Editor editor = mDiskCaches.edit(key);

传入的key,就是我们需要下载的url地址,例如图片的地址,但是,url经常具有很多非法字符,这些会对我们的解析工作造成很多困难,而且,有时候我们的url地址也是需要保密的,所以我们经常通过MD5来进行url的加密,这样不仅可以加密,而且可以让所有的URL都变为规则的十六进制字符串。下面我们展示一个经典的写入缓存模板代码:
01. String key = toMD5String(url);
02.  
03. /////////////////////////////////////////////////////////////////////////////////
04. DiskLruCache.Editor editor = mDiskCaches.edit(key);
05. if (editor != null) {
06. OutputStream outputStream = editor.newOutputStream(0);
07. if (getBitmapUrlToStream(url, outputStream)) {
08. editor.commit();
09. else {
10. editor.abort();
11. }
12. }
13. mDiskCaches.flush();
14. /////////////////////////////////////////////////////////////////////////////////
15.  
16. public String toMD5String(String key) {
17. String cacheKey;
18. try {
19. final MessageDigest digest = MessageDigest.getInstance("MD5");
20. digest.update(key.getBytes());
21. cacheKey = bytesToHexString(digest.digest());
22. catch (NoSuchAlgorithmException e) {
23. cacheKey = String.valueOf(key.hashCode());
24. }
25. return cacheKey;
26. }
27.  
28. private String bytesToHexString(byte[] bytes) {
29. StringBuilder sb = new StringBuilder();
30. for (int i = 0; i < bytes.length; i++) {
31. String hex = Integer.toHexString(0xFF & bytes[i]);
32. if (hex.length() == 1) {
33. sb.append('0');
34. }
35. sb.append(hex);
36. }
37. return sb.toString();
38. }
39.  
40. private static boolean getBitmapUrlToStream(String urlString, OutputStream outputStream) {
41. HttpURLConnection urlConnection = null;
42. BufferedOutputStream out = null;
43. BufferedInputStream in = null;
44. try {
45. final URL url = new URL(urlString);
46. urlConnection = (HttpURLConnection) url.openConnection();
47. in = new BufferedInputStream(urlConnection.getInputStream(), 8 1024);
48. out = new BufferedOutputStream(outputStream, 8 1024);
49. int b;
50. while ((b = in.read()) != -1) {
51. out.write(b);
52. }
53. return true;
54. catch (final IOException e) {
55. e.printStackTrace();
56. finally {
57. if (urlConnection != null) {
58. urlConnection.disconnect();
59. }
60. try {
61. if (out != null) {
62. out.close();
63. }
64. if (in != null) {
65. in.close();
66. }
67. catch (final IOException e) {
68. e.printStackTrace();
69. }
70. }
71. return false;
72. }

这里唯一的需要注意的是,下载的方法与我们之前使用的方法有所不同,主要是为了通用性,DiskLruCache将对应URL的内容以流的形式进行存储,文件名就是MD5加密后的字符串。

读取缓存

读取缓存的方法大家应该也能想到了,自然是调用get方法:
1. DiskLruCache.Snapshot snapShot = mDiskCaches.get(key);

不过它返回的是DiskLruCache的Snapshot对象。当我们获取到了Snapshot对象,就可以从它里面获取输出流,从而取出缓存的数据:
1. DiskLruCache.Snapshot snapShot = mDiskCaches.get(key);
2. InputStream is = snapShot.getInputStream(0); 
3. Bitmap bitmap = BitmapFactory.decodeStream(is); 
4. mImageView.setImageBitmap(bitmap);

移除缓存

移除缓存,我们可以猜到,我们需要使用remove方法来实现:
1. mDiskCache.remove(key);
当然,DiskLruCache并不希望我们手动去移除缓存,因为人家用了Lru算法,跟我们在内存中使用的算法一样,该死的时候,它自己会死。

与生命周期绑定

DiskLruCache在使用时,经常与我们的Activity的生命周期进行绑定,例如在onPause()方法中调用flush()方法,将内容与journal日志文件同步,在onDestroy()方法中去调用close()方法结束DiskLruCache的open。
<think>我们正在讨论Android中的二级缓存机制。二级缓存通常包括内存缓存(一级缓存)和磁盘缓存(二级缓存)。根据引用[1],当需要获取数据(例如网络图片)时,首先从内存中查找,如果没有再从磁盘中查找,最后才从网络获取。同时,从网络获取的数据会同时缓存到内存和磁盘中。引用[3]提到,早期内存缓存使用软引用或弱引用,但由于Android2.3之后垃圾回收机制的变化,这些方式变得不太有效。从Android3.0开始,Bitmap数据被放在堆内存中,由GC管理,但容易导致OOM。因此,Android3.1引入了LruCache作为内存缓存,它使用强引用并基于最近最少使用原则来管理缓存。引用[2]展示了内存缓存的基本操作:添加和获取。通过一个类似Map的结构(如LruCache)来存储键值对。 引用[4]提到磁盘缓存通常存放在cache目录,文件名为MD5命名的PNG文件。读取时使用BitmapFactory.decodeFile,并同时存入内存缓存。 引用[5]则说明了如何检查SD卡状态和权限。现在,我将按照系统指令要求,用中文回答用户的问题,并生成相关问题。###Android二级缓存机制原理及实现方式Android中的二级缓存机制通常由内存缓存(一级缓存)和磁盘缓存(二级缓存)组成,用于提高数据(特别是图片)的加载速度并减少网络请求。其核心思想是优先从快速存储(内存)中获取数据,若未命中则从较慢的存储(磁盘)中获取,最后才从网络加载。 ####1.内存缓存(一级缓存) -**原理**:内存缓存使用应用程序的堆内存存储数据。由于内存访问速度快,适合存储近期使用的数据。但内存有限,需控制缓存大小(通常不超过系统内存的1/4,引用[1])以避免OOM(内存溢出)。- **实现方式**:-使用`LruCache`(LeastRecently UsedCache)管理缓存。它通过强引用保存对象,并按照最近最少使用原则淘汰数据。-示例代码(引用[2]):```java//初始化LruCache,指定最大缓存大小(如KB)privateLruCache<String, Bitmap>mMemoryCache =new LruCache<String,Bitmap>(maxMemory){@Overrideprotected intsizeOf(String key, Bitmap value) {returnvalue.getByteCount() /1024;//返回KB单位的大小}};//添加缓存private voidaddBitmapToMemoryCache(Stringkey,Bitmapbitmap){if (getBitmapFromMemCache(key) ==null){mMemoryCache.put(key,bitmap);}}//获取缓存privateBitmapgetBitmapFromMemCache(Stringkey){return mMemoryCache.get(key);}``` ####2.磁盘缓存(二级缓存)- **原理**:磁盘缓存将数据持久化到设备存储(如内部缓存目录或SD卡)。虽然速度慢于内存,但容量更大且不受应用生命周期影响。 -**实现方式**:-使用文件系统或数据库(如SQLite)存储数据。文件名通常使用URL的MD5值命名(引用[4])。-读取时先检查内存缓存,未命中则读取磁盘文件,并解码为Bitmap(引用[4]):```javaBitmapbitmap =BitmapFactory.decodeFile(file.getAbsolutePath());```-写入磁盘时需注意:-检查存储权限和可用空间(引用[5]:`Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)`)。-使用异步任务避免阻塞主线程。 ####3.二级缓存工作流程1.**数据请求**:根据键(如图片URL)查找内存缓存。-命中:直接返回数据。-未命中:进入步骤2。2.**查找磁盘缓存**:-命中:将数据加载到内存缓存,然后返回。-未命中:进入步骤3。3.**网络请求**:从网络获取数据,并同时:-存入内存缓存。-存入磁盘缓存(异步)。4. **返回数据**。####4.优化与注意事项- **内存控制**:`LruCache`的大小需动态计算(如可用内存的1/8)。 -**磁盘位置**:-内部缓存:`context.getCacheDir()`(应用卸载时自动清除)。-外部缓存:`context.getExternalCacheDir()`(需SD卡挂载,引用[5])。 -**线程安全**:使用线程池管理磁盘和网络操作。 -**图片压缩**:磁盘存储时可适当压缩以减少空间占用。 ###总结二级缓存通过分层存储(内存→磁盘→网络)平衡了速度与容量,是Android性能优化的常见手段。实际开发中可结合现有库(如Glide)简化实现,但理解其原理对定制需求至关重要。§§相关问题§§1.如何合理设置LruCache的最大缓存大小?2.磁盘缓存的文件命名为什么要用MD5?有什么替代方案?3.在二级缓存中,如何处理数据一致性问题(如网络数据更新)? 4.除了图片,二级缓存机制还适用于哪些类型的数据?</think>### Android二级缓存机制原理及实现方式 Android二级缓存机制通过**内存缓存(一级)**和**磁盘缓存(二级)**实现高效数据访问,核心原理是优先从高速存储读取数据,减少网络请求和磁盘IO。以下是详细解析: --- #### **一、二级缓存工作原理** 1. **访问优先级** - **内存缓存**:先检查内存中是否存在数据(键值对查询)[^1] - **磁盘缓存**:内存未命中时检查本地文件/SQLite - **网络请求**:前两级均未命中时从网络获取 $$ \text{数据获取顺序}:\text{内存} \rightarrow \text{磁盘} \rightarrow \text{网络} $$ 2. **数据写入规则** - 网络获取的数据**同时写入内存和磁盘**(双写策略)[^1] - 内存缓存使用**强引用**(Android 3.1+采用`LruCache`)[^3] - 磁盘缓存通常存储在`/cache`目录(PNG文件,MD5命名)[^4] 3. **内存管理约束** - 内存缓存大小需**不超过系统内存的1/4**,避免OOM[^1] - `LruCache`基于LRU(最近最少使用)算法自动淘汰旧数据 --- #### **二、关键实现方式** ##### **1. 内存缓存实现(LruCache)** ```java // 初始化LruCache(以Bitmap缓存为例) int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024 / 4); // 最大内存的1/4 LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(maxMemory) { @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount() / 1024; // 返回KB单位的大小 } }; // 添加缓存[^2] private void addToMemoryCache(String key, Bitmap bitmap) { if (memoryCache.get(key) == null) { memoryCache.put(key, bitmap); } } // 读取缓存[^2] private Bitmap getFromMemoryCache(String key) { return memoryCache.get(key); } ``` ##### **2. 磁盘缓存实现** ```java // 检查存储可用性[^5] boolean canCache = Environment.getExternalStorageState() .equals(Environment.MEDIA_MOUNTED); // 写入磁盘缓存(MD5命名文件) public void saveToDisk(String url, Bitmap bitmap) { String fileName = MD5(url) + ".png"; // 生成MD5文件名 File cacheFile = new File(context.getCacheDir(), fileName); try (FileOutputStream out = new FileOutputStream(cacheFile)) { bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); } } // 读取磁盘缓存[^4] public Bitmap loadFromDisk(String url) { String fileName = MD5(url) + ".png"; File cacheFile = new File(context.getCacheDir(), fileName); if (cacheFile.exists()) { return BitmapFactory.decodeFile(cacheFile.getAbsolutePath()); } return null; } ``` ##### **3. 完整二级缓存流程** ```java public Bitmap loadData(String url) { // 1. 检查内存缓存 Bitmap bitmap = getFromMemoryCache(url); if (bitmap != null) return bitmap; // 2. 检查磁盘缓存 bitmap = loadFromDisk(url); if (bitmap != null) { addToMemoryCache(url, bitmap); // 载入内存 return bitmap; } // 3. 网络请求 bitmap = downloadFromNetwork(url); if (bitmap != null) { addToMemoryCache(url, bitmap); // 写入内存 saveToDisk(url, bitmap); // 写入磁盘 } return bitmap; } ``` --- #### **三、技术演进与优化** - **历史问题**: - Android 2.3前使用`SoftReference`易被GC回收[^3] - Android 3.0+的`Bitmap`存放于堆内存,需主动管理内存[^3] - **现代方案**: - **内存缓存**:`LruCache` + `WeakReference`(非必须场景) - **磁盘缓存**:`DiskLruCache`(官方推荐库) - **扩展能力**:支持SD卡缓存(需权限检查)[^5] --- #### **四、注意事项** 1. **内存泄露**: - `Bitmap`缓存需及时回收,建议结合`onLowMemory()`释放资源 2. **缓存一致性**: - 网络数据更新时需同步清理旧缓存(如时间戳校验) 3. **磁盘空间**: - 定期清理过期文件(如按时间/LRU策略)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值