Bitmap是引起OOM的罪魁祸首之一,当我们从网络上下载图片的时候无法知道网络图片的准确大小,所以为了节约内存,一般会在服务器上缓存一个缩略图,提升下载速度。除此之外,我们还可以在本地显示图片前将图片进行压缩,使其完全符合imageview的大小,这样就不会浪费内存了。

一、思路

思路:计算出要显示bitmap的imageview大小,根据imageview的大小压缩bitmap,最终让bitmap和imageview一样大。

二、获得ImageView的宽高

int android.view.View.getWidth() // 返回:The width of your view, in pixels. 
int android.view.View.getWidth() // 返回:The height of your view, in pixels. 

通过这两个方法我就能得到imageview实际的大小了,单位是pix。这个方法请在imageview加载完毕后再调用,否则一致返回空。如果你不知道什么时候会加载完毕,你可以将其放入view.post方法中。

  1.        view.post(new Runnable() {
  2.  
  3. @Override
  4. public void run() {
  5. // TODO 自动生成的方法存根
  6. }
  7. })

三、通过BitmapFactory得到压缩后的bitmap

3.1 BitmapFactory.Options

BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们应该根据图片的来源选择合适的方法。

这些方法都会为一个bitmap分配内存,如果你的图片太大就很容易造成bitmap。为此,这里面都可以传入一个BitmapFactory.Options对象,用来进行配置。

  1. BitmapFactory.Options options = new BitmapFactory.Options();

BitmapFactory.Options中有个inJustDecodeBounds属性,这个属性为true时,调用上面三个方法返回的就不是一个完整的bitmap对象,而是null。这是因为它禁止这些方法为bitmap分配内存。那么它有什么用呢?设置inJustDecodeBounds=true后,BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。就等于不读取图片,但获得图片的各种参数,大大节约内存。

  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;

3.2 初步计算压缩比率

BitmapFactory.Options中有个inSampleSize属性,可以理解为压缩比率。设定好压缩比率后,调用上面的decodexxxx()就能得到一个缩略图了。比如inSampleSize=4,载入的缩略图是原图大小的1/4。

为了避免OOM异常,最好在解析每张图片的时候都先检查一下图片的大小,以下几个因素是我们需要考虑的:

  • 预估一下加载整张图片所需占用的内存

  • 为了加载这一张图片你所愿意提供多少内存

  • 用于展示这张图片的控件的实际大小

  • 当前设备的屏幕尺寸和分辨率

比如,你的ImageView只有128*96像素的大小,只是为了显示一张缩略图,这时候把一张1024*768像素的图片完全加载到内存中显然是不值得的。比如我们有一张2048*1536像素的图片,将inSampleSize的值设置为4,就可以把这张图片压缩成512*384像素。原本加载这张图片需要占用13M的内存,压缩后就只需要占用0.75M了(假设图片是ARGB_8888类型,即每个像素点占用4个字节)。

同理,假设原图是1500x700的,我们给缩略图留出的空间是100x100的。那么inSampleSize=min(1500/100, 700/100)=7。我们可以得到的缩略图是原图的1/7。

下面的代码可以用来计算这个inSampleSize的值:

  1. public static int calculateInSampleSize(BitmapFactory.Options options,
  2. int reqWidth, int reqHeight) {
  3. // 源图片的高度和宽度
  4. final int height = options.outHeight;
  5. final int width = options.outWidth;
  6. int inSampleSize = 1;
  7. if (height > reqHeight || width > reqWidth) {
  8. // 计算出实际宽高和目标宽高的比率
  9. final int heightRatio = Math.round((float) height / (float) reqHeight);
  10. final int widthRatio = Math.round((float) width / (float) reqWidth);
  11. // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
  12. // 一定都会大于等于目标的宽和高。
  13. inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
  14. }
  15. return inSampleSize;
  16. }

然而,事实不如我们现象的那么美好。inSampleSize的注释中有一个需要注意的一点。

If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1.

Note: the decoder uses a final value based on powers of 2, any other value will be rounded down to the nearest power of 2.

即使设置了inSampleSize=7,但是得到的缩略图却是原图的1/4,原因是inSampleSize只能是2的整数次幂,如果不是的话,向下取得最大的2的整数次幂,7向下寻找2的整数次幂,就是4。这样设计的原因很可能是为了渐变bitmap压缩,毕竟按照2的次方进行压缩会比较高效和方便。

3.3 再次计算压缩比率

通过上面的分析我们知道,bitmap是可以被压缩的,我们可以根据需要来压缩bitmap,但压缩得到的bitmap可能会比我需要的大。因此,还得改进方法!然后我们发现了Bitmap中的这个方法:

  1. Bitmap android.graphics.Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)

createScaledBitmap()可以给我们一个按照要求拉伸/缩小后的bitmap,我们可以通过这个方法把我们之前得到的较大的缩略图进行缩小,让其完全符合实际显示的大小。好了,现在我们开始写代码:

  1. /**
  2. * @description 计算图片的压缩比率
  3. *
  4. * @param options 参数
  5. * @param reqWidth 目标的宽度
  6. * @param reqHeight 目标的高度
  7. * @return
  8. */
  9. private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
  10. // 源图片的高度和宽度
  11. final int height = options.outHeight;
  12. final int width = options.outWidth;
  13. int inSampleSize = 1;
  14. if (height > reqHeight || width > reqWidth) {
  15. // 计算出实际宽高和目标宽高的比率
  16. final int halfHeight = height / 2;
  17. final int halfWidth = width / 2;
  18. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
  19. inSampleSize *= 2;
  20. }
  21. }
  22. return inSampleSize;
  23. }
  1. /**
  2. * @description 通过传入的bitmap,进行压缩,得到符合标准的bitmap
  3. *
  4. * @param src
  5. * @param dstWidth
  6. * @param dstHeight
  7. * @return
  8. */
  9. private static Bitmap createScaleBitmap(Bitmap src, int dstWidth, int dstHeight, int inSampleSize) {
       // 如果是放大图片,filter决定是否平滑,如果是缩小图片,filter无影响,我们这里是缩小图片,所以直接设置为false
  10. Bitmap dst = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false);
  11. if (src != dst) { // 如果没有缩放,那么不回收
  12. src.recycle(); // 释放Bitmap的native像素数组
  13. }
  14. return dst;
  15. }

现在我们就完成了如下的四个过程。

1. 使用inJustDecodeBounds,仅仅读bitmap的长和宽。
2. 根据bitmap的长款和目标缩略图的长和宽,计算出inSampleSize的大小。
3. 使用inSampleSize,载入一个比imageview大一点的缩略图A
4. 使用createScaseBitmap再次压缩A,将缩略图A生成我们需要的缩略图B。
5. 回收缩略图A(如果A和B的比率一样,就不回收A)。

  有朋友问,为啥要先从inSampleSize产生一个缩略图A,而不是直接把原始的bitmap通过createScaseBitmap()缩放为目标图片呢?

因为如果要从原始的bitmap直接进行缩放的话,就需要将原始图片放入内存中,十分危险!!!现在通过计算得到一个缩略图A,这个缩略图A比原图可以小了很多,完全可以直接加载到内存中,这样再进行拉伸就比较安全了。

四、产生工具类

现在已经把所有的技术难点攻克了,完全可以写一个工具类来产生缩略图。工具类中应该包含生成缩略图的重要方法:

  1. /**
  2. * @description 从Resources中加载图片
  3. *
  4. * @param res
  5. * @param resId
  6. * @param reqWidth
  7. * @param reqHeight
  8. * @return
  9. */
  10. public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
  11. final BitmapFactory.Options options = new BitmapFactory.Options();
  12. options.inJustDecodeBounds = true; // 设置成了true,不占用内存,只获取bitmap宽高
  13. BitmapFactory.decodeResource(res, resId, options); // 第一次解码,目的是:读取图片长宽
  14. options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 调用上面定义的方法计算inSampleSize值
  15. // 使用获取到的inSampleSize值再次解析图片
  16. options.inJustDecodeBounds = false;
  17. Bitmap src = BitmapFactory.decodeResource(res, resId, options); // 产生一个稍大的缩略图
  18. return createScaleBitmap(src, reqWidth, reqHeight, options.inSampleSize); // 通过得到的bitmap进一步产生目标大小的缩略图
  19. }
  20.  
  21. /**
  22. * @description 从SD卡上加载图片
  23. *
  24. * @param pathName
  25. * @param reqWidth
  26. * @param reqHeight
  27. * @return
  28. */
  29. public static Bitmap decodeSampledBitmapFromFile(String pathName, int reqWidth, int reqHeight) {
  30. final BitmapFactory.Options options = new BitmapFactory.Options();
  31. options.inJustDecodeBounds = true;
  32. BitmapFactory.decodeFile(pathName, options);
  33. options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
  34. options.inJustDecodeBounds = false;
  35. Bitmap src = BitmapFactory.decodeFile(pathName, options);
  36. return createScaleBitmap(src, reqWidth, reqHeight, options.inSampleSize);
  37. }

这两个方法的过程完全一致,产生一个option对象,设置inJustDecodeBounds参数,开始第一次解析bitmap,获得bitmap的宽高数据,然后通过宽高数据计算缩略图的比率。得到缩略图比率后,我们把inJustDecodeBounds设置为false,开始正式解析bitmap,最终得到的是一个可能比想要的缩略图略大的bitmap。最后一部是检查bitmap是否是我们想要的bitmap,如果是就返回,不是的话就开始拉伸,总之最终会返回一个完全符合imageview大小的bitmap,不浪费一点点内存。

完整的工具类代码如下:

  1. package com.kale.bitmaptest;
  2.  
  3. import android.content.res.Resources;
  4. import android.graphics.Bitmap;
  5. import android.graphics.BitmapFactory;
  6.  
  7. /**
  8. * @author:Jack Tony
  9. * @description :
  10. * @web :
  11. * http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
  12. * http://www.cnblogs.com/kobe8/p/3877125.html
  13. *
  14. * @date :2015年1月27日
  15. */
  16. public class BitmapUtils {
  17.  
  18. /**
  19. * @description 计算图片的压缩比率
  20. *
  21. * @param options 参数
  22. * @param reqWidth 目标的宽度
  23. * @param reqHeight 目标的高度
  24. * @return
  25. */
  26. private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
  27. // 源图片的高度和宽度
  28. final int height = options.outHeight;
  29. final int width = options.outWidth;
  30. int inSampleSize = 1;
  31. if (height > reqHeight || width > reqWidth) {
  32. final int halfHeight = height / 2;
  33. final int halfWidth = width / 2;
  34. // Calculate the largest inSampleSize value that is a power of 2 and keeps both
  35. // height and width larger than the requested height and width.
  36. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
  37. inSampleSize *= 2;
  38. }
  39. }
  40. return inSampleSize;
  41. }
  42.  
  43. /**
  44. * @description 通过传入的bitmap,进行压缩,得到符合标准的bitmap
  45. *
  46. * @param src
  47. * @param dstWidth
  48. * @param dstHeight
  49. * @return
  50. */
  51. private static Bitmap createScaleBitmap(Bitmap src, int dstWidth, int dstHeight, int inSampleSize) {
         // 如果是放大图片,filter决定是否平滑,如果是缩小图片,filter无影响,我们这里是缩小图片,所以直接设置为false
  52. Bitmap dst = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false);
  53. if (src != dst) { // 如果没有缩放,那么不回收
  54. src.recycle(); // 释放Bitmap的native像素数组
  55. }
  56. return dst;
  57. }
  58.  
  59. /**
  60. * @description 从Resources中加载图片
  61. *
  62. * @param res
  63. * @param resId
  64. * @param reqWidth
  65. * @param reqHeight
  66. * @return
  67. */
  68. public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
  69. final BitmapFactory.Options options = new BitmapFactory.Options();
  70. options.inJustDecodeBounds = true; // 设置成了true,不占用内存,只获取bitmap宽高
  71. BitmapFactory.decodeResource(res, resId, options); // 读取图片长宽,目的是得到图片的宽高
  72. options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 调用上面定义的方法计算inSampleSize值
  73. // 使用获取到的inSampleSize值再次解析图片
  74. options.inJustDecodeBounds = false;
  75. Bitmap src = BitmapFactory.decodeResource(res, resId, options); // 载入一个稍大的缩略图
  76. return createScaleBitmap(src, reqWidth, reqHeight, options.inSampleSize); // 通过得到的bitmap,进一步得到目标大小的缩略图
  77. }
  78.  
  79. /**
  80. * @description 从SD卡上加载图片
  81. *
  82. * @param pathName
  83. * @param reqWidth
  84. * @param reqHeight
  85. * @return
  86. */
  87. public static Bitmap decodeSampledBitmapFromFile(String pathName, int reqWidth, int reqHeight) {
  88. final BitmapFactory.Options options = new BitmapFactory.Options();
  89. options.inJustDecodeBounds = true;
  90. BitmapFactory.decodeFile(pathName, options);
  91. options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
  92. options.inJustDecodeBounds = false;
  93. Bitmap src = BitmapFactory.decodeFile(pathName, options);
  94. return createScaleBitmap(src, reqWidth, reqHeight, options.inSampleSize);
  95. }
  96. }

五、测试

我在布局中建立一个100x100dp的imageview,然后在res中放一张图片。

5.1 布局文件

放入两个按钮,一个按钮启动的是用常规的方法加载bitmap,不压缩;另一个启动压缩算法。

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:padding="16dp"
  6. tools:context="${relativePackage}.${activityClass}" >
  7.  
  8. <TextView
  9. android:id="@+id/textView1"
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:text="Bitmap Test" />
  13.  
  14. <ImageView
  15. android:id="@+id/imageView"
  16. android:layout_width="100dp"
  17. android:layout_height="100dp"
  18. android:layout_centerHorizontal="true"
  19. android:layout_centerVertical="true"
  20. android:scaleType="fitXY"
  21. android:src="@drawable/ic_launcher" />
  22.  
  23. <Button
  24. android:id="@+id/original_button"
  25. android:layout_width="wrap_content"
  26. android:layout_height="wrap_content"
  27. android:layout_alignParentBottom="true"
  28. android:onClick="butonListener"
  29. android:text="加载原图" />
  30.  
  31. <Button
  32. android:id="@+id/clip_button"
  33. android:layout_width="wrap_content"
  34. android:layout_height="wrap_content"
  35. android:layout_alignBaseline="@+id/original_button"
  36. android:layout_alignBottom="@+id/original_button"
  37. android:layout_alignParentRight="true"
  38. android:onClick="butonListener"
  39. android:text="加载缩略图" />
  40.  
  41. </RelativeLayout>

5.2 java代码

  1. public void butonListener(View v) {
  2. switch (v.getId()) {
  3. case R.id.original_button:
  4. loadBitmap(false); // 加载原图
  5. break;
  6.  
  7. case R.id.clip_button:
  8. loadBitmap(true); // 加载缩略图
  9. break;
  10. }
  11. }
  12.  
  13. public void loadBitmap(boolean exactable) {
  14. int bmSize = 0;
  15. Bitmap bm = null;
  16. if (exactable) {
  17. // 通过工具类来产生一个符合ImageView的缩略图,因为ImageView的大小是50x50,所以这里得到的缩略图也应该是一样大小的
  18. bm = BitmapUtils.decodeSampledBitmapFromResource(getResources(), R.drawable.saber, iv.getWidth(), iv.getHeight());
  19. } else {
  20. // 直接加载原图
  21. bm = BitmapFactory.decodeResource(getResources(), R.drawable.saber);
  22. }
  23. iv.setImageBitmap(bm);
  24. bmSize += bm.getByteCount(); // 得到bitmap的大小
  25. int kb = bmSize / 1024;
  26. int mb = kb / 1024;
  27. Toast.makeText(this, "bitmap size = " + mb + "MB" + kb + "KB", Toast.LENGTH_LONG).show();
  28. }

根据点击不同的按钮,触发不同的方法,最终把得到的bitmap放入imageview,并且显示当前的bitmap大小。运行后可以发现,经过压缩算法得到的bitmap要小很多,更加节约内存。

结果:原图:1M+;缩略图:156kb。

注意:当你的imageview远远小于bitmap原图大小的时候这种压缩算法十分有效,但是如果的bitmap和imageview大小差不多,你会发现这个算法的作用就不那么明显了,而且不要认为用了压缩就永远不会出现OOM了。

PS:实际使用中我们的bitmap经常是大于imageview的,所以推荐采用此方法。

源码下载:http://download.csdn.net/detail/shark0017/8402227

参考自:

http://www.cnblogs.com/kobe8/p/3877125.html

https://developer.android.com/training/displaying-bitmaps/load-bitmap.html

http://stormzhang.com/android/2013/11/20/android-display-bitmaps-efficiently/

根据ImageView的大小来压缩Bitmap,避免OOM的更多相关文章

  1. Android-根据ImageView的大小来压缩Bitmap,避免OOM

    本文转自:http://www.cnblogs.com/tianzhijiexian/p/4254110.html Bitmap是引起OOM的罪魁祸首之一,当我们从网络上下载图片的时候无法知道网络图片 ...

  2. (转)根据ImageView的大小来压缩Bitmap,避免OOM

    本文转载于:http://www.cnblogs.com/tianzhijiexian/p/4254110.html Bitmap是引起OOM的罪魁祸首之一,当我们从网络上下载图片的时候无法知道网络图 ...

  3. 【开源毕设】一款精美的家校互动APP分享——爱吖校推 [你关注的,我们才推](持续开源更新3)附高效动态压缩Bitmap

    一.写在前面 爱吖校推如同它的名字一样,是一款校园类信息推送交流平台,这么多的家校互动类软件,你选择了我,这是我的幸运.从第一次在博客园上写博客到现在,我一次一次地提高博文的质量和代码的可读性,都是为 ...

  4. 改变系统自带UITableViewCell的imageView的大小

    CGSize itemSize = CGSizeMake(, ); UIGraphicsBeginImageContextWithOptions(itemSize, NO,0.0); CGRect i ...

  5. X264库直接压缩BITMAP格式数据

    最近帮朋友看了下X264压缩视频,主要参考了雷霄骅(leixiaohua1020)的专栏的开源代码: http://blog.csdn.net/leixiaohua1020/article/detai ...

  6. android——获取ImageView上面显示的图片bitmap对象

    获取的函数方法为:Bitmap bitmap=imageView.getDrawingCache(); 但是如果只是这样写我们得到的bitmap对象可能为null值,正确的方式为: imageView ...

  7. JS通过指定大小来压缩图片

    安装: npm i image-conversion --save 引入: <script src="https://cdn.jsdelivr.net/gh/WangYuLue/ima ...

  8. 根据现有Bitmap生成相同图案指定大小的新Bitmap

    通过一张现有的Bitmap,画出一张同样的但是大小使我们指定的Bitmap 需求:直接createBitmap的话不允许生成的bitmap的宽高大于原始的,因此需要特定方法来将一张Bitmap的大小进 ...

  9. 关于android 使用bitmap的OOM心得和解决方式

    android开发,从2010年開始学习到如今的独立完毕一个app,这漫长的四年,已经经历了非常多次bug的折磨.无数次的加班训练.然而,自以为自己已经比較了解android了,却近期在一个项目上.由 ...

随机推荐

  1. 《Java多线程编程核心技术》学习笔记

    第2章 对象及变量的并发访问 2.1 synchronized同步方法 方法内的变量为线程安全: 方法内部的变量是线程私有的 方法中有一个变量num,后面对它赋值 两个线程同时调用这个方法,对其赋不同 ...

  2. 【LOJ】#2546. 「JSOI2018」潜入行动

    题解 dp[i][j][0/1][0/1]表示以\(i\)为根的子树,用了\(j\)个,i点选了或者没选,i点被覆盖或没被覆盖 转移比较显然,但是复杂度感觉不太对? 其实转移到100个的时候就使第二维 ...

  3. 安装m4,autoconf,automake

    ###安装m4 wget http://mirrors.kernel.org/gnu/m4/m4-1.4.13.tar.gz \ && tar -xzvf m4-1.4.13.tar. ...

  4. php 导入excel文件

    excel.php <?phprequire_once 'PHPExcel/PHPExcel.php';require_once 'PHPExcel/PHPExcel/IOFactory.php ...

  5. C#并行编程(4):基于任务的并行

    C#中的任务Task 在C#编程中,实现并行可以直接使用线程,但使用起来很繁琐:也可以使用线程池,线程池很大程度上简化了线程的使用,但是也有着一些局限,比如我们不知道作业什么时候完成,也取不到作业的返 ...

  6. 基于jquery的水平滚轴组件,多参数可设置。

    闲来无事,继续封装.此次封装的为水平滚轴组件,可选择滚动的距离大小.闲话不多说,直接上图. 参数说明: vis:4                中间区域可显示的 li 个数 scroll:4     ...

  7. odoo导入功能二开

    原来有的导入功能相信很多小伙伴对其功能不是很满意,不过没关系,我们可以二开啊,把它的功能改造成你想要的样子,接下来让我们看看怎么办吧 例如我想把员工导入功能中添加上用户同步注册功能 首先,我要找到原模 ...

  8. 处理javabean的JSP标签

    (1) 关于javabean要求: 1,具有无参的构造函数. 2,针对每一个成员变量,因改提供相应get/set. 3,implments Serializable(实现才能对象序列化). (2) 使 ...

  9. normalizr实践使用(个人总结,仅供参考)

    # normalizr实践使用 原数据 (自编数据,本数据仅供参考) var aaaObj ={ "id" : "0000000000000000000000000000 ...

  10. BZOJ.1031.[JSOI2007]字符加密(后缀数组)

    题目链接 环可以拆成链:对字符串排序能想到后缀数组. 完了.输出时忽略长度不足n的串,输出s[sa[i]+n-1],即排名为i的字符串的末尾. //4140kb 744ms #include < ...