最近一段时间的开发中和Bitmap接触较多,就Bitmap的使用有了一些新的认识,如何对Bitmap进行压缩,减少内存占用有了一些总结。

背景

社交类(或者说是包含用户系统)的APP基本上都会包含用户自定义头像的功能,可以让用户从相册选择或拍摄一张图片作为自己的头像,这样才能显现出每个人的个性嘛!每个用户的手机里各种各样不可描述的照片,从尺寸到大小各不相同,因此如何把用户选择的图片正确的加载到ImageView里就成了一件值得探讨的事情。好了,废话不说,下面就让我们一步步揭开Bitmap的神秘面纱。

从相册加载一张图片

我们先从简单的入手,看看从手机相册加载一张图片到ImageView的正确方式。

我们就以上图为列,这张图片在我手机里的信息如下:

可以看到,图片大小不足1M。那么把他加载到手机内存中时又会发生什么呢?

打开相册加载图片

    /*** 打开手机相册*/private void selectFromGalley() {Intent intent = new Intent();intent.setType("image/*");intent.setAction(Intent.ACTION_GET_CONTENT);intent.addCategory(Intent.CATEGORY_OPENABLE);startActivityForResult(intent, REQUEST_CODE_PICK_FROM_GALLEY);}

在Android 中打开相册是一件非常方便的事情,选择好图片之后就可以在onActivityResult中接收这张图片

                if (resultCode == Activity.RESULT_OK) {Uri uri = data.getData();if (uri != null) {ProcessResult(uri);}}

根据Uri得到Bitmap

@TargetApi(Build.VERSION_CODES.KITKAT)private void ProcessResult(Uri destUrl) {String pathName = FileHelper.stripFileProtocol(destUrl.toString());showBitmapInfos(pathName);Bitmap bitmap = BitmapFactory.decodeFile(pathName);if (bitmap != null) {mImageView.setImageBitmap(bitmap);float count = bitmap.getByteCount() / M_RATE;float all = bitmap.getAllocationByteCount() / M_RATE;String result = "这张图片占用内存大小:\n" +"bitmap.getByteCount()== " + count + "M\n" +"bitmap.getAllocationByteCount()= " + all + "M";info.setText(result);Log.e(TAG, result);bitmap = null;} else {T.showLToast(mContext, "fail");}}/*** 获取Bitmap的信息* @param pathName*/private void showBitmapInfos(String pathName) {BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFile(pathName, options);int width = options.outWidth;int height = options.outHeight;Log.e(TAG, "showBitmapInfos: \n" +"width=: " + width + "\n" +"height=: " + height);options.inJustDecodeBounds = false;}

这里的处理很简单,需要注意的一点是onActivityResult 方法中返回的Intent返回的图片地址是一个Uri类型,包含具体协议,为了方便使用BitmapFactory的decode方法,需要将这个个Uri类型的地址转换为普通的地址,stripFileProtocol具体实现可参考源码。

showBitmapInfos 这个方法就是很简单,就是获取一下所要加载图片的信息。这里主要还是靠inJustDecodeBounds 这个参数,当此参数为true时,BitmapFactory 只会解析图片的原始宽/高信息,并不会去真正的加载图片。

我们看一下输出日志及内存变化:

关于getByteCount和getAllocationByteCount的区别,这里暂时不讨论,只要知道他们都可以获取Bitmap占用内存大小

可以看到,由于这张图片是放在手机内部SD卡上,所以showBitmapInfos 解析后获取的图片宽高信息和之前是一致的,宽x高为 2160x1920。看到所占用的内存 15M,是不是有点意外,一张658KB 的加载后居然要占这么大的内存。在看一下monitor检测的内存变化,在20s后选择图片后,占用内存有了一个明显的上升。占用这么大的内存,显然是不好的。可能很多人和我一样,在这个时候想到的第一个词是压缩图片,把图片变小他占的内存不就会变小了吗?好,那就压缩图片

压缩图片

压缩图片方案一(Compress)

因为我们要处理的是Bitmap,首先从他自带的方法出发,果然找到了一个compress方法。

    private Bitmap getCompressedBitmap(Bitmap bitmap) {try {//创建一个用于存储压缩后Bitmap的文件File compressedFile = FileHelper.createFileByType(mContext, destType, "compressed");Uri uri = Uri.fromFile(compressedFile);OutputStream os = getContentResolver().openOutputStream(uri);Bitmap.CompressFormat format = destType == FileHelper.JPEG ?Bitmap.CompressFormat.JPEG : Bitmap.CompressFormat.PNG;boolean success = bitmap.compress(format, compressRate, os);if (success) {T.showLToast(mContext, "success");}final String pathName = FileHelper.stripFileProtocol(uri.toString());showBitmapInfos(pathName);bitmap = BitmapFactory.decodeFile(pathName);os.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return bitmap;}

bitmap.compress(format, compressRate, os) 会按照指定的格式和压缩比例将压缩后的bitmap写入到os 所对应的文件中。compressRate的取值在0-100之间,0表示压缩到最小尺寸。

在ProcessResult方法中,我们获取bitmap后,首先通过上述方法将bitmap压缩,然后在显示到ImageView中。我们看一下,压缩过后的情况。

上面的日志,第一个showBitmapInfos 显示的是选择的图片通过BitmapFactory解析后的信息,第二个showBitmapInfos
显示的压缩后图片的宽高信息,最后很意外,我们的压缩方法似乎没起到作用,占用的内存没有任何变化,依旧是15M。
难道是compress方法没生效吗?其实不然,至少从UI上看compress的确生效了, 当compressRate=0时,懒羊羊的图片显示到ImageView上时已经非常不清晰了,失真非常严重。那么到底是为什么呢?

这里就得从概念上说起,一开始我们提到了这张懒羊羊的图片大小时658KB,这是它在手机存储空间所占的大小,而当我们在选择这张图片,并解析为Bitmap时,他所站的15MB是在内存中所占的大小;而compress方法只能压缩前一种大小,也就是所使用Bitmap的compress方法只是压缩他在存储空间的大小,结果就是导致图片失真;而不能改变他在内存中所占用的大小

那么怎样才能让Bitmap所占用的内存变小呢?这就的从Bitmap占用内存的计算方法入手,在这篇文章中已经对bitmap所占用内存大小做了深入分析,从中我们可以得出结论,决定一张图片所占内存大小的因素是图片的宽高和Bitmap的格式。这里我们加载的时候对Bitmap格式未做更改,也就是默认的ARGB_8888,因此我们就得从宽高入手,得出如下的压缩方案。

压缩图片方案二 (Crop)

    private void CropTheImage(Uri imageUrl) {Intent cropIntent = new Intent("com.android.camera.action.CROP");cropIntent.setDataAndType(imageUrl, "image/*");cropIntent.putExtra("cropWidth", "true");cropIntent.putExtra("outputX", cropTargetWidth);cropIntent.putExtra("outputY", cropTargetHeight);File copyFile = FileHelper.createFileByType(mContext, destType, String.valueOf(System.currentTimeMillis()));copyUrl = Uri.fromFile(copyFile);cropIntent.putExtra("output", copyUrl);startActivityForResult(cropIntent, REQUEST_CODE_CROP_PIC);}

这里调用了系统自带的图片裁剪控件,并创建了一个copyFile 的文件,裁剪过后的图片的地址指向就是这个文件所对应的地址。
当cropTargetWidth=1080,cropTargetHeight=920时,我们看一下日志:

可以看到,Bitmap所占用的内存终于变小了,而且由于在裁剪时宽高各缩小了1/2,整个内存的占用也是缩小了1/4,变成了3.9M左右。同时图片在手机存储空间也变小了。

当然,这里要注意的是,com.android.camera.action.CROP 中两个参数 "outputX" 和"outputY",决定了压缩后图片的大小,因此当这两个值的大小超过原始图片的大小时,内存占用反而会增加,这一点应该很好理解,所以需确保传递合适的值,否则会适得其反。

图片压缩方案三 (Sample )

采用Sample,也就是是采样的方式压缩图片之前,我们首先需要了解一下inSampleSize 这个参数。

inSampleSize 是BitmapFactory.Options 的一个参数,当他为1时,采样后的图片大小为图片原始大小;当inSampleSize 为2时,那么采样后的图片其宽/高均为原图大小的1/2,而像素数为原图的1/4,其占有的内存大小也为原图的1/4。inSampleSize 的取值应该是2的指数。

    private Bitmap getRealCompressedBitmap(String pathName, int reqWidth, int reqHeight) {Bitmap bitmap;BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFile(pathName, options);int width = options.outWidth / 2;int height = options.outHeight / 2;int inSampleSize = 1;while (width / inSampleSize >= reqWidth && height / inSampleSize >= reqHeight) {inSampleSize = inSampleSize * 2;}options.inSampleSize = inSampleSize;options.inJustDecodeBounds = false;bitmap = BitmapFactory.decodeFile(pathName, options);showBitmapInfos(pathName);return bitmap;}

可以如下调用这个方法:

            if (needSample) {bitmap = getRealCompressedBitmap(pathName, 200, 200);}

我们希望将2160x1920像素的原图压缩到200x200 像素的大小,因此在getRealCompressedBitmap方法中,通过while循环inSampleSize的值最终为8,因此内存占用率将变为原来的1/64,这是一个很大的降幅。我们看一下日志,看看到底是否能够如我们所愿:

可以看到,使用这种方法进行图片压缩后,增加的内存只有0.24M,几乎可以忽略不计了。当然前提是我们要使用的图片的确不需要很大,比如这里,需要用这张图片作为用户头像的话,那么将原图缩略成200x200 px的大小是没有问题的。

三种方案对比

上面提到的三种压缩方案,通过对比可以发现,第一种方案适用于进行纯粹的文件压缩,而不适用进行图像处理压缩;第二种方案压缩方案适用于进行图像编辑时的压缩,就像手机自带相册的编辑功能,可以随着裁剪区域的大小进行最终的压缩;第三种方案相对来说,适应性较强,各种场景都会符合。

从Camera 获取Bitmap

有时候,我们除了从相册获取图片之外,还可以通过手机自带的相机拍摄图片。

    private void openCamera() {Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//创建一个临时文件夹存储拍摄的照片File file = FileHelper.createFileByType(mContext, destType, "test");imageUrl = Uri.fromFile(file);takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUrl);if (takePictureIntent.resolveActivity(getPackageManager()) != null) {startActivityForResult(takePictureIntent, REQUEST_CODE_TAKE_PIC_CAMERA);}}

不同于从相册选取图片,打开相机之前需要我们自己定义一个存储图片的临时文件file,这个临时文件既可以在应用的临时存储区也可以在手机存储的临时存储区;通过这个文件就可以生成一个Uri对象,有了这个Uri对象,相机拍摄完照片之后就可以在onActivityResult方法中通过这个Uri获取到Bitmap了。

这里我们可以试一下,随便用手机拍摄一张图片转为Bitmap加载会占多大的手机内存(以我用的小米手机5为列,拍摄一张图片):

可以看到这张图片的分辨率达到了3456x4608 像素,而他加载到内存是所占的大小居然达到了60M,这是非常不科学的做法,也是毫无意义的做法,因为我们的手机可见区域并没有这么大,将整张照片完全加载是没有意义的。因此可以按照之前的压缩方案进行压缩。

bitmap = getRealCompressedBitmap(pathName, screenWidth, screenHeight);

我们可以将原来的图片压缩到手机屏幕大小的图片

可以看到占用内存有了明显的减少。

将拍摄的图片添加到手机相册中

有时需要将拍摄出来的照片添加到手机相册中,方便从相册直接查看

    private void insertToGallery(Uri imageUrl) {Uri galleryUri = Uri.fromFile(new File(FileHelper.getPicutresPath(destType)));boolean result = FileHelper.copyResultToGalley(mContext, imageUrl, galleryUri);if (result) {Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);mediaScanIntent.setData(galleryUri);sendBroadcast(mediaScanIntent);}}

copyResultToGalley 方法的实现很简单,就是将imageUri 这个地址的文件复制到galleryUri 这个地址,复制成功后发送一条
action="ACTION_MEDIA_SCANNER_SCAN_FILE" 的广播即可。

好了,关于Bitmap的初探就说到这里,对于上面提到的各种压缩方案,有兴趣的同学可结合一下demo测试。Github 地址

总结

用了很久的ImageView,发现Bitmap才是Android中图像处理最核心的东西,有很多东西值得去深入了解。

作者:IAM四十二
链接:https://juejin.im/post/58bc1f11ac502e006b0957b7
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Android Bitmap 初探相关推荐

  1. (4.6.31)Android Bitmap 详解

    文章目录 一.从相册加载一张图片 1.1 打开相册加载图片 1.2 根据Uri得到Bitmap 二.Bitmap 内存计算方式 2.1 density 和 densityDpi 2.2 getByte ...

  2. 十九、Android Activity初探

    原文:十九.Android Activity初探 Activity是一个应用中的组件,它为用户提供一个可视的界面,方便用户操作,比如说拔打电话.照相.发邮件或者是浏览地图等.每个activity会提供 ...

  3. Android Bitmap转换WebP图片导致损坏的分析及解决方案

    Android Bitmap转换WebP图片导致损坏的分析及解决方案 参考文章: (1)Android Bitmap转换WebP图片导致损坏的分析及解决方案 (2)https://www.cnblog ...

  4. Android bitmap图片处理

    一.View转换为Bitmap         在Android中所有的控件都是View的直接子类或者间接子类,通过它们可以组成丰富的UI界面.在窗口显示的时候Android会把这些控件都加载到内存中 ...

  5. Android Activity初探

    原地址:Android Activity初探 Activity是一个应用中的组件,它为用户提供一个可视的界面,方便用户操作,比如说拔打电话.照相.发邮件或者是浏览地图等.每个activity会提供一个 ...

  6. android插件框架机制的选择,Android插件开发初探——基础篇

    Android插件开发初探 对于Android的插件化其实已经讨论已久了,但是市面上还没有非常靠谱成熟的插件框架供我们使用.这里我们就尝试性的对比一下Java中,我们使用插件化该是一个怎么样的流程,且 ...

  7. Android Bitmap 研究与思考(上篇)

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/107951273 本文出自[赵彦军的博客] 做Android 6年来,一直都没有对 ...

  8. Android—Bitmap图片大小计算、压缩与三级缓存

    Bitmap对象占用内存大小: bitmap.getByteCount() 图片所占内存大小计算方式:图片长度 x 图片宽度 x 一个像素点占用的字节数. Android Bitmap使用的三种颜色格 ...

  9. android bitmap string,Android Bitmap到Base64字符串(Android Bitmap to Base64 String)

    Android Bitmap到Base64字符串(Android Bitmap to Base64 String) 如何将一个大的Bitmap(用手机相机拍摄的照片)转换为Base64 String? ...

最新文章

  1. Python如何使用不同分隔符切分字符串
  2. 用c语言实现存储和读取图片文件,C++实现单张图片读取和保存
  3. FCN网络训练 SIFTFLOW数据集
  4. 【计算机网络】整体体系结构
  5. (原创) 看电影 源代码 有感——量子力学的玄妙
  6. 数据结构之图:用图解决案例,Python代码实现——24
  7. Docker上部署WebERP系统,开源ERP框架
  8. 如何安装mysql 5.6_如何通过编译工具安装mysql 5.6
  9. IE、firefox下怎样获得自定义属性的值
  10. java 事件cancel_Activiti结束事件(End Event)
  11. 纯CSS3浮雕质感的立体文字旋转动画
  12. 拓端tecdat|卡尔曼滤波器:用R语言中的KFAS建模时间序列
  13. 移动端微博 php源码,jQuery仿手机新浪微博聊天界面
  14. vnc远程控制linux密码,如何使用VNC远程控制Linux(Centos)?
  15. ms office word2013教程 - 文字处理之插入复合条饼图
  16. ORA-00979 不是 GROUP BY 表达式
  17. 调整Android音量等级及默认音量
  18. 格兰杰因果 Granger causality
  19. Spring MVC参数化测试 - Junit Parameterized
  20. 微信公众号监听 关注/取消关注事件 消息接收与响应处理(比较细微)

热门文章

  1. 富满电子鸿蒙系统,东吴证券--电子行业周报:HarmonyOS2.0助力AIOT生态体系发展
  2. 系统规划与管理备考整理
  3. 猜谜看照片 凤凰、张家界六人五日游照片放出!
  4. 精致生活用瞬彩定格 佳能打印新品应用体验会在沪举行
  5. 资源分享 | WSDM2020推荐系统论文打包下载
  6. jQuery.print.js 下载 打印机打印
  7. PDFMarker文件遗失,要在修复模式下运行安装程序吗
  8. 运动耳机品牌排行榜前十名,目前最好的六款运动耳机推荐
  9. 国产高端手机拥有了杀手锏
  10. Amazing!你的超级大礼包已送出,请注意查收!