1.简介
大家都知道,在我们Android 开发的过程中,对于图片的处理,是非常重要的,而对于我们如果每次都重网络去拉去图片,那样会造成,现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如淘宝,qq的照片墙)中比较多。
现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响。当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略。总之,图片缓存是很重要而且是必须的。
2.图片缓存的原理
实现图片缓存也不难,需要有相应的cache策略。这里我采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。
关于Java中对象的软引用(SoftReference),如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高 速缓存。使用软引用能防止内存泄露,增强程序的健壮性。
3.实例源码
(1)内存缓存
1. package com.zengtao.tools;
2.
3. import java.lang.ref.SoftReference;
4. import java.util.LinkedHashMap;
5.
6. import android.annotation.SuppressLint;
7. import android.app.ActivityManager;
8. import android.content.Context;
9. import android.graphics.Bitmap;
10. import android.util.LruCache;
11.
12. /**
13. * 内存缓存:两层缓存
14. *
15. * @author zengtao 2015年4月27日 上午10:39:23
16. */
17. public class MemoryCache {
18.
19. private final static int SOFT_CACHE_SIZE = 15; // 软引用缓存容量
20. private static LruCache<String, Bitmap> mLruCache; // 硬引用缓存
21. private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; // 软引用缓存
22.
23. @SuppressLint("NewApi")
24. public MemoryCache(Context context) {
25. int memClass = ((ActivityManager) context
26. .getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
27. int cacheSize = 1024 * 1024 * memClass / 4; // 获取系统的1/4的空间 作为缓存大小
28. new LruCache<String, Bitmap>(cacheSize) {
29.
30. @Override
31. protected int sizeOf(String key, Bitmap value) {
32. if (value != null) {
33. return value.getRowBytes() * value.getHeight();
34. }
35. return 0;
36. }
37.
38. @Override
39. protected void entryRemoved(boolean evicted, String key,
40. Bitmap oldValue, Bitmap newValue) {
41. if (oldValue != null) {
42. // 硬引用缓存满的时候,会根据lru算法把最近没有被使用的图片抓入软引用
43. new SoftReference<Bitmap>(oldValue));
44. }
45. }
46. };
47. new LinkedHashMap<String, SoftReference<Bitmap>>(
48. 0.75f, true) {
49.
50. private static final long serialVersionUID = 1L;
51.
52. @Override
53. protected boolean removeEldestEntry(
54. java.util.Map.Entry<String, SoftReference<Bitmap>> eldest) {
55. if (size() > SOFT_CACHE_SIZE) {
56. return true;
57. }
58. return false;
59. }
60. };
61. }
62.
63. /**
64. * 存储图片到缓存
65. *
66. * @param url
67. * :key
68. * @param bitmap
69. * : 图片
70. */
71. @SuppressLint("NewApi")
72. public void saveBitmap(String url, Bitmap bitmap) {
73. if (bitmap != null) {
74. synchronized (bitmap) {
75. mLruCache.put(url, bitmap);
76. }
77. }
78. }
79.
80. /**
81. * 获取缓存图片
82. *
83. * @param url
84. * :url
85. * @return
86. */
87. @SuppressLint("NewApi")
88. public Bitmap getBitmap(String url) {
89. null;
90. // 从硬引用找
91. synchronized (mLruCache) {
92. // 从硬引用中获取
93. bitmap = mLruCache.get(url);
94. if (bitmap != null) {
95. // 如果找到了,将元素移动到linkendHashMap的最前面,从而保证lrd算法中的是最后删除
96. mLruCache.remove(url);
97. mLruCache.put(url, bitmap);
98. return bitmap;
99. }
100. }
101. // 硬引用没找到,从软引用找
102. synchronized (mSoftCache) {
103. SoftReference<Bitmap> softReference = mSoftCache.get(url);
104. if (softReference != null) {
105. bitmap = softReference.get();
106. // 如果找到了,重新添加到硬缓存中
107. mLruCache.put(url, bitmap);
108. mSoftCache.remove(url);
109. return bitmap;
110. else {
111. mSoftCache.remove(url);
112. }
113. }
114. return null;
115. }
116.
117. public void clearCache() {
118. mSoftCache.clear();
119. }
120. }
(2)文件缓存
1. package com.zengtao.tools;
2.
3. import java.io.File;
4. import java.io.FileOutputStream;
5. import java.io.OutputStream;
6. import java.util.Arrays;
7. import java.util.Comparator;
8.
9. import android.annotation.SuppressLint;
10. import android.graphics.Bitmap;
11. import android.graphics.BitmapFactory;
12. import android.os.Environment;
13. import android.os.StatFs;
14.
15. /**
16. * 文件缓存
17. *
18. * @author zengtao 2015年4月27日 上午11:49:52
19. */
20. public class FileCache {
21. private final static String IMAGECACHE = "ImageCache";
22. private final static String lASTPATHNAME = ".cache"; // 文件名
23.
24. private final static int MB = 1024 * 1024;
25. private final static int CACHESIZE = 10;
26. private final static int SDCARD_FREE_SPANCE_CACHE = 10;
27.
28. public FileCache() {
29. removeCache(getDirectory());
30. }
31.
32. /**
33. * 将图片存入缓存
34. *
35. * @param url
36. * : 地址
37. * @param bitmap
38. * : 图片
39. */
40. public void saveBitmap(String url, Bitmap bitmap) {
41. if (bitmap == null) {
42. return;
43. }
44. if (SDCARD_FREE_SPANCE_CACHE > caluateSDCardFreeSpance()) {
45. return; // 空间不足
46. }
47. String fileName = convertUrlToFileName(url);
48. String dirPath = getDirectory();
49. new File(dirPath);
50. if (dirFile.exists()) {
51. dirFile.mkdirs();
52. }
53. new File(dirPath + "/" + fileName);
54. try {
55. file.createNewFile();
56. new FileOutputStream(file);
57. 100, outputStream);
58. outputStream.flush();
59. outputStream.close();
60. catch (Exception e) {
61. "文件未找到或者io异常");
62. }
63. }
64.
65. /**
66. * 获取文件缓存图片
67. *
68. * @param url
69. * : 地址
70. * @return : bitmap
71. */
72. public Bitmap getBitmap(final String url) {
73. null;
74. final String path = getDirectory() + convertUrlToFileName(url);
75. new File(path);
76. if (file.exists()) {
77. bitmap = BitmapFactory.decodeFile(path);
78. if (bitmap == null) {
79. file.delete();
80. else {
81. updateFileTime(path);
82. }
83. }
84. return bitmap;
85. }
86.
87. /**
88. * 获取sdCard路径
89. *
90. * @return :路径地址
91. */
92. private String getSDCardPath() {
93. "";
94. null;
95. boolean isSDCardExist = Environment.getExternalStorageState()
96. // 判断是否有sdCard
97. if (isSDCardExist) {
98. file = Environment.getExternalStorageDirectory();
99. }
100. if (file != null) {
101. path = file.toString();
102. }
103. return path;
104. }
105.
106. /**
107. * 计算存储目录下的文件大小,
108. * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
109. * 那么删除40%最近没有被使用的文件
110. */
111. private boolean removeCache(String dirPath) {
112. new File(dirPath);
113. File[] files = dir.listFiles();
114. if (files == null) {
115. return true;
116. }
117.
118. if (!android.os.Environment.getExternalStorageState().equals(
119. android.os.Environment.MEDIA_MOUNTED)) {
120. return false;
121. }
122.
123. int dirSize = 0;
124. for (int i = 0; i < files.length; i++) {
125. if (files[i].getName().contains(lASTPATHNAME)) {
126. dirSize += files[i].length();
127. }
128. }
129.
130. if (dirSize > CACHESIZE * MB
131. || SDCARD_FREE_SPANCE_CACHE > caluateSDCardFreeSpance()) {
132. int removeFactor = (int) ((0.4 * files.length) + 1);
133. new FileLastModifSort());
134. for (int i = 0; i < removeFactor; i++) {
135. if (files[i].getName().contains(lASTPATHNAME)) {
136. files[i].delete();
137. }
138. }
139. }
140.
141. if (caluateSDCardFreeSpance() <= CACHESIZE) {
142. return false;
143. }
144.
145. return true;
146. }
147.
148. /**
149. * 获取缓存目录
150. *
151. * @return : 目录
152. */
153. private String getDirectory() {
154. return getSDCardPath() + "/" + IMAGECACHE;
155. }
156.
157. /**
158. * 将url转换成文件名
159. *
160. * @param url
161. * : 地址
162. * @return : 文件名
163. */
164. private String convertUrlToFileName(final String url) {
165. "/");
166. return strs[strs.length - 1] + lASTPATHNAME;
167. }
168.
169. /**
170. * 计算sdCard上的空闲空间
171. *
172. * @return : 大小
173. */
174. @SuppressLint("NewApi")
175. private int caluateSDCardFreeSpance() {
176. int freespance = 0;
177. new StatFs(Environment.getExternalStorageDirectory()
178. .getPath());
179. long blocksize = start.getBlockSizeLong();
180. long availableBlocks = start.getAvailableBlocksLong();
181. "");
182. return freespance;
183. }
184.
185. /** 修改文件的最后修改时间 **/
186. public void updateFileTime(String path) {
187. new File(path);
188. long lastTime = System.currentTimeMillis();
189. file.setLastModified(lastTime);
190. }
191.
192. /** 根据文件的最后修改时间进行排序 **/
193. private class FileLastModifSort implements Comparator<File> {
194. public int compare(File arg0, File arg1) {
195. if (arg0.lastModified() > arg1.lastModified()) {
196. return 1;
197. else if (arg0.lastModified() == arg1.lastModified()) {
198. return 0;
199. else {
200. return -1;
201. }
202. }
203. }
204. }
(3)http缓存
1. package com.zengtao.tools;
2.
3. import java.io.FilterInputStream;
4. import java.io.IOException;
5. import java.io.InputStream;
6.
7. import org.apache.http.HttpEntity;
8. import org.apache.http.HttpResponse;
9. import org.apache.http.HttpStatus;
10. import org.apache.http.client.HttpClient;
11. import org.apache.http.client.methods.HttpGet;
12. import org.apache.http.impl.client.DefaultHttpClient;
13.
14. import android.graphics.Bitmap;
15. import android.graphics.BitmapFactory;
16. import android.util.Log;
17.
18. /**
19. * 网络缓存
20. *
21. * @author zengtao 2015年4月27日 下午3:32:34
22. */
23. public class HttpCache {
24. private static final String LOG_TAG = "ImageGetFromHttp";
25.
26. public static Bitmap downloadBitmap(String url) {
27. final HttpClient client = new DefaultHttpClient();
28. final HttpGet getRequest = new HttpGet(url);
29.
30. try {
31. HttpResponse response = client.execute(getRequest);
32. final int statusCode = response.getStatusLine().getStatusCode();
33. if (statusCode != HttpStatus.SC_OK) {
34. "Error " + statusCode
35. " while retrieving bitmap from " + url);
36. return null;
37. }
38.
39. final HttpEntity entity = response.getEntity();
40. if (entity != null) {
41. null;
42. try {
43. inputStream = entity.getContent();
44. new FlushedInputStream(inputStream);
45. return BitmapFactory.decodeStream(fit);
46. finally {
47. if (inputStream != null) {
48. inputStream.close();
49. null;
50. }
51. entity.consumeContent();
52. }
53. }
54. catch (IOException e) {
55. getRequest.abort();
56. "I/O error while retrieving bitmap from " + url, e);
57. catch (IllegalStateException e) {
58. getRequest.abort();
59. "Incorrect URL: " + url);
60. catch (Exception e) {
61. getRequest.abort();
62. "Error while retrieving bitmap from " + url, e);
63. finally {
64. client.getConnectionManager().shutdown();
65. }
66. return null;
67. }
68.
69. /**
70. * InputStream流有个小bug在慢速网络的情况下可能产生中断,可以考虑重写FilterInputStream处理skip方法来解决这个bug
71. * BitmapFactory类的decodeStream方法在网络超时或较慢的时候无法获取完整的数据,这里我
72. * 们通过继承FilterInputStream类的skip方法来强制实现flush流中的数据
73. * ,主要原理就是检查是否到文件末端,告诉http类是否继续。
74. *
75. * @author zengtao 2015年4月27日 下午6:33:17
76. */
77. static class FlushedInputStream extends FilterInputStream {
78. public FlushedInputStream(InputStream inputStream) {
79. super(inputStream);
80. }
81.
82. @Override
83. public long skip(long n) throws IOException {
84. long totalBytesSkipped = 0L;
85. while (totalBytesSkipped < n) {
86. long bytesSkipped = in.skip(n - totalBytesSkipped);
87. if (bytesSkipped == 0L) {
88. int b = read();
89. if (b < 0) {
90. break; // we reached EOF
91. else {
92. 1; // we read one byte
93. }
94. }
95. totalBytesSkipped += bytesSkipped;
96. }
97. return totalBytesSkipped;
98. }
99. }
100. }
(4)主函数中调用
1. @SuppressLint("HandlerLeak")
2. private Handler handler = new Handler() {
3. public void handleMessage(Message msg) {
4. if (msg.arg1 == 0x1) {
5. if (msg.obj != null) {
6. image.setImageBitmap((Bitmap) msg.obj);
7. }
8. }
9. };
10. };
11.
12. class MyThread extends Thread {
13. @Override
14. public void run() {
15. "http://a.hiphotos.baidu.com/image/pic/item/77c6a7efce1b9d16f5022b7ef1deb48f8d5464e3.jpg");
16. new Message();
17. 0x1;
18. message.obj = bitmap;
19. handler.sendMessage(message);
20. }
21. }
22.
23. /*** 获得一张图片,从三个地方获取,首先是内存缓存,然后是文件缓存,最后从网络获取 ***/
24. public Bitmap getBitmap(String url) {
25. // 1.从内存缓存中获取图片
26. Bitmap resultBitmap = memoryCache.getBitmap(url);
27. if (resultBitmap == null) {
28. // 2.文件缓存中获取
29. resultBitmap = fileCache.getBitmap(url);
30. if (resultBitmap == null) {
31. // 3.从网络获取
32. resultBitmap = HttpCache.downloadBitmap(url);
33. if (resultBitmap != null) {
34. fileCache.saveBitmap(url, resultBitmap);
35. memoryCache.saveBitmap(url, resultBitmap);
36. "3.网络缓存中获取图片");
37. }
38. else {
39. // 添加到内存缓存
40. memoryCache.saveBitmap(url, resultBitmap);
41. "2.文件缓存中获取图片");
42. }
43. else {
44. "1.内存缓存中获取图片");
45. }
46. return resultBitmap;
47. }
4.总结
以上就完成了一套缓存的设计,值得注意的是,当去网络获取图片的时候,图片过于庞大,一定要做去异步线程中获取图片,或者做本地缓存,这样不会让用户感觉自己的app卡死,是的用户体验效果更加。