Android7.0适配之图片裁剪

96
巴别塔下牧羊
2016.09.23 14:11* 字数 1058

Android 7.0系统发布后,拿到能升级的nexus 6P,就开始了7.0的适配。发现Android7.0在修改头像时候进行拍照并裁剪图片时会出现photos app崩溃。仔细分析操作步骤和流程,发现照片拍照是成功的,SD卡也能保存相关的图片信息,但是在对拍照的图片进行裁剪时候出现了photos app崩溃;如下图:




同时发现通过选择相册进行选中图片后才进行裁剪就没有问题。看一下代码:

Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(Uri.fromFile(inputfile), IMAGE_UNSPECIFIED);//主要问题就在这个File Uri上面 ————代码语句A
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 180);
intent.putExtra("outputY", 180);
intent.putExtra("scale", true);
intent.putExtra("return-data", false);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(outputfile));//定义输出的File Uri,之后根据这个Uri去拿裁剪好的图片信息 ————代码B
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
startActivityForResult(intent, RequestCode);

上网查找了一些资料都是说Android7.0后,发现Android7.0在这个方面最大变化就在于以前适用的File Uri需要更改为Content Uri,详情可以看这里。但是这个更改是需要编译的targetSdkVersion是24的时候才有效,我们的apptargetSdkVersion是23。所以问题上面的链接解决办法应该不是这个原因(这个解决在后文解决targetSdkVersion=24的时候需要用到);

但是从文章中知道了File Uri和Content Uri的区别,然后再去分析一下为什么从相册选择图片进行裁剪是生效的?通过代码分析和debug后发现,从相册选取图片得到的Uri是Content Uri而拍照后使用的是文件路径生成的File Uri,看来问题就是出在这里,并不是说我们app的targetSDKVersion不是24就可以使用File Uri,但是photos app的targetSdkVersion可能是24导致了它接受了File Uri而崩溃,那么我们需要做的就是把File Uri换成Content Uri。这里需要提的是,直接按照这里的做法去更换Content Uri并不能生效,会提示“Can not edit image under 50*50 pixels”的错误toast提示,其实是photos app找不到Content Uri传进去的图片文件。那么我们需要换一种方式去更换Content Uri,我们在stackoverflow上面找到更换Content Uri的方法,需要注意的是不是所有的File Uri都可以转换成Content Uri,应该是多媒体相关的文件才可以。下面是代码:

public static Uri getImageContentUri(Context context, File imageFile) {
String filePath = imageFile.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Images.Media._ID },
MediaStore.Images.Media.DATA + "=? ",
new String[] { filePath }, null);

if (cursor != null && cursor.moveToFirst()) {
int id = cursor.getInt(cursor
.getColumnIndex(MediaStore.MediaColumns._ID));
Uri baseUri = Uri.parse("content://media/external/images/media");
return Uri.withAppendedPath(baseUri, "" + id);
} else {
if (imageFile.exists()) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, filePath);
return context.getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
return null;
}
}
}

我们需要把上文第一处代码的Uri.fromFile(inputfile)更换成getImageContentUri生成的Uri。替换上去后运行程序试试,这样修改就可以了。

更进一步:要是我们把上文的代码,把app的targetSdkVersion改成24之后,在启动相机进行拍照时候,会发现这样的错误

android.os.FileUriExposedException: file:///storage/emulated/0/DCIM/Camera/TEMP_IMAGE1474468182889.jpg exposed beyond app through ClipData.Item.getUri();

不能拍照成功;这个时候我们就要用上文介绍的文章file:// scheme is now not allowed to be attached with Intent on targetSdkVersion 24 (Android Nougat).使用FileProvider来产生Content Uri代替File Uri,按照上面网址介绍方法替换掉就可以;

然而我们在用FileProvider.getUriForFile替换掉所有的Uri.fromFile时候,可以拍照成功了,但是在剪裁图片时候还是会出现之前的“Can not edit image under 50*50 pixels”没有办法,只能把上文的代码语句A重新更改为getImageContentUri生成Content Uri,重新运行程序;这个时候可以拍照成功,进入图片裁剪photos app里面,但是裁剪完成Save的时候photos app又崩溃了:


应该是FileProvider的属性为android:exported="false"的原因,但是这里不能改为true(会报另一个错误:java.lang.RuntimeException: Unable to get provider android.support.v4.content.FileProvider: java.lang.SecurityException: Provider must not be exported)。

这个时候我们需要把代码语句B里面outputUri改为File Uri就可以了,最终的代码如下,调用相机拍照的代码自己替换成FileProvider就可以了:

Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(getImageContentUri(context , inputfile), IMAGE_UNSPECIFIED);//自己使用Content Uri替换File Uri
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 180);
intent.putExtra("outputY", 180);
intent.putExtra("scale", true);
intent.putExtra("return-data", false);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(outputfile));//定义输出的File Uri
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
startActivityForResult(intent, RequestCode);

综上,我们完成了targetSdkVersion=24和小于24两种情况的图片裁剪适配;之后还是采用自己app内程序进行图片裁剪适配性比较好。

Android