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卡死,是的用户体验效果更加。