Android系统裁剪功能
实现图片裁剪,通过Intent开启系统裁剪功能。这里是一个常见裁剪配置,包含裁剪的尺寸,原始图片地址,裁剪后的图片地址等配置。具体代码如下:
/**
* 根据Uri裁剪图片
*
* @param uri
*/
private void crop(Uri uri) {
// 裁剪图片意图
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
intent.putExtra("crop", "true");
// 裁剪框的比例,1:1
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// 裁剪后输出图片的尺寸大小
intent.putExtra("outputX", 250);
intent.putExtra("outputY", 250);
// 图片格式
intent.putExtra("outputFormat", Bitmap.CompressFormat.PNG.toString());
// 取消人脸识别
intent.putExtra("noFaceDetection", true);
/* //是否把剪切后的图片通过data返回
intent.putExtra("return-data", true);*/
File cropFile = BitmapUtils.getDiskFile(getApplicationContext(), BitmapUtils.getBitmapFileName());
currentPicturePath = cropFile.getAbsolutePath();
Uri cropUri = Uri.fromFile(cropFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, cropUri);
// 开启一个带有返回值的Activity,请求码为PHOTO_REQUEST_CUT
startActivityForResult(intent, PHOTO_REQUEST_CUT);
}
Android 6.0的权限问题
在Android 7.0模拟器上,运行以上的代码,发生以下异常:
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=2, result=-1, data=Intent { }}
to activity {com.zhongke.weiduo/com.zhongke.weiduo.mvp.ui.activity.ActivateDeviceActivity}:
android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/com.zhongke.weiduo/files/Pictures/3cd82f9a4c2fe0caa21fe32e339c1161.png
exposed beyond app through Intent.getData()
at android.app.ActivityThread.deliverResults(ActivityThread.java:4053)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4096)
at android.app.ActivityThread.-wrap20(ActivityThread.java)
处理FileUriExposedException
,需要在项目中自定义配置FileProvider,接下来会介绍到。
项目中配置FileProvider
按照以上介绍,实现FileProvider配置后,修改以上的代码如下:
/**
* 根据Uri裁剪图片
*
* @param uri
*/
private void crop(Uri uri) {
// 裁剪图片意图
Intent intent = new Intent("com.android.camera.action.CROP");
if (Build.VERSION.SDK_INT>=24){
Uri newUri=FileProvider.getUriForFile( getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider",new File(uri.getEncodedPath()));
intent.setDataAndType(newUri ,"image/*");
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}else {
intent.setDataAndType(uri, "image/*");
}
intent.putExtra("crop", "true");
// 裁剪框的比例,1:1
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// 裁剪后输出图片的尺寸大小
intent.putExtra("outputX", 250);
intent.putExtra("outputY", 250);
// 图片格式
intent.putExtra("outputFormat", Bitmap.CompressFormat.PNG.toString());
// 取消人脸识别
intent.putExtra("noFaceDetection", true);
/* //是否把剪切后的图片通过data返回
intent.putExtra("return-data", true);*/
File cropFile = BitmapUtils.getDiskFile(getApplicationContext(), BitmapUtils.getBitmapFileName());
currentPicturePath = cropFile.getAbsolutePath();
if (Build.VERSION.SDK_INT>=24){
intent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", new File(currentPicturePath)));
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}else{
Uri cropUri = Uri.fromFile(cropFile);
//设置裁剪后文件保存路径
intent.putExtra(MediaStore.EXTRA_OUTPUT, cropUri);
}
// 开启一个带有返回值的Activity,请求码为PHOTO_REQUEST_CUT
startActivityForResult(intent, PHOTO_REQUEST_CUT);
}
再次执行,纳尼,又发生异常了,简直泪奔了。还得老实去查看,具体如下:
DatabaseUtils: Writing exception to parcel
java.lang.SecurityException: Permission Denial: reading android.support.v4.content.FileProvider uri content://com.zhongke.weiduo.provider/root/storage/emulated/0/Android/data/com.zhongke.weiduo/files/Pictures/20b11deb8c0de58d6b95acf60489a67d.png from pid=3426, uid=10049 requires the provider be exported, or grantUriPermission()
at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:608)
at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:483)
at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:474)
at android.content.ContentProvider$Transport.openTypedAssetFile(ContentProvider.java:419)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:313)
at android.os.Binder.execTransact(Binder.java:565)
在代码中intent.setDataAndType(newUri ,"image/*");intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
已经授写入权限,而报错却提醒未授予读取权限。因此,根据提示,改为授予读取权限intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
。
修改完成后,再次执行,发觉以上bug呗完美填掉。小样,总算解决掉你了。这bug呀,雨后春笋,解决一个又一个出现了,这时的我,内牛满面。依旧打起精神去看bug , 具体如下:
Writing exception to parcel
java.lang.SecurityException: Permission Denial: writing android.support.v4.content.FileProvider uri content://com.zhongke.weiduo.provider/root/storage/emulated/0/Android/data/com.zhongke.weiduo/files/Pictures/9348410c5f10dc07b76271c15ac65999.png from pid=27283, uid=10049 requires the provider be exported, or grantUriPermission()
at android.content.ContentProvider.enforceWritePermissionInner(ContentProvider.java:682)
at android.content.ContentProvider$Transport.enforceWritePermission(ContentProvider.java:497)
at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:469)
at android.content.ContentProvider$Transport.openAssetFile(ContentProvider.java:384)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:262)
at android.os.Binder.execTransact(Binder.java:565)
裁剪图片的Intent带有图片原始路径和存储路径,因此需要通过FileProvider处理两次URI。
脑袋中出现一个大大的问号,心里就纳闷了,为什么授予了读取权限和写入权限,还报错写入权限错误呢。
PS : 带着疑问,去上路,寻找与我一样内心受伤的小伙伴,交流一下感情,看是否可以互相安慰一下。
解决方式
去StackOverFlow搜索了file provider - permission denial
一把,还真的,让我找到了相关答案。
有位小伙伴提出一个解释:
在Intent中添加Flag权限前,需要
Context.grantUriPermission()
。
这里,编写一个工具类,具体代码如下:
public class FileProviderUtils {
/**
* 为Intent中文件路径的Uri,授予读写的权限
* @param context
* @param intent
* @param uri
*/
public static void grantUriPermission(Context context , Intent intent,Uri uri){
List<ResolveInfo> resolveInfoList=context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo:resolveInfoList){
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
}
实现Android裁剪功能,完整代码,如下:
/**
* 根据Uri裁剪图片
*
* @param uri
*/
private void crop(Uri uri) {
// 裁剪图片意图
Intent intent = new Intent("com.android.camera.action.CROP");
if (Build.VERSION.SDK_INT>=24){
Log.i(tag,"裁剪前的图片路径:"+uri.getEncodedPath());
Uri newUri=FileProvider.getUriForFile( getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider",new File(uri.getEncodedPath()));
intent.setDataAndType(newUri ,"image/*");
FileProviderUtils.grantUriPermission(this,intent,uri);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_READ_URI_PERMISSION);
}else {
intent.setDataAndType(uri, "image/*");
}
intent.putExtra("crop", "true");
// 裁剪框的比例,1:1
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// 裁剪后输出图片的尺寸大小
intent.putExtra("outputX", 250);
intent.putExtra("outputY", 250);
// 图片格式
intent.putExtra("outputFormat", Bitmap.CompressFormat.PNG.toString());
// 取消人脸识别
intent.putExtra("noFaceDetection", true);
File cropFile = BitmapUtils.getDiskFile(getApplicationContext(), BitmapUtils.getBitmapFileName());
currentPicturePath = cropFile.getAbsolutePath();
if (Build.VERSION.SDK_INT>=24){
Log.i(tag,"裁剪后的存储的图片路径:"+currentPicturePath);
Uri saveFileUri= FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID + ".provider",cropFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT,saveFileUri);
FileProviderUtils.grantUriPermission(this,intent,saveFileUri);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_READ_URI_PERMISSION);
}else{
//设置裁剪后文件保存路径
Uri cropUri = Uri.fromFile(cropFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, cropUri);
}
// 开启一个带有返回值的Activity,请求码为PHOTO_REQUEST_CUT
startActivityForResult(intent, PHOTO_REQUEST_CUT);
}
裁剪成功后,onActivityResult()
返回,裁剪成功标识,则加载图片的代码,由各位小伙伴自行解决。
注意点:以上代码适配Android7.0以上和7.0以下API,实现裁剪功能。
长征路漫漫,终于搞定了,效果如下所示:
资源参考: