Android 起调相机拍照剪裁内存优化

本篇实现功能主要是通过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; 
        } 
    }
 


接下来我们将bitmap进行压缩保存到本地返回绝对路径一遍后续上传操作:

/**
     * 保存剪裁图片
     * @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在拍照前后内存波动很小:










评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值