在android7.0以上系统,如果uri仍然使用fill类型Uri的话,拍照和剪裁时可能会出现以下两种错误:
//错误一
android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/com.yuyh.imgsel/cache/1486438962645.jpg exposed beyond app through ClipData.Item.getUri()
//错误二
android.os.FileUriExposedException: file:///storage/emulated/0/DCIM/RxGalleryFinal/IMG_20161018180127.jpg exposed beyond app through Intent.getData()
之所以会出现以上两种错误是因为我们7.0之前的进行拍照或者裁剪时传递Uri时可以直接使用Uri.FromFile(file)的形式进行Uri的传递,而从7.0之后我们的Uri不允许通过这种方式传递,而可以通过FileProvider.getUriFromFile进行Uri的转换即可。具体实现方式如下:
android7.0以前拍照代码
/**
* 拍照
*/
public void takePhoto() {
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
outputPath =Environment.getExternalStorageDirectory().getAbsolutePath().concat(File.separator).concat("facePhoto.jpg");
}else {
outputPath =getCacheDir().getAbsolutePath().concat(File.separator).concat("facePhoto.jpg");
}
try {
Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
Uri uri=Uri.fromFile(new File(outputPath));
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent,REQUEST_CODE_TAC_PIC);
} catch (ActivityNotFoundException e) {
ToastHelp.showShortToast(getString(R.string.errormsg_cantopencamera));
return;
}
}
android 7.0之前裁剪代码:
private void cropPicture(Uri sourceUri){
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
tempFile =new File(getExternalCacheDir(),"crop.jpg");
}else {
tempFile =new File(getCacheDir()+"crop.jpg");
}
if(!tempFile.exists()){
try {
tempFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(sourceUri, "image/*");
intent.putExtra("crop", "true");
// aspectX aspectY 是宽高的比例
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", width);
intent.putExtra("outputY", width);
intent.putExtra("return-data", true);// 是否返回数据
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);// 关闭人脸检测
startActivityForResult(intent, REQUEST_CODE_CUT_PIC);
}
之所以以上代码放到7.0以上系统会报以上两种类型错误,是因为7.0以后不允许向外部应用传递file类型Uri,并且7.0以后私有文件访问也受到限制;具体原因不做过多解释,如果有需要可以查看7.0适配相关文档,文档连接我会在当前文档最后提供给大家。具体7.0以上解决方案如下:
1.在清单文件中添加
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
2.在res创建一个xml文件夹,res/xml/provider_paths.xml
具体provider_paths.xml内容如下
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
3.代码更改适配7.0
7.0以上系统拍照代码
/**
* 拍照
*/
public void takePhoto() {
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
outputPath =Environment.getExternalStorageDirectory().getAbsolutePath().concat(File.separator).concat("facePhoto.jpg");
}else {
outputPath =getCacheDir().getAbsolutePath().concat(File.separator).concat("facePhoto.jpg");
}
try {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//此处新加代码不可省略
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
Uri uri=getUriForFile(AccountInfoActivity.this,new File(outputPath));//此处为更改Uri为content类型的Uri
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent,REQUEST_CODE_TAC_PIC);
} catch (ActivityNotFoundException e) {
ToastHelp.showShortToast(getString(R.string.errormsg_cantopencamera));
return;
}
}
7.0以上裁剪图片代码
private void cropPicture(Uri sourceUri){
if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
tempFile =new File(getExternalCacheDir(),"crop.jpg");
}else {
tempFile =new File(getCacheDir()+"crop.jpg");
}
if(!tempFile.exists()){
try {
tempFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Intent intent = new Intent("com.android.camera.action.CROP");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//此处新加代码不可省略
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);//此处新加代码不可省略
intent.setDataAndType(sourceUri, "image/*");//数据源uri对于拍照返回携带的uri需要进行处理 要在传递之前先转换成content类型的uri
intent.putExtra("crop", "true");
// aspectX aspectY 是宽高的比例
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", width);
intent.putExtra("outputY", width);
intent.putExtra("return-data", true);// 是否返回数据
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));//此时数据时设置给当前app内部的uri无需进行转换 //此处有些文档也对Uri进行转换 但经自己亲测后转换反而会有问题
intent.putExtra("outputFormat",Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);// 关闭人脸检测
startActivityForResult(intent, REQUEST_CODE_CUT_PIC);
}
此时剪裁代码有传进来一个Uri,如果是拍照之后的剪裁那么在剪裁之前需要将file:Uri转换成content://Uri及假如是拍照之后的剪裁我们代码如下:
Uri uri=getUriForFile(AccountInfoActivity.this,new File(outputPath));//拍照之后的剪裁 先对Uri进行转换 再调用剪裁的方法
cropPicture(uri);
转化Uri通用方法
/**
* 征对不同版本对uri的处理
* @param context
* @param file
* @return
*/
private Uri getUriForFile(Context context, File file) {
if (context == null || file == null) {
throw new NullPointerException();
}
Uri uri;
if (Build.VERSION.SDK_INT >= 24) {
uri = FileProvider.getUriForFile(context.getApplicationContext(), BuildConfig.APPLICATION_ID+".fileprovider", file);
} else {
uri = Uri.fromFile(file);
}
return uri;
}
正对7.0以后的拍照、剪裁除了以上方案外还有史上最简单的方案,几句代码即可搞定,简单暴力
在Application的onCreat()方法中添加以下代码:
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
builder.detectFileUriExposure();
相关参考文档:7.0适配链接