Displaying Bitmaps Efficiently


http://wiki.eoeandroid.com/index.php?title=Displaying_Bitmaps_Efficiently&diff=10485&oldid=0

http://phenom.iteye.com/blog/1541336

处理Bitmap对象的,可以帮助您的UI快速响应,避免内存溢出。如果您不小心,可能很快地消耗了内存,导致程序的崩溃。抛出异常:java.lang.OutofMemoryError: bitmap size exceeds VM budget. Bitmap会消耗很多的内存,尤其像照片,摄像头在GalaxyNexus拍一张照片有2592*1936像素,如果bitmap使用ARGB_8888配置(2.3默认的),加载这张照片到内存需要消耗约19mb内存,(2592*1936*4bytes)。就超过了应用的内存限制了。 




  1. Read Bitmap Dimensions and Type  
  2. BitmapFactory类提供了一些解码的方法decodeByteArray(),decodeFile(),decodeResource()等,来创建一个位图资源。需要选择正确的方法来解析图片资源。这些方法会构造一个位图,然后申请内存,容易造成oome,每一个方法都有一个额外的信息为您 分配解码选项BitmapFactory.Options类,设置inJustDecodeBounds属性为true时,解码避免了内存占用,它不会返回位图信息,而设置了outWidth,outHeight,outMimeTYpe。这些就可以供读取解析度与类型了。  
  3. BitmapFactory.Options options = new BitmapFactory.Options();  
  4. options.inJustDecodeBounds = true
  5. BitmapFactory.decodeResource(getResources(), R.id.myimage, options);  
  6. int imageHeight = options.outHeight;  
  7. int imageWidth = options.outWidth;  
  8. String imageType = options.outMimeType; 

  1. Load a Scaled Down Version into Memory  
  2. 加载一个缩放后的版本到内存中,位图的dimensions从上面得到后,可用于决定是否位图可以加载到内存,或加载一个缩放后的版本。  
  3. 这里给出一些因素供参考:  
  4. 估计内存的消耗。  
  5. 计算应用的其它部分占用的内存与当前图片应该占用的内存。  
  6. 贴图目标需要多大的dimensions,如ImageView,或其它ui。  
  7. 屏幕的大小与解析度。  
  8.   
  9. 如,显示在128x96像素的ImageView中不值得加载一个1024*768像素的图片。  
  10. 在BitmapFactory.Options 设置inSampleSize的值就可以改变了。  
  11. 2048*1536像素的图片解码时inSampleSize=4,就会得到一个512*384大小的位图了,使用0.75mb内存,而不是12mb。  
  12. 下面提供一个方法来计算 :  
  13. public static int calculateInSampleSize(  
  14.             BitmapFactory.Options options, int reqWidth, int reqHeight) {  
  15.     // Raw height and width of image  
  16.     final int height = options.outHeight;  
  17.     final int width = options.outWidth;  
  18.     int inSampleSize = 1;  
  19.   
  20.     if (height > reqHeight || width > reqWidth) {  
  21.         if (width > height) {  
  22.             inSampleSize = Math.round((float)height / (float)reqHeight);  
  23.         } else {  
  24.             inSampleSize = Math.round((float)width / (float)reqWidth);  
  25.         }  
  26.     }  
  27.     return inSampleSize;  
  28. }  
  29. 注意:使用2的倍数会对解码更高效的(我估计是二进制的原因),  
  30. 使用这个方法,首先需要options.inJustDecodeBounds = true;先解码一次,然后再使用新的inSampleSize值,options.inJustDecodeBounds = false来解码需要的图片。  
  31. public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,  
  32.         int reqWidth, int reqHeight) {  
  33.   
  34.     // First decode with inJustDecodeBounds=true to check dimensions  
  35.     final BitmapFactory.Options options = new BitmapFactory.Options();  
  36.     options.inJustDecodeBounds = true;  
  37.     BitmapFactory.decodeResource(res, resId, options);  
  38.   
  39.     // Calculate inSampleSize  
  40.     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
  41.   
  42.     // Decode bitmap with inSampleSize set  
  43.     options.inJustDecodeBounds = false;  
  44.     return BitmapFactory.decodeResource(res, resId, options);  


BitmapFactory.decode*方法,在上一篇讨论过的,不应该在ui线程上处理的情况:从硬盘加载或从网络加载。因为加载时间未知,如果时间过久,会导致程序失去响应。 
a variety of factors (speed of reading from disk or network,size of image, power of CPU, etc.).

  1. AsyncTask类提供了一个简易的方法处理后台事务,并通知ui线程。使用它需要创建一个子类,覆盖一些方法这里举一个加载图片到ImageView的例子:  
  2. class BitmapWorkerTask extends AsyncTask {  
  3.     private final WeakReference imageViewReference;  
  4.     private int data = 0;  
  5.   
  6.     public BitmapWorkerTask(ImageView imageView) {  
  7.         // Use a WeakReference to ensure the ImageView can be garbage collected  
  8.         imageViewReference = new WeakReference(imageView);  
  9.     }  
  10.   
  11.     // Decode image in background.  
  12.     @Override  
  13.     protected Bitmap doInBackground(Integer... params) {  
  14.         data = params[0];  
  15.         return decodeSampledBitmapFromResource(getResources(), data, 100100));  
  16.     }  
  17.   
  18.     // Once complete, see if ImageView is still around and set bitmap.  
  19.     @Override  
  20.     protected void onPostExecute(Bitmap bitmap) {  
  21.         if (imageViewReference != null && bitmap != null) {  
  22.             final ImageView imageView = imageViewReference.get();  
  23.             if (imageView != null) {  
  24.                 imageView.setImageBitmap(bitmap);  
  25.             }  
  26.         }  
  27.     }  
  28. }  
  29. WeakReference是为了避免ImageView被回收时由于引用造成无法回收。所以多次判断是否为null值。这种为空的情况如Activity已经到了其它Activity,或配置变化了。 
The WeakReference to the ImageView ensures that the AsyncTask does not prevent the ImageView and anything itreferences from being garbage collected. There’s no guarantee the ImageViewis still around when the task finishes, so you must also check the reference in onPostExecute(). The ImageViewmay no longer exist, if for example, the user navigates away from the activity or if aconfiguration change happens before the task finishes.


  1. Handle Concurrency:  
  2. ListView,GridView是另一个麻烦的地方,为了有效地使用内存,这些组件会在用户滚动时回收一些子View,如果每一个View都触发一个AsyncTask,不能保证在操作完成时,相关的View还存在。它可能被回收了  
  3. http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html 更详细地说明了并发的问题,提供了一个解决办法。存储最近的AsyncTask。  
  4.   
  5. 提供专用的Drawable子类来存储task,  
  6. static class AsyncDrawable extends BitmapDrawable {  
  7.     private final WeakReference bitmapWorkerTaskReference;  
  8.   
  9.     public AsyncDrawable(Resources res, Bitmap bitmap,  
  10.             BitmapWorkerTask bitmapWorkerTask) {  
  11.         super(res, bitmap);  
  12.         bitmapWorkerTaskReference =  
  13.             new WeakReference(bitmapWorkerTask);  
  14.     }  
  15.   
  16.     public BitmapWorkerTask getBitmapWorkerTask() {  
  17.         return bitmapWorkerTaskReference.get();  
  18.     }  
  19. }  
  20. 在执行BitmapWorkerTask时,先创建一个AsyncDrawable,绑定到相关的ImageView中,  
  21. public void loadBitmap(int resId, ImageView imageView) {  
  22.     if (cancelPotentialWork(resId, imageView)) {  
  23.         final BitmapWorkerTask task = new BitmapWorkerTask(imageView);  
  24.         final AsyncDrawable asyncDrawable =  
  25.                 new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);  
  26.         imageView.setImageDrawable(asyncDrawable);  
  27.         task.execute(resId);  
  28.     }  
  29. }  
  30. cancelPotentialWork方法就是检查是否关联的task已经在运行了。它先调用cancel()结束先前的方法,  
  31. public static boolean cancelPotentialWork(int data, ImageView imageView) {  
  32.     final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);  
  33.   
  34.     if (bitmapWorkerTask != null) {  
  35.         final int bitmapData = bitmapWorkerTask.data;  
  36.         if (bitmapData != data) {  
  37.             // Cancel previous task  
  38.             bitmapWorkerTask.cancel(true);  
  39.         } else {  
  40.             // The same work is already in progress  
  41.             return false;  
  42.         }  
  43.     }  
  44.     // No task associated with the ImageView, or an existing task was cancelled  
  45.     return true;  
  46. }  
  47. getBitmapWorkerTask这个方法用于关联特定的ImageView  
  48. private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {  
  49.    if (imageView != null) {  
  50.        final Drawable drawable = imageView.getDrawable();  
  51.        if (drawable instanceof AsyncDrawable) {  
  52.            final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;  
  53.            return asyncDrawable.getBitmapWorkerTask();  
  54.        }  
  55.     }  
  56.     return null;  
  57. }  
  58. 最后一步是在onPostExecute()确认是否任务结束了和当前的关联ImageView匹配:  
  59. class BitmapWorkerTask extends AsyncTask {  
  60.     ...  
  61.   
  62.     @Override  
  63.     protected void onPostExecute(Bitmap bitmap) {  
  64.         if (isCancelled()) {  
  65.             bitmap = null;  
  66.         }  
  67.   
  68.         if (imageViewReference != null && bitmap != null) {  
  69.             final ImageView imageView = imageViewReference.get();  
  70.             final BitmapWorkerTask bitmapWorkerTask =  
  71.                     getBitmapWorkerTask(imageView);  
  72.             if (this == bitmapWorkerTask && imageView != null) {  
  73.                 imageView.setImageBitmap(bitmap);  
  74.             }  
  75.         }  
  76.     }  
  77. }  
  78.   
  79. 这个实现可以用于listview,gridview这样回收他们的子元素的组件中,只要简单地调用loadBitmap方法就可以了。  
  80.  
LruCache去缓存图片

A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.

  1. Use a Disk Cache 磁盘缓存。  
  2. 内存缓存更快速,当然不能仅靠内存来维持,listview,gridview会很快地占用了内存中的图片,而且你的Activity可能被销毁,然后再加载,这时就需要另一处缓存了。  
  3.   
  4. 磁盘缓存主要在下载图片时用到,缓存后不用再次下载,从磁盘中加载当然比从网络中要快得多了  
  5. DiskLruCache已经是一种健壮的实现 了,在4.0中提供了源码libcore/luni/src/main/java/libcore/io/DiskLruCache.java, 
  1. Handle Configuration Changes  
  2. 运行时配置改变了,如屏幕的方向改变了,导致Android会销毁,重启。这就需要避免处理所有的图片了,南昌需要一个更缓和,更高效的办法。  
  3.   
  4. 前面已经讨论过内存缓存了,这个缓存可以通过Fragment的setRetainInstance(true)得到,Activity重建以后,Fragment会重新加载,reattached附着到Activity中下面是一个使用Fragment与LruCache在配置改变时的例子。 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值