Android开发中解析、创建Bitmap对象时OOM的有效解决方法并附上一些干货
2016-06-01 19:15
423 查看
先来点鸡汤:
Stay hungry,stay foolish
这句话的的解读:我们必须了解自己的渺小。如果我们不学习,科技发展的速度会让我们五年后被清空。所以,我们必须用初学者谦虚的自觉,饥饿者渴望的求知态度,来拥抱未来的知识。
这几天做的项目中需要从图库选择图片或者拍照生成图片,然后展现在IamgeView控件上。当然,从图库选择图片和拍照选择图片的功能实现起来很简单。直接写上代码:
以上代码很简单,就是创建一个对话框,有两个item选项。一个是拍照、一个是图库。如下所示:
两个item选项的功能都是为了生成一张图片并设置到ImageView控件上。
拍照和从图库选择图片都先创建了一个File对象,用于存储摄像头拍下的图片或者存储从图库中选择的图片。
然后将它放在手机的根目录下,我的手机是在内存设备的根目录下,这个无所谓。然后调用Uri的fromFile()方法将File对象转换成Uri对象。这个Uri对象标识着图片的唯一地址。
如果点击的是拍照,接着会构建出一个Intent对象,并将这个Intent的action指定为:MediaStore.ACTION_IMAGE_CAPTURE,再调用Intent的putExtra()方法指定图片的输出地址,这里就填入刚刚得到的Uri对象,最后调用startActivityForResult()来启动活动。
因为上面的代码是使用startActivityForResult()来启动活动的,所以拍照成功后会回调onActivityResult()方法,也即拍完照后会有结果返回到onActivityResult()方法中。所以重写该方法就能对拍照后的图片做进一步的处理了。方法如下所示:
上面说到如果用摄像头拍照成功后会回调onActivityResult()方法,这时候会继续构建Intent对象,把它的action指定为:
“com.android.camera.action.CROP”。
这个Intent是用于拍出的照片进行裁剪的。因为摄像头比较大,而我们可能只希望截取其中的一小部分。然后给这个Intent设置上一些必要的属性,并再调用startActivityForResult()来启动裁剪程序。裁剪后的图片同样会输出到手机根目录下的图片文件中。
===========================================
如果点击的item选项是图库。那么会调用系统的图库选择图片。在这里,跟拍照时基本的操作没有什么太多的差别。
都是先创建一个File对象,保存从图库选择的文件。然后构建出一个Intent对象,并将它的action指定为:“android.intent.action.GET_CONTENT”。接着给这个Intent对象设置一些必要的参数,包括是否允许缩放和裁剪、图片的输出位置等。最后调用startActivityForResult()方法,就可以打开相册程序选择照片了。
这里用到了一个小技巧,就是我们在调用startActivityForResult()方法的时候,给第二个参数传入的值仍然是:CROP_PHOTO常量,这样就可以复用之前调用摄像头显示图片的逻辑了,不用再编写第二遍显示图片的逻辑。
好了,以上的两个item选项的操作都讲完了,接下来就是关键的时刻了。
裁剪操作完成后,程序又会回调到onActivityResult方法中,这个时候就可以利用BItmapFactory的decodeStream()方法将存储在手机根目录下的图片文件解析成Bitmap对象,然后把它设置到ImageView控件上显示出来。
我在这里用的是gridView控件显示一些图片。不过都是一样的,在gridView设置adapter的时候,需要在自定义的Adapter中把Bitmap对象设置到ImageView控件上。一开始设置一两张图片的时候如下所示:
但是放的图的一旦多一点,程序就直接崩溃了,看了一下logcat的错误日志。
一个醒目的Java.lang.OutOfMemoryError(内存溢出错误),最不想遇见的错误。说心里话,蛋疼。困惑了有段时间,试了很多的方法,终于很好的做了一些有效的图片压缩方法优化内存溢出的问题,但是并不能彻底的解决内存溢出问题。所以有时候我们自己要对图片的张数做一些限制。像钉钉的出差管理的图片选择最多允许选择9张、微信发朋友圈发图片最多允许9张。有时候需要一些限制才能解决一些无法避免的问题。
上面的发生内存溢出错误的代码定位了一下,直接就定位到下面这一行,如下图所示:
该行代码如下:
bitmap=BitmapFactory.decodeStream(getActivity().getContentResolver().openInputStream(imageUri));
为什么这行代码会导致OOM呢?给大家讲讲:该行代码的作用是用于从指定输入流中解析、创建Bitmap对象。但是由于手机系统的内存比较小,如果不停的去解析、创建Bitmap对象,可能由于前面所创建的Bitmap所占用的内存还没有回收,而导致程序运行时引发OutOfMemory错误。所以我们需要将Bitmap对象先进行压缩,使用的时候可以降低对内存的占用,这样就可以有效的解决这个问题。我的解决方法是将以上的代码替换为下图所示的代码:
上图所示的各行代码解释如下:
======
该行代码用来创建一个BitmapFactory.Options对象,且Options 只保存图片尺寸大小,不保存图片到内存。
======
该行代码是最关键的代码。给大家点进去看一下源码,源码如下所示:
源码的注释给大家讲讲。
注释说:如果该值设置为>1,就会请求解析器对原图做二次抽样,即二次解析,返回一个较小的图片用来节省内存。二次抽样样本大小的像素尺寸,对应于一个解码位图的像素。举个例子,如果inSampleSize == 4,会返回一个是原图1/4高度和1/4宽度的图像,和1/16像素的数量。对任何值< = 1的值都用=1来赋值。
以上源码的注释很好的说明了该行代码的关键性。大家要有效避免OOM错误的话一定要记得加上哦!只有这样,才能有效的节省Bitmap对象占用的内存。别忘记了!
======
该行代码是附加上图片的Config参数,解析器或根据当前的参数配置进行对应的解析,这也可以有效减少加载的内存。
======
该行代码作用:由此产生的位图将分配它的像素,这样他们可以被净化系统需要回收的内存。
======
该行代码的作用:inInputShareable属性和inPurgeable 有关,当inPurgeable 属性设置为false的时候,inInputShareable属性就可以忽略。但是如果这两个属性都设置为true的时候,源码的注释这样说:确定位图可以共享一个参考输入数据如果它必须深拷贝的话。好了这都不是什么关键的,不设置也是可以的。
======
最后调用方法:
该行代码就是根据options的一些选项设置解析输入流返回一个压缩后的Bitmap对象。这时候使用bitmap的时候就可以有效的避免OOM了。下面附上我的项目的测试图片给大家看,眼见为实嘛!如下图:
好了,没有报OOM的错误,9张图片都成功的呈现在界面上。(爆料一下,中间那张图片是我的哦!不要羡慕哥,有时间记得锻炼!你也可以有我的身材! :) )
===========================================
就先说到这里,以后会遇到更多实际开发的小问题,到时候继续学习进步并把一些解决方案分享给大家。
好了说了这么久了。干货上场了!
给大家几点程序员摆脱疲劳的方法(博客上写的,但是我自己认为不错的):
链接地址是:程序员摆脱疲劳的方法
Stay hungry,stay foolish
这句话的的解读:我们必须了解自己的渺小。如果我们不学习,科技发展的速度会让我们五年后被清空。所以,我们必须用初学者谦虚的自觉,饥饿者渴望的求知态度,来拥抱未来的知识。
这几天做的项目中需要从图库选择图片或者拍照生成图片,然后展现在IamgeView控件上。当然,从图库选择图片和拍照选择图片的功能实现起来很简单。直接写上代码:
CharSequence[] items = { "拍照", "图库" }; new AlertDialog.Builder(context).setTitle("请选择:") .setIcon(R.drawable.ic_choose_picture) .setItems(items, new OnClickListener() { public void onClick(DialogInterface dialog,int which) { switch (which) { case 0: // 调用拍照功能 // 创建File对象,用于存储拍照后的对象 takePhotoImage = new File(Environment.getExternalStorageDirectory(),"take_photo_image.jpg"); try { if (takePhotoImage.exists()) { takePhotoImage.delete(); } takePhotoImage.createNewFile(); } catch (Exception e) { e.printStackTrace(); } // 将File对象转换成Uri对象 imageUri =Uri.fromFile(takePhotoImage); Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // 指定图片的输出地址 intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); startActivityForResult(intent, TAKE_PHOTO); break; case 1: // 调用系统图库 // 创建File对象,用于存储选择图库后的图片 File choosePhoto = new File(Environment.getExternalStorageDirectory(),"choose_photo.jpg"); try { if (choosePhoto.exists()) { choosePhoto.delete(); } choosePhoto.createNewFile(); } catch (Exception e) { e.printStackTrace(); } // 将File对象转换成Uri对象 imageUri = Uri.fromFile(choosePhoto); Intent intent2 = new Intent("android.intent.action.GET_CONTENT"); intent2.setType("image/*"); intent2.putExtra("crop", "true"); intent2.putExtra("scale", true); intent2.putExtra(MediaStore.EXTRA_OUTPUT,imageUri); startActivityForResult(intent2, CROP_PHOTO); break; } } }).show();
以上代码很简单,就是创建一个对话框,有两个item选项。一个是拍照、一个是图库。如下所示:
两个item选项的功能都是为了生成一张图片并设置到ImageView控件上。
拍照和从图库选择图片都先创建了一个File对象,用于存储摄像头拍下的图片或者存储从图库中选择的图片。
然后将它放在手机的根目录下,我的手机是在内存设备的根目录下,这个无所谓。然后调用Uri的fromFile()方法将File对象转换成Uri对象。这个Uri对象标识着图片的唯一地址。
如果点击的是拍照,接着会构建出一个Intent对象,并将这个Intent的action指定为:MediaStore.ACTION_IMAGE_CAPTURE,再调用Intent的putExtra()方法指定图片的输出地址,这里就填入刚刚得到的Uri对象,最后调用startActivityForResult()来启动活动。
因为上面的代码是使用startActivityForResult()来启动活动的,所以拍照成功后会回调onActivityResult()方法,也即拍完照后会有结果返回到onActivityResult()方法中。所以重写该方法就能对拍照后的图片做进一步的处理了。方法如下所示:
/** * 在调用startActivityForResult的时候会回调该方法 */ @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { // 拍照 case TAKE_PHOTO: if (resultCode == Activity.RESULT_OK) { Intent intent = new Intent("com.android.camera.action.CROP"); intent.setDataAndType(imageUri, "image/*"); intent.putExtra("scale", true); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); startActivityForResult(intent, CROP_PHOTO); } break; // 裁剪图片 case CROP_PHOTO: if (resultCode == Activity.RESULT_OK) { try { // 压缩Bitmap对象 BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 8; options.inPreferredConfig = Bitmap.Config.RGB_565; options.inPurgeable = true; options.inInputShareable = true; bitmap=BitmapFactory.decodeStream(getActivity().getContentResolver().openInputStream(imageUri),null, options); setImageData(bitmap); if (bitmapEntities.size() == 1) { adapter = new BitmapAdapter(context, bitmapEntities); gv_Picture.setAdapter(adapter); } else { adapter.notifyDataSetChanged(); } linear_Picture.addView(view_Picture, 1); } catch (Exception e) { e.printStackTrace(); } } break; } }
上面说到如果用摄像头拍照成功后会回调onActivityResult()方法,这时候会继续构建Intent对象,把它的action指定为:
“com.android.camera.action.CROP”。
这个Intent是用于拍出的照片进行裁剪的。因为摄像头比较大,而我们可能只希望截取其中的一小部分。然后给这个Intent设置上一些必要的属性,并再调用startActivityForResult()来启动裁剪程序。裁剪后的图片同样会输出到手机根目录下的图片文件中。
===========================================
如果点击的item选项是图库。那么会调用系统的图库选择图片。在这里,跟拍照时基本的操作没有什么太多的差别。
都是先创建一个File对象,保存从图库选择的文件。然后构建出一个Intent对象,并将它的action指定为:“android.intent.action.GET_CONTENT”。接着给这个Intent对象设置一些必要的参数,包括是否允许缩放和裁剪、图片的输出位置等。最后调用startActivityForResult()方法,就可以打开相册程序选择照片了。
这里用到了一个小技巧,就是我们在调用startActivityForResult()方法的时候,给第二个参数传入的值仍然是:CROP_PHOTO常量,这样就可以复用之前调用摄像头显示图片的逻辑了,不用再编写第二遍显示图片的逻辑。
好了,以上的两个item选项的操作都讲完了,接下来就是关键的时刻了。
裁剪操作完成后,程序又会回调到onActivityResult方法中,这个时候就可以利用BItmapFactory的decodeStream()方法将存储在手机根目录下的图片文件解析成Bitmap对象,然后把它设置到ImageView控件上显示出来。
我在这里用的是gridView控件显示一些图片。不过都是一样的,在gridView设置adapter的时候,需要在自定义的Adapter中把Bitmap对象设置到ImageView控件上。一开始设置一两张图片的时候如下所示:
但是放的图的一旦多一点,程序就直接崩溃了,看了一下logcat的错误日志。
一个醒目的Java.lang.OutOfMemoryError(内存溢出错误),最不想遇见的错误。说心里话,蛋疼。困惑了有段时间,试了很多的方法,终于很好的做了一些有效的图片压缩方法优化内存溢出的问题,但是并不能彻底的解决内存溢出问题。所以有时候我们自己要对图片的张数做一些限制。像钉钉的出差管理的图片选择最多允许选择9张、微信发朋友圈发图片最多允许9张。有时候需要一些限制才能解决一些无法避免的问题。
上面的发生内存溢出错误的代码定位了一下,直接就定位到下面这一行,如下图所示:
该行代码如下:
bitmap=BitmapFactory.decodeStream(getActivity().getContentResolver().openInputStream(imageUri));
为什么这行代码会导致OOM呢?给大家讲讲:该行代码的作用是用于从指定输入流中解析、创建Bitmap对象。但是由于手机系统的内存比较小,如果不停的去解析、创建Bitmap对象,可能由于前面所创建的Bitmap所占用的内存还没有回收,而导致程序运行时引发OutOfMemory错误。所以我们需要将Bitmap对象先进行压缩,使用的时候可以降低对内存的占用,这样就可以有效的解决这个问题。我的解决方法是将以上的代码替换为下图所示的代码:
上图所示的各行代码解释如下:
======
BitmapFactory.Options options = new BitmapFactory.Options();
该行代码用来创建一个BitmapFactory.Options对象,且Options 只保存图片尺寸大小,不保存图片到内存。
======
options.inSampleSize = 8;
该行代码是最关键的代码。给大家点进去看一下源码,源码如下所示:
源码的注释给大家讲讲。
注释说:如果该值设置为>1,就会请求解析器对原图做二次抽样,即二次解析,返回一个较小的图片用来节省内存。二次抽样样本大小的像素尺寸,对应于一个解码位图的像素。举个例子,如果inSampleSize == 4,会返回一个是原图1/4高度和1/4宽度的图像,和1/16像素的数量。对任何值< = 1的值都用=1来赋值。
以上源码的注释很好的说明了该行代码的关键性。大家要有效避免OOM错误的话一定要记得加上哦!只有这样,才能有效的节省Bitmap对象占用的内存。别忘记了!
======
options.inPreferredConfig = Bitmap.Config.RGB_565;
该行代码是附加上图片的Config参数,解析器或根据当前的参数配置进行对应的解析,这也可以有效减少加载的内存。
======
options.inPurgeable = true;
该行代码作用:由此产生的位图将分配它的像素,这样他们可以被净化系统需要回收的内存。
======
options.inInputShareable = true;
该行代码的作用:inInputShareable属性和inPurgeable 有关,当inPurgeable 属性设置为false的时候,inInputShareable属性就可以忽略。但是如果这两个属性都设置为true的时候,源码的注释这样说:确定位图可以共享一个参考输入数据如果它必须深拷贝的话。好了这都不是什么关键的,不设置也是可以的。
======
最后调用方法:
bitmap=BitmapFactory.decodeStream(getActivity().getContentResolver().openInputStream(imageUri),null, options);
该行代码就是根据options的一些选项设置解析输入流返回一个压缩后的Bitmap对象。这时候使用bitmap的时候就可以有效的避免OOM了。下面附上我的项目的测试图片给大家看,眼见为实嘛!如下图:
好了,没有报OOM的错误,9张图片都成功的呈现在界面上。(爆料一下,中间那张图片是我的哦!不要羡慕哥,有时间记得锻炼!你也可以有我的身材! :) )
===========================================
就先说到这里,以后会遇到更多实际开发的小问题,到时候继续学习进步并把一些解决方案分享给大家。
好了说了这么久了。干货上场了!
给大家几点程序员摆脱疲劳的方法(博客上写的,但是我自己认为不错的):
链接地址是:程序员摆脱疲劳的方法
每天进步一点点!加油!
相关文章推荐
- Android实现表情 抓取新浪表情
- 详解Android解析Xml的三种方式——DOM、SAX以及XMLpull
- C#实现位图转换成图标的方法
- C++实现位图排序实例
- android开发环境遇到adt无法启动的问题分析及解决方法
- Android开发 旋转屏幕导致Activity重建解决方法
- Android开发技巧之在a标签或TextView控件中单击链接弹出Activity(自定义动作)
- Android开发技巧之ViewStub控件惰性装载
- C语言实现的bitmap位图代码分享
- Android开发常用经典代码段集锦
- 分享10个很棒的学习Android开发的网站
- android开发之横向滚动/竖向滚动的ListView(固定列头)
- Android传递Bitmap对象在两个Activity之间
- 数据结构之位图(bitmap)详解
- Android开发技巧之我的菜单我做主(自定义菜单)
- Windows下使用Apache Cordova开发ANDROID之HelloWorld
- 转:抗锯齿方法两种(其一:paint.setAntiAlias(ture);paint.setBitmapFilter(true))
- 位图索引 btree索引
- 根据根据图片的url怎么取得图片ImageView对象
- [转] ADB server didn't ACK