前言
其實很幸運,入職一周之后就能跟著兩個師兄做android開發,師兄都是大神,身為小白的我只能多多學習,多多努力。最近一段時間都忙的沒機會總結,今天剛完成了android客戶端圖片異步加載的類,這里記錄一下(ps:其實我這里都是參考網上開源實現)
原理
在ListView或者GridView中加載圖片的原理基本都是一樣的:
先從內存緩存中獲取,取到則返回,取不到進行下一步
從文件緩存中獲取,取到則返回并更新到內存緩存,取不到則進行進行下一步
從網絡上下載圖片,并更新內存緩存和文件緩存
流程圖如下:
同時,要注意線程的數量。一般在listview中加載圖片,大家都是開啟新的線程去加載,但是當快速滑動時,很容易造成OOM,因此需要控制線程數量。我們可以通過線程池控制線程的數量,具體線程池的大小還需要根據處理器的情況和業務情況自行判斷
建立線程池的方法如下:
1
|
ExecutorService executorService = Executors.newFixedThreadPool( 5 ); // 5是可變的 |
文件緩存類
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
|
import java.io.File; import android.content.Context; public class FileCache { private static final String DIR_NAME = "your_dir" ; private File cacheDir; public FileCache(Context context) { // Find the directory to save cached images if (android.os.Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED)) { cacheDir = new File( android.os.Environment.getExternalStorageDirectory(), DIR_NAME); } else { cacheDir = context.getCacheDir(); } if (!cacheDir.exists()) { cacheDir.mkdirs(); } } public File getFile(String url) { // Identify images by url's hash code String filename = String.valueOf(url.hashCode()); File f = new File(cacheDir, filename); return f; } public void clear() { File[] files = cacheDir.listFiles(); if (files == null ) { return ; } else { for (File f : files) { f.delete(); } } } } |
內存緩存類
這里使用了軟引用,Map<String, SoftReference<Bitmap>> cache,可以google一下軟引用的機制,簡單的說:實現了map,同時當內存緊張時可以被回收,不會造成內存泄露
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
|
import java.lang.ref.SoftReference; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import android.graphics.Bitmap; public class MemoryCache { private Map<String, SoftReference<Bitmap>> cache = Collections .synchronizedMap( new LinkedHashMap<String, SoftReference<Bitmap>>( 10 , 1 .5f, true )); public Bitmap get(String id) { if (!cache.containsKey(id)) { return null ; } SoftReference<Bitmap> ref = cache.get(id); return ref.get(); } public void put(String id, Bitmap bitmap) { cache.put(id, new SoftReference<Bitmap>(bitmap)); } public void clear() { cache.clear(); } } |
圖片加載類
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
|
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Collections; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Handler; import android.widget.ImageView; public class ImageLoader { /** * Network time out */ private static final int TIME_OUT = 30000 ; /** * Default picture resource */ private static final int DEFAULT_BG = R.drawable.plate_list_head_bg; /** * Thread pool number */ private static final int THREAD_NUM = 5 ; /** * Memory image cache */ MemoryCache memoryCache = new MemoryCache(); /** * File image cache */ FileCache fileCache; /** * Judge image view if it is reuse */ private Map<ImageView, String> imageViews = Collections .synchronizedMap( new WeakHashMap<ImageView, String>()); /** * Thread pool */ ExecutorService executorService; /** * Handler to display images in UI thread */ Handler handler = new Handler(); public ImageLoader(Context context) { fileCache = new FileCache(context); executorService = Executors.newFixedThreadPool(THREAD_NUM); } public void disPlayImage(String url, ImageView imageView) { imageViews.put(imageView, url); Bitmap bitmap = memoryCache.get(url); if (bitmap != null ) { // Display image from Memory cache imageView.setImageBitmap(bitmap); } else { // Display image from File cache or Network queuePhoto(url, imageView); } } private void queuePhoto(String url, ImageView imageView) { PhotoToLoad photoToLoad = new PhotoToLoad(url, imageView); executorService.submit( new PhotosLoader(photoToLoad)); } private Bitmap getBitmap(String url) { File f = fileCache.getFile(url); // From File cache Bitmap bmp = decodeFile(f); if (bmp != null ) { return bmp; } // From Network try { Bitmap bitmap = null ; URL imageUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) imageUrl .openConnection(); conn.setConnectTimeout(TIME_OUT); conn.setReadTimeout(TIME_OUT); conn.setInstanceFollowRedirects( true ); InputStream is = conn.getInputStream(); OutputStream os = new FileOutputStream(f); copyStream(is, os); os.close(); conn.disconnect(); bitmap = decodeFile(f); return bitmap; } catch (Throwable ex) { if (ex instanceof OutOfMemoryError) { clearCache(); } return null ; } } private void copyStream(InputStream is, OutputStream os) { int buffer_size = 1024 ; try { byte [] bytes = new byte [buffer_size]; while ( true ) { int count = is.read(bytes, 0 , buffer_size); if (count == - 1 ) { break ; } os.write(bytes, 0 , count); } } catch (Exception e) { } } private Bitmap decodeFile(File f) { try { // TODO:Compress image size FileInputStream fileInputStream = new FileInputStream(f); Bitmap bitmap = BitmapFactory.decodeStream(fileInputStream); return bitmap; } catch (FileNotFoundException e) { return null ; } } private void clearCache() { memoryCache.clear(); fileCache.clear(); } /** * Task for the queue * * @author zhengyi.wzy * */ private class PhotoToLoad { public String url; public ImageView imageView; public PhotoToLoad(String url, ImageView imageView) { this .url = url; this .imageView = imageView; } } /** * Asynchronous to load picture * * @author zhengyi.wzy * */ class PhotosLoader implements Runnable { PhotoToLoad photoToLoad; public PhotosLoader(PhotoToLoad photoToLoad) { this .photoToLoad = photoToLoad; } private boolean imageViewReused(PhotoToLoad photoToLoad) { String tag = imageViews.get(photoToLoad.imageView); if (tag == null || !tag.equals(photoToLoad.url)) { return true ; } return false ; } @Override public void run() { // Abort current thread if Image View reused if (imageViewReused(photoToLoad)) { return ; } Bitmap bitmap = getBitmap(photoToLoad.url); // Update Memory memoryCache.put(photoToLoad.url, bitmap); if (imageViewReused(photoToLoad)) { return ; } // Don't change UI in children thread BitmapDisplayer bd = new BitmapDisplayer(bitmap, photoToLoad); handler.post(bd); } class BitmapDisplayer implements Runnable { Bitmap bitmap; PhotoToLoad photoToLoad; public BitmapDisplayer(Bitmap bitmap, PhotoToLoad photoToLoad) { this .bitmap = bitmap; this .photoToLoad = photoToLoad; } @Override public void run() { if (imageViewReused(photoToLoad)) { return ; } if (bitmap != null ) { photoToLoad.imageView.setImageBitmap(bitmap); } else { photoToLoad.imageView.setImageResource(DEFAULT_BG); } } } } } |
調用方法
1
2
|
ImageLoader imageLoader = new ImageLoader(context); imageLoader.disPlayImage(imageUrl, imageView); |