首先看一下链接:Android7.0 完美适配——FileProvider 拍照裁剪全解析,这里面有关问题讲的很详细。 总之一句话在Android7.0之前拍照,读取相册,裁剪是可以通过file://Uri 来传递意图,之后便不被允许。解决办法是使用FileProvider操作。但是我按照文中所讲在小米7.0的手机和小米6.0的手机中不能完全适配。折腾很久之后才找到解决办法。如下: 首先在AndroidManifest中添加如下代码:
<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>
其中“applicationId”表示的是包名,“fileprovider”是随意起的名称,在之后使用FileProvider.getUriForFile()方法时会用到。 之后在res目录下添加xml文件夹,里面添加paths文件,文件名是provider_paths,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- xml文件是唯一设置分享的目录 ,不能用代码设置
1.<files-path> getFilesDir() /data/data//files目录
2.<cache-path> getCacheDir() /data/data//cache目录
3.<external-path> Environment.getExternalStorageDirectory()
SDCard/Android/data/你的应用的包名/files/ 目录
4.<external-files-path> Context#getExternalFilesDir(String) Context.getExternalFilesDir(null).
5.<external-cache-path> Context.getExternalCacheDir().
-->
<!-- path :代表设置的目录下一级目录 eg:<external-path path="images/"
整个目录为Environment.getExternalStorageDirectory()+"/images/"
name: 代表定义在Content中的字段 eg:name = "myimages" ,并且请求的内容的文件名为default_image.jpg
则 返回一个URI content://com.example.myapp.fileprovider/myimages/default_image.jpg
-->
<!--当path 为空时 5个全配置就可以解决-->
<external-path name="external_files" path="/com.ooli/"/>
</paths>
上文中path里的路径要注意,本文使用的是HttpUrl.photoPath=Environment.getExternalStorageDirectory()+"/com.ooli";一定要对应好,不然在之后会因路径不对导致出错。 再之后便是在代码中添加拍照,读取相册,裁剪的逻辑,如下:
// 创建文件夹
file = new File(HttpUrl.photoPath);
if (!file.exists()) {
file.mkdirs();
}
cemera_tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (hasSdcard()) {
//第二个参数是需要申请的权限
if (ContextCompat.checkSelfPermission(CenterOfUserActivity.this,
android.Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
//权限还没有授予,需要在这里写申请权限的代码
/*
第二个参数是一个字符串数组,里面是你需要申请的权限。既然是一个数组,那么就说明你一次可以申请多个权限。
最后一个参数是一个整型常量,用于标志你这次申请的权限,该常量在onRequestPermissionsResult(…)方法中会用到。
*/
ActivityCompat.requestPermissions(CenterOfUserActivity.this,
new String[]{android.Manifest.permission.CAMERA}, Util.MY_PERMISSIONS_REQUEST_CALL_PHOTO);
} else {
//权限已经被授予,在这里直接写要执行的相应方法即可
startTakePhoto();
}
} else {
Util.showToast(CenterOfUserActivity.this, “存储卡不可用”);
}
}
});
album_tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (hasSdcard()) {
//判断是否有读写手机存储的权限
if (ContextCompat.checkSelfPermission(CenterOfUserActivity.this,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//权限还没有授予,需要在这里写申请权限的代码
/*
第二个参数是一个字符串数组,里面是你需要申请的权限。既然是一个数组,那么就说明你一次可以申请多个权限。
最后一个参数是一个整型常量,用于标志你这次申请的权限,该常量在onRequestPermissionsResult(…)方法中会用到。
*/
ActivityCompat.requestPermissions(CenterOfUserActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Util.MY_PERMISSIONS_REQUEST_WRITE);
} else {
startphotoAlbum();
}
} else {
Util.showToast(CenterOfUserActivity.this, getResources().getString(R.string.sdcard_usable));
}
}
});
/**
* 检查设备是否存在SDCard的工具方法
*/
public boolean hasSdcard() {
try {
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) {
// 有存储的SDCard
return true;
}
} catch (Exception e) {
LogUtil.e(getClass(), "hasSdcard()", e);
}
return false;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//判断是拍照的权限
if (requestCode == Util.MY_PERMISSIONS_REQUEST_CALL_PHOTO) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startTakePhoto();
} else {
Util.intentPermission(CenterOfUserActivity.this, getResources().getString(R.string.allow_take_photo));
}
}
//判断是读写手机存储的权限
if (requestCode == Util.MY_PERMISSIONS_REQUEST_WRITE) {
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Util.intentPermission(CenterOfUserActivity.this, getResources().getString(R.string.allow_take_write));
} else {
startphotoAlbum();
}
}
}
以上代码只是判断是否有权限, 第二步便是发起读取相册和拍照的代码:
//读取相册
private void startphotoAlbum() {
try {
Intent intentFromGallery = new Intent();
// 设置文件类型
intentFromGallery.setType("image/*");
intentFromGallery.setAction(Intent.ACTION_GET_CONTENT);
intentFromGallery.addCategory(Intent.CATEGORY_OPENABLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//如果大于等于7.0使用FileProvider
File outFile = new File(file, System.currentTimeMillis() + ".jpg");
Uri uriForFile = FileProvider.getUriForFile(CenterOfUserActivity.this, getPackageName() + ".fileprovider", outFile);
intentFromGallery.putExtra(MediaStore.EXTRA_OUTPUT, uriForFile);
intentFromGallery.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intentFromGallery.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
startActivityForResult(intentFromGallery,
Util.CODE_GALLERY_REQUEST);
} catch (Exception e) {
LogUtil.e(getClass(), "startphotoAlbum()", e);
}
}
//执行拍照
private void startTakePhoto() {
try {
Intent intentFromCapture = new Intent(
MediaStore.ACTION_IMAGE_CAPTURE);
IMAGE_FILE_NAME = System.currentTimeMillis() + ".jpg";
File outFile = new File(file, IMAGE_FILE_NAME);
//如果该文件以经存在,则删除,否则创建一个
if (outFile.exists()) {
outFile.delete();
}
try {
outFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
Uri photoUri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intentFromCapture.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intentFromCapture.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
photoUri = FileProvider.getUriForFile(CenterOfUserActivity.this, getPackageName() + ".fileprovider", outFile);
} else {
photoUri = Uri.fromFile(outFile);
}
intentFromCapture.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(intentFromCapture,
Util.CODE_CAMERA_REQUEST);
} catch (Exception e) {
LogUtil.e(getClass(), "startTakePhoto()", e);
}
}
在这之后便是在onActivityResult()中接收照片,处理逻辑:
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
try {
super.onActivityResult(requestCode, resultCode, intent);
// 用户没有进行有效的设置操作,返回
if (resultCode == RESULT_CANCELED) {
return;
}
switch (requestCode) {
case Util.CODE_GALLERY_REQUEST:
//从相册中选择图片
if (intent != null) {
cropRawPhoto(intent.getData());
}
break;
case Util.CODE_CAMERA_REQUEST:
//拍照完成时
if (hasSdcard() && IMAGE_FILE_NAME != null) {
File outFile = new File(file, IMAGE_FILE_NAME);
Uri photoUri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
photoUri = FileProvider.getUriForFile(CenterOfUserActivity.this, getPackageName() + ".fileprovider", outFile);
} else {
photoUri = Uri.fromFile(outFile);
}
cropRawPhoto(photoUri);
} else {
Util.showToast(CenterOfUserActivity.this, getResources().getString(R.string.no_sdcard));
}
break;
case Util.CODE_RESULT_REQUEST:
if (intent != null) {
//裁剪完成之后通过intent.getDataString();获取图片Uri地址进行下一步操作(一般是使用七牛云上传本地图片).
//本文中将裁剪后的图片的Uri写成全局变量,可以直接获取到地址。即是下文代码中的imageUri 。
}
break;
}
} catch (Exception e) {
LogUtil.e(
getClass(),
"onActivityResult(int requestCode, int resultCode,Intent intent)",
e);
}
}
其中从相册中选取照片和拍照完成之后都要进行裁剪,要说明的是在相册返回中,应该是通过intent.getData()获取在相册中选择的照片的Uri,不用进行其它的操作,将Uri传递给要裁剪的方法即可,裁剪代码如下:
/**
* 裁剪原始的图片
*/
public void cropRawPhoto(Uri uri) {
try {
File outFile = new File(file, System.currentTimeMillis() + ".jpg");
Intent intent = new Intent("com.android.camera.action.CROP");
Uri photoUri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
photoUri = Uri.fromFile(outFile);
intent.setDataAndType(uri, "image/*");// 剪切特定的图片
} else {
photoUri = Uri.fromFile(outFile);
intent.setDataAndType(uri, "image/*");
}
imageUri = photoUri;
// 设置裁剪
intent.putExtra("crop", "true");
// aspectX , aspectY :宽高的比例
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// outputX , outputY : 裁剪图片宽高
intent.putExtra("outputX", 480);
intent.putExtra("outputY", 480);
// return-data为true时,会直接返回bitmap数据,但是大图裁剪时会出现问题
// return-data为false时,不会返回bitmap,但需要指定一个MediaStore.EXTRA_OUTPUT保存图片uri
intent.putExtra("return-data", false);// 是否返回数据
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);// 图像保存的路径
intent.putExtra("outputFormat",
Bitmap.CompressFormat.JPEG.toString());// 返回的格式
intent.putExtra("noFaceDetection", true);// 是否去除面部检测,
// 如果你需要特定的比例去裁剪图片,那么这个一定要去掉,因为它会破坏掉特定的比例。
startActivityForResult(intent, Util.CODE_RESULT_REQUEST);
} catch (Exception e) {
LogUtil.e(getClass(), "cropRawPhoto(Uri uri)", e);
}
}
其中注意一点的是在裁剪中是通过Uri.fromFile()获取对应的Uri 的,添加if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)判断只是为了动态获取读写文件的权限。在读取相册和拍照时才通过FileProvider.getUriForFile()获取Uri,这一点要分清。