原創文章,轉載請註明:轉載自Keegan小鋼
本文鏈接地址:http://keegan-lee.diandian.com/post/2012-12-06/40047548955
如果每次加載同一張圖片都要從網絡獲取,那代價實在太大了。所以同一張圖片只要從網絡獲取一次就夠了,然後在本地緩存起來,之後加載同一張圖片時就從緩存中加載就可以了。從內存緩存讀取圖片是最快的,但是因爲內存容量有限,所以最好再加上文件緩存。文件緩存空間也不是無限大的,容量越大讀取效率越低,因此可以設置一個限定大小比如10M,或者限定保存時間比如一天。
因此,加載圖片的流程應該是:
1、先從內存緩存中獲取,取到則返回,取不到則進行下一步;
2、從文件緩存中獲取,取到則返回並更新到內存緩存,取不到則進行下一步;
3、從網絡下載圖片,並更新到內存緩存和文件緩存。
接下來看內存緩存類:ImageMemoryCache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
public
class ImageMemoryCache { /** * 從內存讀取數據速度是最快的,爲了更大限度使用內存,這裏使用了兩層緩存。 * 硬引用緩存不會輕易被回收,用來保存常用數據,不常用的轉入軟引用緩存。 */ private
static final
int SOFT_CACHE_SIZE = 15 ;
//軟引用緩存容量 private
static LruCache<String, Bitmap> mLruCache;
//硬引用緩存 private
static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache;
//軟引用緩存 public
ImageMemoryCache(Context context) { int
memClass = ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); int
cacheSize = 1024
* 1024 * memClass /
4 ; //硬引用緩存容量,爲系統可用內存的1/4 mLruCache =
new LruCache<String, Bitmap>(cacheSize) { @Override protected
int sizeOf(String key, Bitmap value) { if
(value != null ) return
value.getRowBytes() * value.getHeight(); else return
0 ; } @Override protected
void entryRemoved( boolean
evicted, String key, Bitmap oldValue, Bitmap newValue) { if
(oldValue != null ) // 硬引用緩存容量滿的時候,會根據LRU算法把最近沒有被使用的圖片轉入此軟引用緩存 mSoftCache.put(key,
new SoftReference<Bitmap>(oldValue)); } }; mSoftCache =
new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE,
0 .75f, true ) { private
static final
long serialVersionUID = 6040103833179403725L; @Override protected
boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) { if
(size() > SOFT_CACHE_SIZE){ return
true ; }
return
false ; } }; } /** * 從緩存中獲取圖片 */ public
Bitmap getBitmapFromCache(String url) { Bitmap bitmap; //先從硬引用緩存中獲取 synchronized
(mLruCache) { bitmap = mLruCache.get(url); if
(bitmap != null ) { //如果找到的話,把元素移到LinkedHashMap的最前面,從而保證在LRU算法中是最後被刪除 mLruCache.remove(url); mLruCache.put(url, bitmap); return
bitmap; } } //如果硬引用緩存中找不到,到軟引用緩存中找 synchronized
(mSoftCache) { SoftReference<Bitmap> bitmapReference = mSoftCache.get(url); if
(bitmapReference != null ) { bitmap = bitmapReference.get(); if
(bitmap != null ) { //將圖片移回硬緩存 mLruCache.put(url, bitmap); mSoftCache.remove(url); return
bitmap; }
else { mSoftCache.remove(url); } } } return
null ; }
/** * 添加圖片到緩存 */ public
void addBitmapToCache(String url, Bitmap bitmap) { if
(bitmap != null ) { synchronized
(mLruCache) { mLruCache.put(url, bitmap); } } } public
void clearCache() { mSoftCache.clear(); } } |
文件緩存類:ImageFileCache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
public
class ImageFileCache { private
static final
String CACHDIR = "ImgCach" ; private
static final
String WHOLESALE_CONV = ".cach" ; private
static final
int MB = 1024 * 1024 ; private
static final
int CACHE_SIZE = 10 ; private
static final
int FREE_SD_SPACE_NEEDED_TO_CACHE = 10 ; public
ImageFileCache() { //清理文件緩存 removeCache(getDirectory()); } /** 從緩存中獲取圖片 **/ public
Bitmap getImage( final
String url) { final
String path = getDirectory() + "/"
+ convertUrlToFileName(url); File file =
new File(path); if
(file.exists()) { Bitmap bmp = BitmapFactory.decodeFile(path); if
(bmp == null ) { file.delete(); }
else { updateFileTime(path); return
bmp; } } return
null ; } /** 將圖片存入文件緩存 **/ public
void saveBitmap(Bitmap bm, String url) { if
(bm == null ) { return ; } //判斷sdcard上的空間 if
(FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) { //SD空間不足 return ; } String filename = convertUrlToFileName(url); String dir = getDirectory(); File dirFile =
new File(dir); if
(!dirFile.exists()) dirFile.mkdirs(); File file =
new File(dir + "/"
+ filename); try
{ file.createNewFile(); OutputStream outStream =
new FileOutputStream(file); bm.compress(Bitmap.CompressFormat.JPEG,
100 , outStream); outStream.flush(); outStream.close(); }
catch (FileNotFoundException e) { Log.w( "ImageFileCache" ,
"FileNotFoundException" ); }
catch (IOException e) { Log.w( "ImageFileCache" ,
"IOException" ); } }
/** * 計算存儲目錄下的文件大小, * 當文件總大小大於規定的CACHE_SIZE或者sdcard剩餘空間小於FREE_SD_SPACE_NEEDED_TO_CACHE的規定 * 那麼刪除40%最近沒有被使用的文件 */ private
boolean removeCache(String dirPath) { File dir =
new File(dirPath); File[] files = dir.listFiles(); if
(files == null ) { return
true ; } if
(!android.os.Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED)) { return
false ; } int
dirSize = 0 ; for
( int
i = 0 ; i < files.length; i++) { if
(files[i].getName().contains(WHOLESALE_CONV)) { dirSize += files[i].length(); } } if
(dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) { int
removeFactor = ( int ) (( 0.4
* files.length) + 1 ); Arrays.sort(files,
new FileLastModifSort()); for
( int
i = 0 ; i < removeFactor; i++) { if
(files[i].getName().contains(WHOLESALE_CONV)) { files[i].delete(); } } } if
(freeSpaceOnSd() <= CACHE_SIZE) { return
false ; } return
true ; } /** 修改文件的最後修改時間 **/ public
void updateFileTime(String path) { File file =
new File(path); long
newModifiedTime = System.currentTimeMillis(); file.setLastModified(newModifiedTime); } /** 計算sdcard上的剩餘空間 **/ private
int freeSpaceOnSd() { StatFs stat =
new StatFs(Environment.getExternalStorageDirectory().getPath()); double
sdFreeMB = (( double )stat.getAvailableBlocks() * ( double ) stat.getBlockSize()) / MB; return
( int ) sdFreeMB; }
/** 將url轉成文件名 **/ private
String convertUrlToFileName(String url) { String[] strs = url.split( "/" ); return
strs[strs.length - 1 ] + WHOLESALE_CONV; } /** 獲得緩存目錄 **/ private
String getDirectory() { String dir = getSDPath() +
"/" + CACHDIR; return
dir; } /** 取SD卡路徑 **/ private
String getSDPath() { File sdDir =
null ; boolean
sdCardExist = Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED);
//判斷sd卡是否存在 if
(sdCardExist) { sdDir = Environment.getExternalStorageDirectory();
//獲取根目錄 } if
(sdDir != null ) { return
sdDir.toString(); }
else { return
"" ; } }
/** * 根據文件的最後修改時間進行排序 */ private
class FileLastModifSort
implements Comparator<File> { public
int compare(File arg0, File arg1) { if
(arg0.lastModified() > arg1.lastModified()) { return
1 ; }
else if
(arg0.lastModified() == arg1.lastModified()) { return
0 ; }
else { return
- 1 ; } } } } |
從網絡獲取圖片:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
public
class ImageGetFromHttp { private
static final
String LOG_TAG = "ImageGetFromHttp" ; public
static Bitmap downloadBitmap(String url) { final
HttpClient client = new
DefaultHttpClient(); final
HttpGet getRequest = new
HttpGet(url); try
{ HttpResponse response = client.execute(getRequest); final
int statusCode = response.getStatusLine().getStatusCode(); if
(statusCode != HttpStatus.SC_OK) { Log.w(LOG_TAG,
"Error " + statusCode +
" while retrieving bitmap from "
+ url); return
null ; } final
HttpEntity entity = response.getEntity(); if
(entity != null ) { InputStream inputStream =
null ; try
{ inputStream = entity.getContent(); FilterInputStream fit =
new FlushedInputStream(inputStream); return
BitmapFactory.decodeStream(fit); }
finally { if
(inputStream != null ) { inputStream.close(); inputStream =
null ; } entity.consumeContent(); } } }
catch (IOException e) { getRequest.abort(); Log.w(LOG_TAG,
"I/O error while retrieving bitmap from "
+ url, e); }
catch (IllegalStateException e) { getRequest.abort(); Log.w(LOG_TAG,
"Incorrect URL: "
+ url); }
catch (Exception e) { getRequest.abort(); Log.w(LOG_TAG,
"Error while retrieving bitmap from "
+ url, e); }
finally { client.getConnectionManager().shutdown(); } return
null ; } /* * An InputStream that skips the exact number of bytes provided, unless it reaches EOF. */ static
class FlushedInputStream
extends FilterInputStream { public
FlushedInputStream(InputStream inputStream) { super (inputStream); } @Override public
long skip( long
n) throws
IOException { long
totalBytesSkipped = 0L; while
(totalBytesSkipped < n) { long
bytesSkipped = in.skip(n - totalBytesSkipped); if
(bytesSkipped == 0L) { int
b = read(); if
(b < 0 ) { break ;
// we reached EOF }
else { bytesSkipped =
1 ; // we read one byte } } totalBytesSkipped += bytesSkipped; } return
totalBytesSkipped; } } } |
最後,獲取一張圖片的流程就如下代碼所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/*** 獲得一張圖片,從三個地方獲取,首先是內存緩存,然後是文件緩存,最後從網絡獲取 ***/ public
Bitmap getBitmap(String url) { // 從內存緩存中獲取圖片 Bitmap result = memoryCache.getBitmapFromCache(url); if
(result == null ) { // 文件緩存中獲取 result = fileCache.getImage(url); if
(result == null ) { // 從網絡獲取 result = ImageGetFromHttp.downloadBitmap(url); if
(result != null ) { fileCache.saveBitmap(result, url); memoryCache.addBitmapToCache(url, result); } }
else { // 添加到內存緩存 memoryCache.addBitmapToCache(url, result); } } return
result; } |