连载 1 - 深入讨论 Android 关于高效显示图片的问题 - 如何高效的加载大位图

更加详细的说明,可以参阅如下官网地址: http://developer.android.com/training/building-graphics.html  

  刚开始做 Android 应用时,以为显示图片是很简单的事,在模拟器里运行的好好的,一放到真机上,经常遇到类似于 java.lang.OutofMemoryError: bitmap size exceeds VM budget. 之类的异常。后来看了下官网的详细的介绍,才发现关于图片的显示说头还不少。 
  下面就把学习心得与大家分享下: 

   为什么显示图片会遇到很棘手的问题?  
  手机显示一张 800 万像素的图片(现在主流的手机像素基本上都是 800 万像素以上),大约需要使用 32 MB 的内存,而这刚好是 Android 系统分配给每个应用的最大内存(有的 Android 设备分配给每个应用的最大内存只有 16 MB),所以如果手机应用直接打开这样一张图,基本上都会遇到由于内存溢出而导致程序被迫退出的情况。相信这种情况很多人可能都遇到过。 

  以 Galaxy Nexus 为例,其后置相机的像素是 500 万,其分辨率为 2592x1936 像素。若位图设置使用的是 ARGB_8888 (在 Android 2.3 及更高版本中,该值为默认值),那么加载该图将占用大约 19 MB 的内存(2592*1936*4 bytes),因此程序很快就会耗尽 Android 分配给每个应用程序的最大内存,从而导致程序崩溃。 

  即使应用程序不见得非要显示一张 500 万或更高像素的图片,如果程序设计不当,同样会在显示图片时,遇到程序崩溃的问题。例如,在图片相关的应用中,经常需要显示大量图片,因此经合会使用 ListView, GridView 或 ViewPager。若不对显示的图片进行处理,也会由于显示图片过多而导致程序崩溃。 

   如何解决显示图片导致的内存溢出的问题?  
  要解决这个问题,需要从以下五点入手: 
  1. 如何高效的加载大位图。(如何解码大位图,避免超过每个应用允许使用的最大内存) http://yhz61010.iteye.com/blog/1848337  
  2. 如何在非 UI 线程处理位图。(如何使用 AsyncTask 在后台线程处理位图及处理并发问题) http://yhz61010.iteye.com/blog/1848811  
  3. 如何对位图进行缓存。(如何通过创建内存缓存和磁盘缓存来流畅的显示多张位图) http://yhz61010.iteye.com/blog/1849645  
  4. 如何管理位图内存。(如何针对不同的 Android 版本管理位图内存) http://yhz61010.iteye.com/blog/1850232  
  5. 如何在 UI 中显示位图。(如何通过 ViewPager 和 GridView 显示多张图片) http://yhz61010.iteye.com/blog/1852927  

  下面我们来一一进行说明。 

   如何高效的加载大位图?  
  1. 获取位置尺寸及类型 
  使用 BitmapFactory 对位图进行解码时,使用 BitmapFactory.Options。将 Options 的 inJustDecodeBounds 设置为 true 时,可以避免为位图分配内存,此时 BitmapFactory.decodeX 的返回结果为 null,但是会为 Options 设置 outWidth, outHeight 和 outMimeType 值。 
Java代码   收藏代码
  1. BitmapFactory.Options options = new BitmapFactory.Options();  
  2. options.inJustDecodeBounds = true;  
  3. BitmapFactory.decodeResource(getResources(), R.id.myimage, options);  
  4. int imageHeight = options.outHeight;  
  5. int imageWidth = options.outWidth;  
  6. String imageType = options.outMimeType;  

  通过上述代码,你就可以在不为位图分配内存的情况下,获得位图的宽,高及位图类型。之后在显示图片时,就可以通过获取的信息来判断是否需要对图片进行处理后再显示,从而避免内存溢出问题。 

  2. 将缩小比例后的图片加载到内存 
  若程序中仅仅是为了显示一张 128x96 大小的缩略图,而将一张原始大小为 1024x768 的图片加载到内存,就显得很不划算了。因此,对将要显示的图片进行等比例缩小后再进行显示就显得很有必要。 

  通过设置 options.inSampleSize 来产生缩小后的图片。例如,若 options.inSampleSize = 4,那么对于一张原始大小为 2048x1536 的位图来说,产生的新位图大小约为 512x384。将这个新的位图加载到内存只需要 0.75 MB 内存,而原图则需要占用大约 12 MB 的内存。 

  具体程序实现如下: 
  首先,将 inJustDecodeBounds 设置为 true,获取位图信息,然后再设置新的 inSampleSize 值,最后再将 inJustDecodeBounds 设置为 false,从而将新生成的位图加载至内存。 
Java代码   收藏代码
  1. public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,  
  2.         int reqWidth, int reqHeight) {  
  3.   
  4.     // First decode with inJustDecodeBounds=true to check dimensions  
  5.     final BitmapFactory.Options options = new BitmapFactory.Options();  
  6.     options.inJustDecodeBounds = true;  
  7.     BitmapFactory.decodeResource(res, resId, options);  
  8.   
  9.     // Calculate inSampleSize  
  10.     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
  11.   
  12.     // Decode bitmap with inSampleSize set  
  13.     options.inJustDecodeBounds = false;  
  14.     return BitmapFactory.decodeResource(res, resId, options);  
  15. }  

  上述方法中使用的 calculateInSampleSize 方法的实现如下: 
Java代码   收藏代码
  1. public static int calculateInSampleSize(  
  2.             BitmapFactory.Options options, int reqWidth, int reqHeight) {  
  3.     // Raw height and width of image  
  4.     final int height = options.outHeight;  
  5.     final int width = options.outWidth;  
  6.     int inSampleSize = 1;  
  7.   
  8.     if (height > reqHeight || width > reqWidth) {  
  9.   
  10.         // Calculate ratios of height and width to requested height and width  
  11.         final int heightRatio = Math.round((float) height / (float) reqHeight);  
  12.         final int widthRatio = Math.round((float) width / (float) reqWidth);  
  13.   
  14.         // Choose the smallest ratio as inSampleSize value, this will guarantee  
  15.         // a final image with both dimensions larger than or equal to the  
  16.         // requested height and width.  
  17.         inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;  
  18.     }  
  19.   
  20.     return inSampleSize;  
  21. }  

  需要注意的是,上述方法返回的 inSampleSize 的值,最好是 2 的 n 次幂。(详见 http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inSampleSize ) 

  综上所述,有了上述这些方法,我们就可以在程序中加载任意大小的图片,而不用担心内存溢出的问题。例如,下述代码会将原始图片显示成 100x100 像素的缩略图: 
Java代码   收藏代码
  1. mImageView.setImageBitmap(  
  2.     decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100100));  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值