android 调用相机拍照、压缩

原文出处:http://www.yummy-cow.com/2016/05/25/20160525/

介绍

公司android的网络请求框架使用的是okhttp,上传图片也是用的它;

在做头像上传的时候,一般都会先把用户选定的图片,进行本地压缩再上传到服务器,

之前用的压缩算法是按像素/2来进行压缩的,但是现在的手机的像素越来越高,拍出来的图片大小基本上都是3-5mb的,

有时候选一个高清一点的图片会出现网络超时,程序也会crash,出现oom等。

在github上找到了一个图片压缩类,压缩效果还不错,上传图片也快了,目前也没有出现过crash。

调用系统相机:

//实例化一个intent,并指定action
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//指定一个图片路径对应的file对象
uri = Uri.fromFile(ImageUtil.getImageFile());
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
//启动activity
startActivityForResult(intent, REQUEST_CODE_CAMERA);

但是在onActivityResult(int requestCode, int resultCode, Intent data)代码中得到的data总为null?

分析原因:

在Android系统框架中的Camera应用程序中,找到了关于系统照相机如何处理返回值data的问题!

默认情况下,既不需要指定intent.putExtra(MediaStore.EXTRA_OUTPUT,uri) ;

照相机有自己默认的存储路径,拍摄的照片将返回一个缩略图,如果想访问原始图片地址,可以通过data.extra获取原始图片位置;

即,如果指定了目标uri,data就没有数据,如果没有指定uri,则data就返回有数据。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	switch (requestCode) {
	case REQUEST_CODE_CAMERA:
		if (resultCode == RESULT_OK) {
			if(data !=null){ //可能尚未指定intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
				//返回有缩略图
				if(data.hasExtra("data")){
					Bitmap thumbnail = data.getParcelableExtra("data");
					//得到bitmap后的操作
				}
			}else{
				//由于指定了目标uri,存储在目标uri,intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
				// 通过目标uri,找到图片
				// 对图片的缩放处理
				// 操作
			}
		}
	}
}

系统照相机部分关键代码:

// First handle the no crop case -- just return the value.  If the
// caller specifies a "save uri" then write the data to it's
// stream. Otherwise, pass back a scaled down version of the bitmap
// directly in the extras.
if (mSaveUri != null) {	//存在mSaveUri,即指定了目标uri
	OutputStream outputStream = null;
	try {
		outputStream = mContentResolver.openOutputStream(mSaveUri);
		outputStream.write(data);
		outputStream.close();
		setResult(RESULT_OK);	//直接返回RESULT_OK,并没有指定intent
		finish();
	} catch (IOException ex) {
		// ignore exception
	} finally {
		Util.closeSilently(outputStream);
	}
} else {
	Bitmap bitmap = createCaptureBitmap(data);
	// 返回RESULT_OK,并包含一个Intent对象,其中Extra中科key为data,value为一个bitmap
	setResult(RESULT_OK, new Intent("inline-data").putExtra("data", bitmap));
	finish();
}

常见问题及解决办法:

如果我们设置了照片的存储路径,那么很可能会遇到以下三种问题:

  • onActivityResult方法中的data返回为空(数据表明,93%的机型的data 将会是Null,所以如果我们指定了路径,就不要使用data 来获取照片,起码在使用前要做空判断)

  • 照片无法存储,如果自定义存储路径是/mnt/sdcard/lowry/,而手机SD 卡下在拍照前没有名为lowry 的文件夹,那么部分手机拍照后图片不会保存,导致我们无法获得照片,大多数手机的相机遇到文件夹不存在的情况都会自己创建出不存在的文件夹,而个别手机却不会创建,其代表机型为:三星I8258、华为H30-T00、红米等。解决的方法就是在指定存储路径前先判断路径中的文件夹是否都存在,不存在先创建再调用相机。

  • 照片可以存储,但是名字不对 file:///mnt/sdcard/123 1.jpg,由于Uri 的fromFile 方法会将路径中的空格用“%20”取代。其实对于大多数的手机这都不算事,手机在解析存储路径的时候都会将“%20”替换为空格,这样实际上最终的照片名字还是我们当初指定的名字:123 1.jpg,遗憾的是个别手机(如酷派7260)系统自带的相机没有将“%20”读成空格,拍照后的照片的名字是123%201.jpg,我们用路径“file:///mnt/sdcard/123 1.jpg”能找到照片才怪!!

解决方法:

  1. 使用 onActivityResult 中的 intent(data)前要做空判断。

  2. 指定拍照路径时,先检查路径中的文件夹是否都存在,不存在时先创建文件夹再调用相机拍照。

  3. 指定拍照存储路径时,照片的命名中不要包含空格等特殊符号。

图片压缩工具类:

public static String saveBitmapToFile(File file, String newpath) {
        try {
            // BitmapFactory options to downsize the image
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            o.inSampleSize = 6;
            // factor of downsizing the image
            FileInputStream inputStream = new FileInputStream(file);
            //Bitmap selectedBitmap = null;
            BitmapFactory.decodeStream(inputStream, null, o);
            inputStream.close();
            // The new size we want to scale to
            final int REQUIRED_SIZE = 75;
            // Find the correct scale value. It should be the power of 2.
            int scale = 1;
            while (o.outWidth / scale / 2 >= REQUIRED_SIZE &&
                    o.outHeight / scale / 2 >= REQUIRED_SIZE) {
                scale *= 2;
            }
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize = scale;
            inputStream = new FileInputStream(file);
            Bitmap selectedBitmap = BitmapFactory.decodeStream(inputStream, null, o2);
            inputStream.close();
            // here i override the original image file
//            file.createNewFile();
//
//
//            FileOutputStream outputStream = new FileOutputStream(file);
//
//            selectedBitmap.compress(Bitmap.CompressFormat.JPEG, 100 , outputStream);
            File aa = new File(newpath);
            FileOutputStream outputStream = new FileOutputStream(aa);
            //choose another format if PNG doesn't suit you
            selectedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
            String filepath = aa.getAbsolutePath();
            Log.e("getAbsolutePath", aa.getAbsolutePath());
            return filepath;
        } catch (Exception e) {
            return null;
        }
    }

使用:

ImageCompress.saveBitmapToFile(new File(“source.jpg”),”newImage.jpg”);

source.jpg 是原始图片;
newImage 压缩后存储的位置;