从图库中选取图像会出现错误 Android N
Picking an image from gallery gives error Android N
我无法从 Android N 中的意图检索 Uri。
据我所知,在 Android 24 及更高版本上,要获得外部 Uri,您需要在 Manifest
中声明 FileProvider
。这一切都完成了,它可以与相机一起使用,但是当我尝试从图库中获取图像时,我在 onActivityResult data.getData();
中遇到错误
这些是我的代码示例:
public void getPictureFromGallery(){
picUriCar = null;
try {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, PIC_SELECT);
} catch (ActivityNotFoundException e) {
Toast.makeText(MainActivity.this, getResources().getString(R.string.text_error_no_gallery_app),
Toast.LENGTH_SHORT).show();
}
}
和onActivityResult
:
else if(requestCode == PIC_SELECT){
picUriCar = data.getData();
if (picUriCar != null){
performCrop();
}
}
据我所知 data.getData()
returns 一个 Uri,这在 Marshmallow 上工作正常,但在 Nougat phone 上我得到这个错误:
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=5, result=-1, data=Intent {
dat=content://com.android.externalstorage.documents/document/4996-1EFF:DCIM/100ANDRO/DSC_0004.JPG
flg=0x1 }} to activity
{com.company.example/com.company.example.MainActivity}:
java.lang.SecurityException: Uid 10246 does not have permission to uri
0 @
content://com.android.externalstorage.documents/document/4996-1EFF%3ADCIM%2F100ANDRO%2FDSC_0004.JPG
at android.app.ActivityThread.deliverResults(ActivityThread.java:4267)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4310)
at android.app.ActivityThread.-wrap20(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1628)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loop(Looper.java:203)
at android.app.ActivityThread.main(ActivityThread.java:6361)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924)
Caused by: java.lang.SecurityException: Uid 10246 does not have permission to uri 0 @
content://com.android.externalstorage.documents/document/4996-1EFF%3ADCIM%2F100ANDRO%2FDSC_0004.JPG
at android.os.Parcel.readException(Parcel.java:1683)
at android.os.Parcel.readException(Parcel.java:1636)
at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:3213)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1525)
at android.app.Activity.startActivityForResult(Activity.java:4235)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:767)
at android.app.Activity.startActivityForResult(Activity.java:4194)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:754)
at com.company.example.MainActivity.performCrop(MainActivity.java:1654)
at com.company.example.MainActivity.onActivityResult(MainActivity.java:1534)
at android.app.Activity.dispatchActivityResult(Activity.java:6928)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4263)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4310)
at android.app.ActivityThread.-wrap20(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1628)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loop(Looper.java:203)
at android.app.ActivityThread.main(ActivityThread.java:6361)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924)
我的问题是:
如何在没有任何错误的情况下将 data.getData()
uri 传递给 picUriCar?
使用此代码创建选择图像的新意图:
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setType(image/*);
startActivityForResult(Intent.createChooser(intent, ""), Constants.SELECT_PICTURE);
在onActivityResult
中使用此代码:
if (resultCode == Activity.RESULT_OK) {
if (requestCode == Constants.SELECT_PICTURE) {
Uri selectedImageUri = data.getData();
try {
if (isNewGooglePhotosUri(selectedImageUri)) {
resultFile = getPhotoFile(selectedImageUri);
} else {
resultFile = getFilePathForGallery(selectedImageUri);
}
if (resultFile == null) {
//error
return;
}
} catch (Exception e) {
e.printStackTrace();
//error
return;
}
}
}
}
这里还有一些我在代码中使用的有用函数:
private File getFilePathForGallery(Uri contentUri) {
String path = null;
String[] proj = {MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
if (cursor.moveToFirst()) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
path = cursor.getString(column_index);
}
cursor.close();
return new File(path);
}
public static boolean isNewGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.contentprovider".equals(uri.getAuthority());
}
private File getPhotoFile(Uri selectedImageUri) {
try {
InputStream is = mActivityInstance.getContentResolver().openInputStream(selectedImageUri);
if (is != null) {
Bitmap pictureBitmap = BitmapFactory.decodeStream(is);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
pictureBitmap.compress(Bitmap.CompressFormat.JPEG, 80, bytes);
File output = new File(FileManager.getImageCacheDir(mActivityInstance), System.currentTimeMillis() + ".jpg");
output.createNewFile();
FileOutputStream fo = new FileOutputStream(output);
fo.write(bytes.toByteArray());
fo.close();
return output;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
FileManager 中的函数 class:
private static File getCacheDir(Context context) {
File cacheDir = context.getExternalFilesDir(null);
if (cacheDir != null) {
if (!cacheDir.exists())
cacheDir.mkdirs();
} else {
cacheDir = context.getCacheDir();
}
return cacheDir;
}
public static File getImageCacheDir(Context context) {
File imageCacheDir = new File(getCacheDir(context), "cache_folder");
if (!imageCacheDir.exists())
imageCacheDir.mkdirs();
return imageCacheDir;
}
您还需要在 xml 文件夹中创建新的 xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_files"
path="." />
</paths>
然后将新的 provider
添加到清单文件:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/your_xml_file" />
</provider>
我做了一些检查,一如既往,解决方案比我预期的要简单。这是我的 performCrop 函数。单击视图时,我有两个选项。从图库中获取图像或使用相机捕捉图像。我记得画廊选项问题是在我修复相机问题后才出现的。所以问题可能出在作物代码中的某个地方。这就是我所做的:
public void performCrop(boolean cameraShot){
try {
Intent cropIntent = new Intent("com.android.camera.action.CROP");
//indicate image type and Uri
//cropIntent.setDataAndType(picUri, "image");
cropIntent.setDataAndType(picUriCar, "image/*");
//set crop properties
cropIntent.putExtra("crop", "true");
//indicate aspect of desired crop
cropIntent.putExtra("aspectX", 2);
cropIntent.putExtra("aspectY", 1);
//indicate output X and Y
cropIntent.putExtra("outputX", 1024);
cropIntent.putExtra("outputY", 512);
cropIntent.putExtra("scale", true);
cropIntent.putExtra("return-data", false);
Uri uri;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !cameraShot) {
uri = Uri.fromFile(croppedFileCar);
}else{
String authority = "com.company.example.provider";
uri = FileProvider.getUriForFile(
MainActivity.this,
authority,
croppedFileCar);
cropIntent.setClipData(ClipData.newRawUri( "", uri ) );
cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
Please notice - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N ||
!cameraShot)
所以基本上它背后的逻辑是,如果图像不是由相机提供的(在onActivityResult中确定)或者API低于24,执行行:
uri = Uri.fromFile(croppedFileCar);
否则使用 FileProvider。在我进行这些更改之前,应用程序在从图库中选择图像时崩溃了。我不知道为什么它现在有效,但如果有人知道答案,我会很高兴听到。
我无法从 Android N 中的意图检索 Uri。
据我所知,在 Android 24 及更高版本上,要获得外部 Uri,您需要在 Manifest
中声明 FileProvider
。这一切都完成了,它可以与相机一起使用,但是当我尝试从图库中获取图像时,我在 onActivityResult data.getData();
这些是我的代码示例:
public void getPictureFromGallery(){
picUriCar = null;
try {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, PIC_SELECT);
} catch (ActivityNotFoundException e) {
Toast.makeText(MainActivity.this, getResources().getString(R.string.text_error_no_gallery_app),
Toast.LENGTH_SHORT).show();
}
}
和onActivityResult
:
else if(requestCode == PIC_SELECT){
picUriCar = data.getData();
if (picUriCar != null){
performCrop();
}
}
据我所知 data.getData()
returns 一个 Uri,这在 Marshmallow 上工作正常,但在 Nougat phone 上我得到这个错误:
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=5, result=-1, data=Intent { dat=content://com.android.externalstorage.documents/document/4996-1EFF:DCIM/100ANDRO/DSC_0004.JPG flg=0x1 }} to activity {com.company.example/com.company.example.MainActivity}: java.lang.SecurityException: Uid 10246 does not have permission to uri 0 @ content://com.android.externalstorage.documents/document/4996-1EFF%3ADCIM%2F100ANDRO%2FDSC_0004.JPG at android.app.ActivityThread.deliverResults(ActivityThread.java:4267) at android.app.ActivityThread.handleSendResult(ActivityThread.java:4310) at android.app.ActivityThread.-wrap20(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1628) at android.os.Handler.dispatchMessage(Handler.java:110) at android.os.Looper.loop(Looper.java:203) at android.app.ActivityThread.main(ActivityThread.java:6361) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924) Caused by: java.lang.SecurityException: Uid 10246 does not have permission to uri 0 @ content://com.android.externalstorage.documents/document/4996-1EFF%3ADCIM%2F100ANDRO%2FDSC_0004.JPG at android.os.Parcel.readException(Parcel.java:1683) at android.os.Parcel.readException(Parcel.java:1636) at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:3213) at android.app.Instrumentation.execStartActivity(Instrumentation.java:1525) at android.app.Activity.startActivityForResult(Activity.java:4235) at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:767) at android.app.Activity.startActivityForResult(Activity.java:4194) at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:754) at com.company.example.MainActivity.performCrop(MainActivity.java:1654) at com.company.example.MainActivity.onActivityResult(MainActivity.java:1534) at android.app.Activity.dispatchActivityResult(Activity.java:6928) at android.app.ActivityThread.deliverResults(ActivityThread.java:4263) at android.app.ActivityThread.handleSendResult(ActivityThread.java:4310) at android.app.ActivityThread.-wrap20(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1628) at android.os.Handler.dispatchMessage(Handler.java:110) at android.os.Looper.loop(Looper.java:203) at android.app.ActivityThread.main(ActivityThread.java:6361) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924)
我的问题是:
如何在没有任何错误的情况下将 data.getData()
uri 传递给 picUriCar?
使用此代码创建选择图像的新意图:
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setType(image/*);
startActivityForResult(Intent.createChooser(intent, ""), Constants.SELECT_PICTURE);
在onActivityResult
中使用此代码:
if (resultCode == Activity.RESULT_OK) {
if (requestCode == Constants.SELECT_PICTURE) {
Uri selectedImageUri = data.getData();
try {
if (isNewGooglePhotosUri(selectedImageUri)) {
resultFile = getPhotoFile(selectedImageUri);
} else {
resultFile = getFilePathForGallery(selectedImageUri);
}
if (resultFile == null) {
//error
return;
}
} catch (Exception e) {
e.printStackTrace();
//error
return;
}
}
}
}
这里还有一些我在代码中使用的有用函数:
private File getFilePathForGallery(Uri contentUri) {
String path = null;
String[] proj = {MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
if (cursor.moveToFirst()) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
path = cursor.getString(column_index);
}
cursor.close();
return new File(path);
}
public static boolean isNewGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.contentprovider".equals(uri.getAuthority());
}
private File getPhotoFile(Uri selectedImageUri) {
try {
InputStream is = mActivityInstance.getContentResolver().openInputStream(selectedImageUri);
if (is != null) {
Bitmap pictureBitmap = BitmapFactory.decodeStream(is);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
pictureBitmap.compress(Bitmap.CompressFormat.JPEG, 80, bytes);
File output = new File(FileManager.getImageCacheDir(mActivityInstance), System.currentTimeMillis() + ".jpg");
output.createNewFile();
FileOutputStream fo = new FileOutputStream(output);
fo.write(bytes.toByteArray());
fo.close();
return output;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
FileManager 中的函数 class:
private static File getCacheDir(Context context) {
File cacheDir = context.getExternalFilesDir(null);
if (cacheDir != null) {
if (!cacheDir.exists())
cacheDir.mkdirs();
} else {
cacheDir = context.getCacheDir();
}
return cacheDir;
}
public static File getImageCacheDir(Context context) {
File imageCacheDir = new File(getCacheDir(context), "cache_folder");
if (!imageCacheDir.exists())
imageCacheDir.mkdirs();
return imageCacheDir;
}
您还需要在 xml 文件夹中创建新的 xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_files"
path="." />
</paths>
然后将新的 provider
添加到清单文件:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/your_xml_file" />
</provider>
我做了一些检查,一如既往,解决方案比我预期的要简单。这是我的 performCrop 函数。单击视图时,我有两个选项。从图库中获取图像或使用相机捕捉图像。我记得画廊选项问题是在我修复相机问题后才出现的。所以问题可能出在作物代码中的某个地方。这就是我所做的:
public void performCrop(boolean cameraShot){
try {
Intent cropIntent = new Intent("com.android.camera.action.CROP");
//indicate image type and Uri
//cropIntent.setDataAndType(picUri, "image");
cropIntent.setDataAndType(picUriCar, "image/*");
//set crop properties
cropIntent.putExtra("crop", "true");
//indicate aspect of desired crop
cropIntent.putExtra("aspectX", 2);
cropIntent.putExtra("aspectY", 1);
//indicate output X and Y
cropIntent.putExtra("outputX", 1024);
cropIntent.putExtra("outputY", 512);
cropIntent.putExtra("scale", true);
cropIntent.putExtra("return-data", false);
Uri uri;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !cameraShot) {
uri = Uri.fromFile(croppedFileCar);
}else{
String authority = "com.company.example.provider";
uri = FileProvider.getUriForFile(
MainActivity.this,
authority,
croppedFileCar);
cropIntent.setClipData(ClipData.newRawUri( "", uri ) );
cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
Please notice - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !cameraShot)
所以基本上它背后的逻辑是,如果图像不是由相机提供的(在onActivityResult中确定)或者API低于24,执行行:
uri = Uri.fromFile(croppedFileCar);
否则使用 FileProvider。在我进行这些更改之前,应用程序在从图库中选择图像时崩溃了。我不知道为什么它现在有效,但如果有人知道答案,我会很高兴听到。