本篇实现功能主要是通过app起调系统相机拍照剪裁后返回app内存优化
先说下遇到的问题:以前实现相机拍照由于图片过大几张以后很容易导致OOM,
这里说一下实现:
启动相机拍照有两种方法获取拍照后的图片数据:
$1: 直接启动相机拍照,通过onActivityForResult 的Bundle接受拍照数据
Intent i = new Intent("android.media.action.IMAGE_CAPTURE");
startActivityForResult(i, 1000);
@Override
protected void onActivityResult(int requestCode, int resultCode,Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
switch(resultCode)
{
case 1000:
if(data!=null){
Bundle extras = data.getExtras();
Bitmap bmp = (Bitmap) extras.get("data");
// TODO
}
// TODO
}
但是有个问题:因为android系统为每个app分配的的内存都是有限,通过bundle获取的数据 适用于小数据图片,但是超过一定的大小之后次方法就不在适用。(经测试同时有些手机拍照后data依然为空是获取不到所需数据的)。
$2: 启动相机设置拍照参数:不再通过Bundle获取拍照后的数据,而是通过Uri来获取图片
public void startCamera() {
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
// 拍照保存原始图片地址
String directory = FileUtil.getImagePhotoPath();
// 拍照图片按照当前时间命名
String filename = System.currentTimeMillis() + ".jpg";
File dir = new File(directory);
if (!dir.exists())
dir.mkdirs();
File file = new File(directory, filename);
tempPhotoPath = file.getAbsolutePath(); // 拍照图片绝对路径
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
// 开启系统拍照
startActivityForResult(intent, 1000);
// 动画
overridePendingTransition(0, 0);
}
@Override
protected void onActivityResult(int requestCode, int resultCode,Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
switch(resultCode)
{
case 1000:
getUri(imagPath);
break;
}
}
这里我们使用Uri获取图片直接进行剪裁减少一次 Bitmap的绘制。
private void getUri(String imagPath){
Uri uri = FileUtil.geturi(RealNameInfoActivity.this, imagPath);
if(uri == null){
File mfile = new File(imagePath);
uri = Uri.fromFile(mfile);
}
cutPic(uri);
}
接下来剪裁图片:
/**
* 裁剪图片
*/
private void cutPic(Uri uri){
int outputX = (int)(mpoint.x);
int outputY = (int)(outputX/1.5);
Uri mImageUri = uri;
Intent intent = new Intent();
intent.setAction("com.android.camera.action.CROP");
intent.setDataAndType(mImageUri, "image/*");// mUri是已经选择的图片Uri
intent.putExtra("crop", "true"); // crop=true是设置在开启的Intent中设置显示的VIEW可裁剪
intent.putExtra("scale", true); // 去黑边
intent.putExtra("aspectX", 3); // aspectX aspectY 是宽高的比例
intent.putExtra("aspectY", 2);
intent.putExtra("outputX", outputX); // outputX outputY 是裁剪图片宽高
intent.putExtra("outputY", outputY);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("return-data", false); // 剪裁后在onActivityResult不通过data获取数据
intent.putExtra(MediaStore.EXTRA_OUTPUT, FileUtil.getTempUri()); // 设置通过uri来获取剪裁后图片
intent.putExtra("noFaceDetection", false); // 关闭人脸检测
startActivityForResult(intent, REQUEST_CODE_PIC_CUT);
}
同样对应剪裁后的图片我们在onActivityResult中不通过data获取获取(毕竟承载大小有限),这里我们依然通过Uri获取剪裁后的图片
<pre name="code" class="java">@Override
protected void onActivityResult(int requestCode, int resultCode,Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
switch(resultCode)
{
case 1000:
getUri(imagPath);
break;
case REQUEST_CODE_PIC_CUT:
if(data == null)
return;
Bitmap bitmap = null;
try {
// 压缩处理剪裁后图片并保存本地等待上传
bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(FileUtil.getTempUri()));
cutbitmappath_one = FileUtil.saveCutBitmap(bitmap);
handleImage(cutbitmappath_one);
} catch (Exception e) {
e.printStackTrace();
} break;
}
}
/**
* 保存剪裁图片
* @return 返回图片路径
*/
public static String saveCutBitmap(Bitmap bitmap) {
// 根据当前时间命名图片
String fileName = System.currentTimeMillis() + ".jpg";
// 将该处理过后的 Bitmap图片保存本地
String str = getImagePhotoPath();
File file = new File(str, fileName);
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
bitmap.compress(CompressFormat.JPEG, 80, outputStream); // 80-压缩比:表示压缩20% 如果不压缩 写成100即可
} catch (Exception e) {
} finally {
try {
outputStream.flush();
outputStream.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
return str + "/" + fileName;
}
最后我们将剪裁后的图片显示在对应的控件上:
/**
* 显示图片
*/
private void handleImage(String filePath) {
Drawable drawable = BitmapUtil.pathToDrawable(filePath);
view.setBackgroundDrawable(drawable);
}
PS:涉及到Bitmap转换的时候尽量使用Bitmapfactory.options类,这个类有一个字段叫做 inJustDecodeBounds,SDK中对这个成员说明是这样的:
If set to true, the decoder will return null (no bitmap), but the out…
也就是说,如果我们把它设为true,那么BitmapFactory.decodeFile(String path, Options opt)并不会真的返回一个Bitmap给你,它仅仅会把它的宽,高取回来给你,这样就不会占用太多的内存,也就不会那么频繁的发生OOM了。
/**
* 将 路径转换为 bitmap
* 根据图片路径 处理后返回bitmap
* 相对于直接使用 BitmapFactory.decodeFile(filePath); 有效防止OOM
* @param filePath 文件路径
* @param zoomLevel 缩放级别(建议是2的整倍数)
* @return
*/
public static Bitmap pathToBitmap(String filePath,int zoomLevel) {
try {
InputStream is = new FileInputStream(filePath);
return inputstremToBitmap(is,zoomLevel);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
/**
* 将 inputStrem 转换为bitmap
*
* @param is 文件字节流
* @param zoomLevel 缩放级别(建议为2的整倍数)
* @return
*/
public static Bitmap inputstremToBitmap(InputStream is,int zoomLevel) {
try {
BitmapFactory.Options opts = new BitmapFactory.Options();
// 内存中申请100k缓存空间
opts.inTempStorage = new byte[100 * 1024];
opts.inPreferredConfig = Bitmap.Config.RGB_565;
//设置图片可以被回收,创建Bitmap用于存储Pixel的内存空间在系统内存不足时可以被回收
opts.inPurgeable = true;
//设置位图缩放比例 width,hight设为原来的四分一(该参数请使用2的整数倍)
// 这也减小了位图占用的内存大小;例如,一张分辨率为2048*1536px的图像使用inSampleSize值为4的设置来解码,产生的Bitmap大小约为512*384px。相较于完整图片占用12M的内存,这种方式只需0.75M内存(假设Bitmap配置为ARGB_8888)。
opts.inSampleSize = zoomLevel;
//设置解码位图的尺寸信息
opts.inInputShareable = true;
//解码位图
Bitmap bitmap = BitmapFactory.decodeStream(is, null, opts);
// 返回所需bitmap
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
经测试原照2.3M 经过裁剪压缩后 100k以内:
清晰度对比:
通过AS的Monitors可以直观的看到APP在拍照前后内存波动很小: