代码改变世界

Android图片管理组件

2013-03-07 10:00  yueyueniao  阅读(863)  评论(0编辑  收藏  举报

ImageManager2这个类具有异步从网络下载图片,从sd读取本地图片,内存缓存,硬盘缓存,图片使用动画渐现等功能,已经将其应用在包含大量图片的应用中一年多,没有出现oom。

Android程序常常会内存溢出,网上也有很多解决方案,如软引用,手动调用recycle等等。但经过我们实践发现这些方案,都没能起到很好的效果,我们的应用依然会出现很多oom,尤其我们的应用包含大量的图片。android3.0之后软引用基本已经失效,因为虚拟机只要碰到软引用就回收,所以带不来任何性能的提升。

我这里的解决方案是HandlerThread(异步加载)+LruCache(内存缓存)+DiskLruCache(硬盘缓存)

作为程序员,我也不多说,直接和大家共享我的代码,用代码交流更方便些。

源码demo地址:https://github.com/yueyueniao2012/multiimagechooser

 
  1 package com.example.util;
  2 
  3 import java.io.File;
  4 import java.util.Iterator;
  5 import java.util.LinkedList;
  6 import java.util.Queue;
  7 import java.util.Stack;
  8 
  9 import org.apache.http.HttpEntity;
 10 import org.apache.http.HttpResponse;
 11 import org.apache.http.client.methods.HttpGet;
 12 import org.apache.http.util.EntityUtils;
 13 
 14 import android.app.ActivityManager;
 15 import android.content.Context;
 16 import android.graphics.Bitmap;
 17 import android.graphics.BitmapFactory;
 18 import android.graphics.drawable.BitmapDrawable;
 19 import android.graphics.drawable.ColorDrawable;
 20 import android.graphics.drawable.Drawable;
 21 import android.graphics.drawable.TransitionDrawable;
 22 import android.media.ThumbnailUtils;
 23 import android.os.Handler;
 24 import android.os.HandlerThread;
 25 import android.os.Looper;
 26 import android.os.Message;
 27 import android.support.v4.util.LruCache;
 28 import android.widget.ImageView;
 29 
 30 import com.example.MyApplication;
 31 
 32 /**
 33  * 图片加载类
 34  * 
 35  * @author 月月鸟
 36  */
 37 public class ImageManager2 {
 38 
 39     private static ImageManager2 imageManager;
 40     public LruCache<String, Bitmap> mMemoryCache;
 41     private static final int DISK_CACHE_SIZE = 1024 * 1024 * 20; // 10MB
 42     private static final String DISK_CACHE_SUBDIR = "thumbnails";
 43     public DiskLruCache mDiskCache;
 44     private static MyApplication myapp;
 45 
 46     /** 图片加载队列,后进先出 */
 47     private Stack<ImageRef> mImageQueue = new Stack<ImageRef>();
 48 
 49     /** 图片请求队列,先进先出,用于存放已发送的请求。 */
 50     private Queue<ImageRef> mRequestQueue = new LinkedList<ImageRef>();
 51 
 52     /** 图片加载线程消息处理器 */
 53     private Handler mImageLoaderHandler;
 54 
 55     /** 图片加载线程是否就绪 */
 56     private boolean mImageLoaderIdle = true;
 57 
 58     /** 请求图片 */
 59     private static final int MSG_REQUEST = 1;
 60     /** 图片加载完成 */
 61     private static final int MSG_REPLY = 2;
 62     /** 中止图片加载线程 */
 63     private static final int MSG_STOP = 3;
 64     /** 如果图片是从网络加载,则应用渐显动画,如果从缓存读出则不应用动画 */
 65     private boolean isFromNet = true;
 66 
 67     /**
 68      * 获取单例,只能在UI线程中使用。
 69      * 
 70      * @param context
 71      * @return
 72      */
 73     public static ImageManager2 from(Context context) {
 74 
 75         // 如果不在ui线程中,则抛出异常
 76         if (Looper.myLooper() != Looper.getMainLooper()) {
 77             throw new RuntimeException("Cannot instantiate outside UI thread.");
 78         }
 79 
 80         if (myapp == null) {
 81             myapp = (MyApplication) context.getApplicationContext();
 82         }
 83 
 84         if (imageManager == null) {
 85             imageManager = new ImageManager2(myapp);
 86         }
 87 
 88         return imageManager;
 89     }
 90 
 91     /**
 92      * 私有构造函数,保证单例模式
 93      * 
 94      * @param context
 95      */
 96     private ImageManager2(Context context) {
 97         int memClass = ((ActivityManager) context
 98                 .getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
 99         memClass = memClass > 32 ? 32 : memClass;
100         // 使用可用内存的1/8作为图片缓存
101         final int cacheSize = 1024 * 1024 * memClass / 8;
102 
103         mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
104 
105             protected int sizeOf(String key, Bitmap bitmap) {
106                 return bitmap.getRowBytes() * bitmap.getHeight();
107             }
108 
109         };
110 
111         File cacheDir = DiskLruCache
112                 .getDiskCacheDir(context, DISK_CACHE_SUBDIR);
113         mDiskCache = DiskLruCache.openCache(context, cacheDir, DISK_CACHE_SIZE);
114 
115     }
116 
117     /**
118      * 存放图片信息
119      */
120     class ImageRef {
121 
122         /** 图片对应ImageView控件 */
123         ImageView imageView;
124         /** 图片URL地址 */
125         String url;
126         /** 图片缓存路径 */
127         String filePath;
128         /** 默认图资源ID */
129         int resId;
130         int width = 0;
131         int height = 0;
132 
133         /**
134          * 构造函数
135          * 
136          * @param imageView
137          * @param url
138          * @param resId
139          * @param filePath
140          */
141         ImageRef(ImageView imageView, String url, String filePath, int resId) {
142             this.imageView = imageView;
143             this.url = url;
144             this.filePath = filePath;
145             this.resId = resId;
146         }
147 
148         ImageRef(ImageView imageView, String url, String filePath, int resId,
149                 int width, int height) {
150             this.imageView = imageView;
151             this.url = url;
152             this.filePath = filePath;
153             this.resId = resId;
154             this.width = width;
155             this.height = height;
156         }
157 
158     }
159 
160     /**
161      * 显示图片
162      * 
163      * @param imageView
164      * @param url
165      * @param resId
166      */
167     public void displayImage(ImageView imageView, String url, int resId) {
168         if (imageView == null) {
169             return;
170         }
171         if (imageView.getTag() != null
172                 && imageView.getTag().toString().equals(url)) {
173             return;
174         }
175         if (resId >= 0) {
176             if (imageView.getBackground() == null) {
177                 imageView.setBackgroundResource(resId);
178             }
179             imageView.setImageDrawable(null);
180 
181         }
182         if (url == null || url.equals("")) {
183             return;
184         }
185 
186         // 添加url tag
187         imageView.setTag(url);
188 
189         // 读取map缓存
190         Bitmap bitmap = mMemoryCache.get(url);
191         if (bitmap != null) {
192             setImageBitmap(imageView, bitmap, false);
193             return;
194         }
195 
196         // 生成文件名
197         String filePath = urlToFilePath(url);
198         if (filePath == null) {
199             return;
200         }
201 
202         queueImage(new ImageRef(imageView, url, filePath, resId));
203     }
204 
205     /**
206      * 显示图片固定大小图片的缩略图,一般用于显示列表的图片,可以大大减小内存使用
207      * 
208      * @param imageView 加载图片的控件
209      * @param url 加载地址
210      * @param resId 默认图片
211      * @param width 指定宽度
212      * @param height 指定高度
213      */
214     public void displayImage(ImageView imageView, String url, int resId,
215             int width, int height) {
216         if (imageView == null) {
217             return;
218         }
219         if (resId >= 0) {
220 
221             if (imageView.getBackground() == null) {
222                 imageView.setBackgroundResource(resId);
223             }
224             imageView.setImageDrawable(null);
225 
226         }
227         if (url == null || url.equals("")) {
228             return;
229         }
230 
231         // 添加url tag
232         imageView.setTag(url);
233         // 读取map缓存
234         Bitmap bitmap = mMemoryCache.get(url + width + height);
235         if (bitmap != null) {
236             setImageBitmap(imageView, bitmap, false);
237             return;
238         }
239 
240         // 生成文件名
241         String filePath = urlToFilePath(url);
242         if (filePath == null) {
243             return;
244         }
245 
246         queueImage(new ImageRef(imageView, url, filePath, resId, width, height));
247     }
248 
249     /**
250      * 入队,后进先出
251      * 
252      * @param imageRef
253      */
254     public void queueImage(ImageRef imageRef) {
255 
256         // 删除已有ImageView
257         Iterator<ImageRef> iterator = mImageQueue.iterator();
258         while (iterator.hasNext()) {
259             if (iterator.next().imageView == imageRef.imageView) {
260                 iterator.remove();
261             }
262         }
263 
264         // 添加请求
265         mImageQueue.push(imageRef);
266         sendRequest();
267     }
268 
269     /**
270      * 发送请求
271      */
272     private void sendRequest() {
273 
274         // 开启图片加载线程
275         if (mImageLoaderHandler == null) {
276             HandlerThread imageLoader = new HandlerThread("image_loader");
277             imageLoader.start();
278             mImageLoaderHandler = new ImageLoaderHandler(
279                     imageLoader.getLooper());
280         }
281 
282         // 发送请求
283         if (mImageLoaderIdle && mImageQueue.size() > 0) {
284             ImageRef imageRef = mImageQueue.pop();
285             Message message = mImageLoaderHandler.obtainMessage(MSG_REQUEST,
286                     imageRef);
287             mImageLoaderHandler.sendMessage(message);
288             mImageLoaderIdle = false;
289             mRequestQueue.add(imageRef);
290         }
291     }
292 
293     /**
294      * 图片加载线程
295      */
296     class ImageLoaderHandler extends Handler {
297 
298         public ImageLoaderHandler(Looper looper) {
299             super(looper);
300         }
301 
302         public void handleMessage(Message msg) {
303             if (msg == null)
304                 return;
305 
306             switch (msg.what) {
307 
308             case MSG_REQUEST: // 收到请求
309                 Bitmap bitmap = null;
310                 Bitmap tBitmap = null;
311                 if (msg.obj != null && msg.obj instanceof ImageRef) {
312 
313                     ImageRef imageRef = (ImageRef) msg.obj;
314                     String url = imageRef.url;
315                     if (url == null)
316                         return;
317                     // 如果本地url即读取sd相册图片,则直接读取,不用经过DiskCache
318                     if (url.toLowerCase().contains("dcim")) {
319 
320                         tBitmap = null;
321                         BitmapFactory.Options opt = new BitmapFactory.Options();
322                         opt.inSampleSize = 1;
323                         opt.inJustDecodeBounds = true;
324                         BitmapFactory.decodeFile(url, opt);
325                         int bitmapSize = opt.outHeight * opt.outWidth * 4;
326                         opt.inSampleSize = bitmapSize / (1000 * 2000);
327                         opt.inJustDecodeBounds = false;
328                         tBitmap = BitmapFactory.decodeFile(url, opt);
329                         if (imageRef.width != 0 && imageRef.height != 0) {
330                             bitmap = ThumbnailUtils.extractThumbnail(tBitmap,
331                                     imageRef.width, imageRef.height,
332                                     ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
333                             isFromNet = true;
334                         } else {
335                             bitmap = tBitmap;
336                             tBitmap = null;
337                         }
338 
339                     } else
340                         bitmap = mDiskCache.get(url);
341 
342                     if (bitmap != null) {
343                         // ToolUtil.log("从disk缓存读取");
344                         // 写入map缓存
345                         if (imageRef.width != 0 && imageRef.height != 0) {
346                             if (mMemoryCache.get(url + imageRef.width
347                                     + imageRef.height) == null)
348                                 mMemoryCache.put(url + imageRef.width
349                                         + imageRef.height, bitmap);
350                         } else {
351                             if (mMemoryCache.get(url) == null)
352                                 mMemoryCache.put(url, bitmap);
353                         }
354 
355                     } else {
356                         try {
357                             byte[] data = loadByteArrayFromNetwork(url);
358 
359                             if (data != null) {
360 
361                                 BitmapFactory.Options opt = new BitmapFactory.Options();
362                                 opt.inSampleSize = 1;
363 
364                                 opt.inJustDecodeBounds = true;
365                                 BitmapFactory.decodeByteArray(data, 0,
366                                         data.length, opt);
367                                 int bitmapSize = opt.outHeight * opt.outWidth
368                                         * 4;// pixels*3 if it's RGB and pixels*4
369                                             // if it's ARGB
370                                 if (bitmapSize > 1000 * 1200)
371                                     opt.inSampleSize = 2;
372                                 opt.inJustDecodeBounds = false;
373                                 tBitmap = BitmapFactory.decodeByteArray(data,
374                                         0, data.length, opt);
375                                 if (imageRef.width != 0 && imageRef.height != 0) {
376                                     bitmap = ThumbnailUtils
377                                             .extractThumbnail(
378                                                     tBitmap,
379                                                     imageRef.width,
380                                                     imageRef.height,
381                                                     ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
382                                 } else {
383                                     bitmap = tBitmap;
384                                     tBitmap = null;
385                                 }
386 
387                                 if (bitmap != null && url != null) {
388                                     // 写入SD卡
389                                     if (imageRef.width != 0
390                                             && imageRef.height != 0) {
391                                         mDiskCache.put(url + imageRef.width
392                                                 + imageRef.height, bitmap);
393                                         mMemoryCache.put(url + imageRef.width
394                                                 + imageRef.height, bitmap);
395                                     } else {
396                                         mDiskCache.put(url, bitmap);
397                                         mMemoryCache.put(url, bitmap);
398                                     }
399                                     isFromNet = true;
400                                 }
401                             }
402                         } catch (OutOfMemoryError e) {
403                         }
404 
405                     }
406 
407                 }
408 
409                 if (mImageManagerHandler != null) {
410                     Message message = mImageManagerHandler.obtainMessage(
411                             MSG_REPLY, bitmap);
412                     mImageManagerHandler.sendMessage(message);
413                 }
414                 break;
415 
416             case MSG_STOP: // 收到终止指令
417                 Looper.myLooper().quit();
418                 break;
419 
420             }
421         }
422     }
423 
424     /** UI线程消息处理器 */
425     private Handler mImageManagerHandler = new Handler() {
426 
427         @Override
428         public void handleMessage(Message msg) {
429             if (msg != null) {
430                 switch (msg.what) {
431 
432                 case MSG_REPLY: // 收到应答
433 
434                     do {
435                         ImageRef imageRef = mRequestQueue.remove();
436 
437                         if (imageRef == null)
438                             break;
439 
440                         if (imageRef.imageView == null
441                                 || imageRef.imageView.getTag() == null
442                                 || imageRef.url == null)
443                             break;
444 
445                         if (!(msg.obj instanceof Bitmap) || msg.obj == null) {
446                             break;
447                         }
448                         Bitmap bitmap = (Bitmap) msg.obj;
449 
450                         // 非同一ImageView
451                         if (!(imageRef.url).equals((String) imageRef.imageView
452                                 .getTag())) {
453                             break;
454                         }
455 
456                         setImageBitmap(imageRef.imageView, bitmap, isFromNet);
457                         isFromNet = false;
458 
459                     } while (false);
460 
461                     break;
462                 }
463             }
464             // 设置闲置标志
465             mImageLoaderIdle = true;
466 
467             // 若服务未关闭,则发送下一个请求。
468             if (mImageLoaderHandler != null) {
469                 sendRequest();
470             }
471         }
472     };
473 
474     /**
475      * 添加图片显示渐现动画
476      * 
477      */
478     private void setImageBitmap(ImageView imageView, Bitmap bitmap,
479             boolean isTran) {
480         if (isTran) {
481             final TransitionDrawable td = new TransitionDrawable(
482                     new Drawable[] {
483                             new ColorDrawable(android.R.color.transparent),
484                             new BitmapDrawable(bitmap) });
485             td.setCrossFadeEnabled(true);
486             imageView.setImageDrawable(td);
487             td.startTransition(300);
488         } else {
489             imageView.setImageBitmap(bitmap);
490         }
491     }
492 
493     /**
494      * 从网络获取图片字节数组
495      * 
496      * @param url
497      * @return
498      */
499     private byte[] loadByteArrayFromNetwork(String url) {
500 
501         try {
502 
503             HttpGet method = new HttpGet(url);
504             HttpResponse response = myapp.getHttpClient().execute(method);
505             HttpEntity entity = response.getEntity();
506             return EntityUtils.toByteArray(entity);
507 
508         } catch (Exception e) {
509             return null;
510         }
511 
512     }
513 
514     /**
515      * 根据url生成缓存文件完整路径名
516      * 
517      * @param url
518      * @return
519      */
520     public String urlToFilePath(String url) {
521 
522         // 扩展名位置
523         int index = url.lastIndexOf('.');
524         if (index == -1) {
525             return null;
526         }
527 
528         StringBuilder filePath = new StringBuilder();
529 
530         // 图片存取路径
531         filePath.append(myapp.getCacheDir().toString()).append('/');
532 
533         // 图片文件名 
534         filePath.append(MD5.Md5(url)).append(url.substring(index));
535 
536         return filePath.toString();
537     }
538 
539     /**
540      * Activity#onStop后,ListView不会有残余请求。
541      */
542     public void stop() {
543 
544         // 清空请求队列
545         mImageQueue.clear();
546 
547     }
548 
549 }