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 }