Android7.0调用相机时出现新的错误:
android.os.FileUriExposedException: file:///storage/emulated/0/photo.jpeg exposed beyond app through ClipData.Item.getUri()
解决办法:
在Application的onCreat()方法中添加以下代码:
// android 7.0系统解决拍照的问题
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
builder.detectFileUriExposure();
Android 7.0 之拍照与图片裁剪适配
Android 7.0以后,用了Content Uri 替换了原本的File Uri,故在targetSdkVersion=24的时候,部分 “`Uri.fromFile()“` 方法就不适用了。 **File Uri 与 Content Uri 的区别** - File Uri 对应的是文件本身的存储路径 - Content Uri 对应的是文件在Content Provider的路径 所以在android 7.0 以上,我们就需要将File Uri转换为 Content Uri。具体转换方法如下:
package com.example.mvpfour.activity;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import com.example.mvpfour.BuildConfig;
import com.example.mvpfour.R;
import com.example.mvpfour.utils.LogUtils;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* Created by agen on 2018/7/4.
*/
public class CameraActivity extends AppCompatActivity implements View.OnClickListener {
private static final int REQUEST_CODE_TAKE_PHOTO = 100;
private static final int IMAGE_CROP_CODE = 101;
private Button bt_take_photo;
private String mCurrentPhotoPath;
private ImageView iv_take_photo;
private String cropImagePath;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera_activity);
initView();
}
private void initView() {
bt_take_photo = (Button) findViewById(R.id.bt_take_photo);
bt_take_photo.setOnClickListener(this);
iv_take_photo = (ImageView) findViewById(R.id.iv_take_photo);
// iv_take_photo.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_take_photo:
takeSystemPhoto(this);
break;
}
}
private void takeSystemPhoto(Context context) {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
String filename = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.CHINA)
.format(new Date()) + ".png";
File file = new File(Environment.getExternalStorageDirectory(), filename);
mCurrentPhotoPath = file.getAbsolutePath();
Uri fileUri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
LogUtils.d("安卓7的拍照");
/* fileUri = getUriForFile(context,
context.getPackageName() + ".fileprovider", file);*/
fileUri = getImageContentUri(file);
// takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", file)); //Uri.fromFile(file)
} else {
fileUri = Uri.fromFile(file);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
}
// fileUri = Uri.fromFile(file);
LogUtils.d("fileUrl=" + fileUri);
// takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
startActivityForResult(takePictureIntent, REQUEST_CODE_TAKE_PHOTO);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_TAKE_PHOTO) {
iv_take_photo.setImageBitmap(BitmapFactory.decodeFile(mCurrentPhotoPath));
//图片裁剪
crop(mCurrentPhotoPath);
} else if (resultCode == RESULT_OK && requestCode == IMAGE_CROP_CODE) {
iv_take_photo.setImageBitmap(BitmapFactory.decodeFile(cropImagePath));
//图片裁剪
}
// else tip?
}
/**
* 转换 content:// uri
*
* @param imageFile
* @return
*/
public Uri getImageContentUri(File imageFile) {
String filePath = imageFile.getAbsolutePath();
Cursor cursor = 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 getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
return null;
}
}
}
/**
* 裁剪
* @param imagePath
*/
private void crop(String imagePath) {
String filename = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.CHINA)
.format(new Date()) + ".png";
File file = new File(Environment.getExternalStorageDirectory(), filename);
// File file = new File("xxx.jpg");
cropImagePath = file.getAbsolutePath();
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(getImageContentUri(new File(imagePath)), "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// intent.putExtra("outputX", config.outputX);
// intent.putExtra("outputY", config.outputY);
intent.putExtra("scale", true);
intent.putExtra("return-data", false);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
startActivityForResult(intent, IMAGE_CROP_CODE);
}
}
这是因为拍照存储的文件,也需要以Content Uri的形式,故采用以下办法解决:
Step.1 修改AndroidManifest.xml
<application
...>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="{替换为你的包名}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
Step.2 在res/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>
参考:Android 7.0 之拍照与图片裁剪适配_LeBron_Six的专栏-CSDN博客
相关步骤的解析和说明:
参考:安卓调用系统相机拍照、从相册选择图片并裁剪保存到SD卡(适配7.0) - 简书
参考Android拍照及图片裁剪、调用系统相册(兼容6.0以上权限处理及7.0以上文件管理)
Android拍照及图片裁剪、调用系统相册(兼容6.0以上权限处理及7.0以上文件管理) – Android开发中文站。。。
系统拍照和打开相册
private static final int SYSTEM_PHOTO_REQUST = 1;//系统拍照的请求码
private static final int REQUEST_PICK_IMAGE = 2;//系统相册的请求码
/**
* 开启系统拍照,获取图片
*/
private void takeSystemPhoto() {
LogUtils.d("开启系统拍照_takeSystemPhoto");
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, SYSTEM_PHOTO_REQUST);
}
//从相册选择
private void getImage() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*"),
REQUEST_PICK_IMAGE);
} else {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, REQUEST_PICK_IMAGE);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
LogUtils.d("onActivityResult");
if (data != null && resultCode == Activity.RESULT_OK) {
rlPhoto.setVisibility(View.GONE);
//系统拍照
if (SYSTEM_PHOTO_REQUST == requestCode) {
//加载本地缓存图片
LogUtils.d("系统拍照返回的数据");
Bundle bundle = data.getExtras();
Bitmap bitmap = (Bitmap) bundle.get("data");
} else if (REQUEST_PICK_IMAGE == requestCode) {
//从相册选择
if (data != null) {
realPathFromUri = RealPathFromUriUtils.getRealPathFromUri(this, data.getData());
LogUtils.d("realPathFromUri=" + realPathFromUri);
} else {
Toast.makeText(this, "图片损坏,请重新选择", Toast.LENGTH_SHORT).show();
}
}
}
}
获取图片uri工具类
package com.example.mvpfour.utils;
/**
* Created by agen on 2019/5/7.
*/
import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
public class RealPathFromUriUtils {
/**
* 根据Uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
public static String getRealPathFromUri(Context context, Uri uri) {
int sdkVersion = Build.VERSION.SDK_INT;
if (sdkVersion >= 19) { // api >= 19
return getRealPathFromUriAboveApi19(context, uri);
} else { // api < 19
return getRealPathFromUriBelowAPI19(context, uri);
}
}
/**
* 适配api19以下(不包括api19),根据uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
private static String getRealPathFromUriBelowAPI19(Context context, Uri uri) {
return getDataColumn(context, uri, null, null);
}
/**
* 适配api19及以上,根据uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
@SuppressLint("NewApi")
private static String getRealPathFromUriAboveApi19(Context context, Uri uri) {
String filePath = null;
if (DocumentsContract.isDocumentUri(context, uri)) {
// 如果是document类型的 uri, 则通过document id来进行处理
String documentId = DocumentsContract.getDocumentId(uri);
if (isMediaDocument(uri)) { // MediaProvider
// 使用':'分割
String id = documentId.split(":")[1];
String selection = MediaStore.Images.Media._ID + "=?";
String[] selectionArgs = {id};
filePath = getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs);
} else if (isDownloadsDocument(uri)) { // DownloadsProvider
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(documentId));
filePath = getDataColumn(context, contentUri, null, null);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
// 如果是 content 类型的 Uri
filePath = getDataColumn(context, uri, null, null);
} else if ("file".equals(uri.getScheme())) {
// 如果是 file 类型的 Uri,直接获取图片对应的路径
filePath = uri.getPath();
}
return filePath;
}
/**
* 获取数据库表中的 _data 列,即返回Uri对应的文件路径
*
* @return
*/
private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
String path = null;
String[] projection = new String[]{MediaStore.Images.Media.DATA};
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
int columnIndex = cursor.getColumnIndexOrThrow(projection[0]);
path = cursor.getString(columnIndex);
}
} catch (Exception e) {
if (cursor != null) {
cursor.close();
}
}
return path;
}
/**
* @param uri the Uri to check
* @return Whether the Uri authority is MediaProvider
*/
private static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri the Uri to check
* @return Whether the Uri authority is DownloadsProvider
*/
private static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
}