我使用以下方式启动图像选择器意图:
final Intent pickIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
startActivityForResult(intent, PICK_IMAGE);
并在
onActivityResult()
中,我获取所有选取图像的uri,并启动在后台运行的作业并上传这些图像(https://github.com/yigit/android-priority-jobqueue)。但是,如果我按后退按钮并退出活动,则任何未启动的作业在运行时都无法访问选取的图像并引发异常:
java.lang.SecurityException:权限拒绝:从 ProcessRecord{...} (pid=2407,uid=10117)打开未从 uid 10123 导出的提供程序 com.google.android.apps.photos.contentprovider.MediaContentProvider
发生这种情况的原因是活动完成后权限就会被撤销。根据文档https://developer.android.com/guide/topics/providers/content-provider-basics.html:
这些是特定内容 URI 的权限,持续到接收它们的活动完成为止。
我的问题是,有解决方法吗?比如在应用程序级别获得许可之类的?
有什么替代方案可以解决这个问题?一个快速的解决方案似乎是复制所有选定的图像,然后上传它们,但这似乎是最后的手段。
注意: 尝试从 uri 获取文件名是错误的。不要这样做!内容提供商还可以共享任何文件中不存在的任意数据,或者文件名可能会产生误导。例如。
content://downloads/some_secret_data
可能指向不在 downloads
文件夹中的文件。
所以最好的办法是立即从内容提供商读取/复制数据,然后使用该数据做任何你想做的事情。就我而言,我正在上传它。
这就是我所做的,对我来说效果很好。如果有人有更好的解决方案请分享。 我有
android.permission.READ_EXTERNAL_STORAGE
,所以当用户选择图像时,我使用具体的文件路径而不是 uri
中返回的 onActivityResult()
。为了检索文件路径,我使用了这个方便的类https://stackoverflow.com/a/20559175/826606,瞧!
我通过以下代码解决了这个问题: 当我们从谷歌照片应用程序获取图像时,我们需要图像的 realPath
//get Path
@TargetApi(Build.VERSION_CODES.KITKAT)
public String getRealPathFromURI(final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{
split[1]
};
return getDataColumn(contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
} else
return getRealPathFromURIDB(uri);
return null;
}
/**
* Gets real path from uri.
*
* @param contentUri the content uri
* @return the real path from uri
*/
private String getRealPathFromURIDB(Uri contentUri) {
Cursor cursor = context.getContentResolver().query(contentUri, null, null, null, null);
if (cursor == null) {
return contentUri.getPath();
} else {
cursor.moveToFirst();
int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
String realPath = cursor.getString(index);
cursor.close();
return realPath;
}
}
/**
* Gets data column.
*
* @param uri the uri
* @param selection the selection
* @param selectionArgs the selection args
* @return the data column
*/
public String getDataColumn(Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* Is external storage document boolean.
*
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* Is downloads document boolean.
*
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* Is media document boolean.
*
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* Is google photos uri boolean.
*
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
现在在 onActivityResult 方法中:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
if (requestCode == ChoosePhoto.CHOOSE_PHOTO_INTENT) {
if (data != null && data.getData() != null) {
handleGalleryResult(data);
} else {
handleCameraResult(choosePhoto.getCameraUri());
}
} else if (requestCode == ChoosePhoto.SELECTED_IMG_CROP) {
mImgProfile.setImageURI(choosePhoto.getCropImageUrl());
}
}
}
现在在handleGalleryResult方法中:
public void handleGalleryResult(Intent data) {
try {
cropPictureUrl = Uri.fromFile(createImageTempFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)));
String realPathFromURI = FileUtil.getRealPathFromURI(data.getData());
File file = new File(realPathFromURI);
if(file.exists()) {
if(currentAndroidDeviceVersion>23){
cropImage(FileProvider.getUriForFile(mContext, mContext.getApplicationContext().getPackageName() + ".provider", file), cropPictureUrl);
}else{
cropImage(Uri.fromFile(file), cropPictureUrl);
}
} else
cropImage(data.getData(), cropPictureUrl);
} catch (IOException e) {
e.printStackTrace();
}
}
创建图像临时文件:
@SuppressLint("SimpleDateFormat")
public File createImageTempFile(File filePathDir) throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
return File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
filePathDir /* directory */
);
}
这个问题有一个比已接受的有关读取/复制的答案更好的解决方案。
基本上,您必须使用不同的意图并请求永久的 Uri 权限,该权限在您重新启动后仍然存在。
解决方案在这里:重新启动后,图库中的图像的权限会丢失
使用以下代码复制 URI 中提供的文件内容并将其写入另一个文件。只有在阅读了 M-Wajeeh 在这里提出的答案后,我才能想出这个解决方案。
InputStream in = getContentResolver().openInputStream(/** Your file Uri */);
OutputStream out = new FileOutputStream(/** Output file */);
byte[] buf = new byte[1024];
int len;
while((len=in.read(buf))>0){
out.write(buf,0,len);
}
out.close();
in.close();
我允许用户选择目录以将文件保存在所选目录下,它工作正常,但是当我从用户使用的共享首选项获取目录路径时,它会显示权限拒绝“无法创建文档 java.lang.SecurityException:权限拒绝:从 ProcessRecord{14e48ae 12161:com.example.pdfproject/u0a305} (pid=12161,uid=10305) 打开提供程序 com.android.externalstorage.ExternalStorageProvider 要求您使用 ACTION_OPEN_DOCUMENT 或相关内容获取访问权限蜜蜂 在 android.os.Parcel.createExceptionOrNull(Parcel.java:2374) 在 android.os.Parcel.createException(Parcel.java:2358) 在 android.os.Parcel.readException(Parcel.java:2341) 在 android.os.Parcel.readException(Parcel.java:2283)" 我已请求运行时权限,也在menifest中但同样的问题