我们知道在app处理图片是经常会出现oom,原因就是我们在处理图片的时候图片所占的内存太大导致的,这里就介绍怎么去结局图片占内存过大的方法,当然,也是为了自己以后使用能方便些。
这篇文章也是在网上看了很多大牛的文章之后自己整理出来的,希望有错的地方大家提出来,一起学习一起进步。
首先我们介绍下相机的使用:
大致有三种:
1. 缩略图
2. 获得原始的拍照文件
3. 获取Gallery里面的图片
第一种: 缩略图
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(intent, 1);
第二种:获得原始的拍照文件
public void OriginalImageView(View view) {
File file = createImageFile();
outputFileUri = Uri.fromFile(file);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
startActivityForResult(intent, 2);
}
第三种:获取Gallery里面的图片
public void GalleryImageView(View view) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(intent, 3);
}
上面只是简单介绍照相,下面是所有的代码:
public class MainActivity extends AppCompatActivity {
private ImageView imageview;
private Uri outputFileUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageview = (ImageView) findViewById(R.id.imageview);
}
/** * 缩略图 */
public void PriviewImageView(View view) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, 1);
}
/** * 获得原始的拍照文件 */
public void OriginalImageView(View view) {
File file = createImageFile();
outputFileUri = Uri.fromFile(file);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
startActivityForResult(intent, 2);
}
/** * 获取Gallery里面的图片 */
public void GalleryImageView(View view) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(intent, 3);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != RESULT_OK) {
if (requestCode == 1) {
/**缩略图*/
Bitmap bitmap = data.getExtras().getParcelable("data");
imageview.setImageBitmap(bitmap);
} else if (requestCode == 2) { /**获得原始的拍照文件*/
Toast.makeText(MainActivity.this, "成功", Toast.LENGTH_SHORT).show();
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), outputFileUri);
imageview.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
} else if (requestCode == 3) {
/**获取Gallery里面的图片*/
Toast.makeText(MainActivity.this, data.getData().toString(), Toast.LENGTH_SHORT).show();
getRealPathFromURI(data.getData());/**获取图片真实路径*/
}
}
}
public static File createImageFile() {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
try {
File image = File.createTempFile(imageFileName, ".jpg", Environment.getExternalStorageDirectory());
return image;
} catch (IOException e) {
return null;
}
}
private String getRealPathFromURI(Uri contentURI) {
String result;
Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
if (cursor == null) {
result = contentURI.getPath();
} else {
cursor.moveToFirst();
int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
result = cursor.getString(idx);
cursor.close();
}
return result;
}
}
其次,在处理图片之前,我们应该了解BitMap和BitmapFactory的一些常用方法,在这里我简单介绍几种常用的。
BitMap:
1.计算图片像素的方法是 bitmap.getWidth()和bitmap.getHeight();
2.bitmap.getByteCount() 是计算它的像素所占用的内存,(bitmap.getWidth()*bitmap.getHeight()*4=bitmap.getByteCount());
3.compress(Bitmap.CompressFormat.JPEG, 100, os),第一个参数是图片的类型,第二个参数是指图片质量(设置为30时是指图片质量被压缩了百分之70,最大100,最小0),第三个参数是指图片流。
BitmapFactory:
1.BitmapFactory.Options使用这个对象去处理图片的属性;
2.inJustDecodeBounds如果是true的话只会返回图片的宽和高;
3.inSampleSize是指设置缩放比例,设置为2时变为原来的1/2(这里的1/2值得是宽和高分别是原图的1/2,所以图片是被压缩了4倍);
4.BitmapFactory.decodeFile(path,options),这个方法的path是图片的路径,options就是BitmapFactory.Options的对象。
5.BitmapFactory.decodeFile(path),返回的是这个路径下图片的Bitmap;
6.BitmapFactory.decodeStream(is, null, options),这个方法is是图片的流,null 是指位图,我们可以设置为nullm,options就是BitmapFactory.Options的对象。
7.BitmapFactory.decodeStream(is),将is转化为BitMap对象;
图片压缩有两种方法,一种是质量压缩((不改变图片的尺寸)),一种是尺寸压缩(像素上的压缩也成采样率压缩)。
测试图片在压缩前和压缩后的大小:
FileInputStream fs = new FileInputStream(pathName);
System.out.println(“图片的大小==”+fs.available());
测试图片所占内存大小:
Bitmap bm = BitmapFactory.decodeFile(pathName);;
System.out.println(“内存的大小==”+bm.getByteCount());
System.out.println(“内存大小的另一种求法==”+bm.getWidth() * bm.getHeight()*4 );
- 质量压缩
质量压缩不会减少图片对内存的使用,但是开发app的程序员都知道我们会尽量为用户减少流量,或者将图片保存到本地时进行压缩,所以使用质量压缩会减少图片的大小,这样上传图片时速度快(因为图片变小了),用户体验就会好。
下面就是方法:
FileInputStream fs = new FileInputStream(pathName);
System.out.println("图片压缩前的大小=="+fs.available());
public static Bitmap CompressBitmap(Bitmap bitmap){
System.out.println("质量压缩前内存="+bitmap.getByteCount());
ByteArrayOutputStream bo=new ByteArrayOutputStream();
//通过这里改变压缩类型,其有不同的结果
bitmap.compress(Bitmap.CompressFormat.JPEG, 70, bo); //注意:不要讲jpg格式的图片压缩成png格式的图片,如果设置成png的话图片可能会变大,但是内存不会发生变化
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
System.out.println("图片压缩后的大小="+bi.available());
Bitmap bitmap=BitmapFactory.decodeStream(bis);
System.out.println("质量压缩后内存="+bitmap.getByteCount());
return bitmap;
}
通过实现我们得出图片压缩前和压缩后的大小确实变了,压缩后的比压缩前的小,但是图片的内存使用没有变。
但是,它不会减少图片的像素。它是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的。进过它压缩的图片文件大小会有改变,但是导入成bitmap后占得内存是不变的。因为要保持像素不变,所以它就无法无限压缩,到达一个值之后就不会继续变小了。显然这个方法并不适用与缩略图,其实也不适用于想通过压缩图片减少内存的适用,仅仅适用于想在保证图片质量的同时减少文件大小的情况而已
例如:
//最开始使用这个来进行压缩,但是始终压缩不到32k这么小
public void Example(){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100 , baos);
int options = 100 ;
while ( baos.toByteArray().length / 1024 > 32 ) {
baos.reset();
image.compress(Bitmap.CompressFormat.JPEG, options, baos);
options -= 10 ;
}
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null , null );
}
2.尺寸压缩(也成采样率压缩)
尺寸压缩会改变图片所占内存的大小,也就是解决我们在加载图片时有时候会出现的OOM。
第一种: 根据图片路径,下面就是方法:
public static Bitmap CompressBitmapSizePath(String imagePath){
Bitmap bitmap = BitmapFactory.decodeFile(pathName);
System.out.println("内存压缩前=="+bitmap.getByteCount());
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; //设置成true我们我是为了得到图片的宽高,这个方法返回的Bitmap对象为空,减少内存的消耗
Bitmap bitmap = BitmapFactory.decodeFile(imagePath,options); // 这个方法返回的bitmap 为空,因为options.inJustDecodeBounds设置为true
options.inJustDecodeBounds = false;
options.inSampleSize = 2; //原来的1/2
bitmap = BitmapFactory.decodeFile(imagePath,options);
System.out.println("内存压缩后=="+bitmap.getByteCount());
return bitmap;
}
第二种:根据流,下面就是方法:
public static Bitmap CompressBitmapSizeIs(Context context, int resId){
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
opt.inSampleSize = 2;
//获取资源图片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is,null,opt);
}
第三中:根据Bitmap,以下就是方法:
public Bitmap CompressBitmapSizeBitMap(Bitmap image, float pixelW, float pixelH) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, os);
if( os.toByteArray().length / 1024>1024) {
//判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
os.reset();//重置baos即清空baos
image.compress(Bitmap.CompressFormat.JPEG, 50, os);//这里压缩50%,把压缩后的数据存放到baos中
}
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
BitmapFactory.Options newOpts = new BitmapFactory.Options();
//开始读入图片,此时把options.inJustDecodeBounds 设回true了
newOpts.inJustDecodeBounds = true;
newOpts.inPreferredConfig = Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeStream(is, null, newOpts);
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
float hh = pixelH;// 设置高度为240f时,可以明显看到图片缩小了
float ww = pixelW;// 设置宽度为120f,可以明显看到图片缩小了
//缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
int be = 1;//be=1表示不缩放
if (w > h && w > ww) {
//如果宽度大的话根据宽度固定大小缩放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {
//如果高度高的话根据宽度固定大小缩放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0) be = 1;
newOpts.inSampleSize = be;//设置缩放比例
//重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
is = new ByteArrayInputStream(os.toByteArray());
bitmap = BitmapFactory.decodeStream(is, null, newOpts);
//压缩好比例大小后再进行质量压缩
// return compress(bitmap, maxSize); // 这里再进行质量压缩的意义不大,反而耗资源,删除
return bitmap;
}
使用中可以根据自己的需要自己选择,最后给大家提供一个完美的压缩图片尺寸的方法,也是我在网上找到的:
//根据路径获得突破并压缩返回bitmap用于显示
public static Bitmap getSmallBitmap(String filePath,int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; //只返回图片的大小信息
BitmapFactory.decodeFile(filePath, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filePath, options);
}
//计算图片的缩放值
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int heightRatio = Math.round((float) height/ (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
最后,使用完BitMap是我们一般要:
bmp.recycle() ; //回收图片所占的内存
system.gc() //提醒系统及时回收 ,回收内存。
另外,一般不要不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存,以下提供一个优化方法(也就是尺寸压缩中的第二个方法,只是一个优化):
public static Bitmap readBitMap(Context context, int resId){
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
opt.inSampleSize = 2;
//获取资源图片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is,null,opt);
}