java.lang.SecurityException:Permission Denial:打开提供者

时间:2016-05-24 09:15:35

标签: android android-contentprovider android-permissions permission-denied

我使用以下方式启动图像选择器意图:

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()中获取所有已挑选图片的uris并启动在后台运行的作业并上传这些图像(https://github.com/yigit/android-priority-jobqueue)。但是,如果我按下后退按钮并退出活动,那么任何未启动的作业都无法在运行时访问所选图像并抛出异常:

  

java.lang.SecurityException:Permission Denial:打开来自ProcessRecord {...}的提供者com.google.android.apps.photos.contentprovider.MediaContentProvider(pid = 2407,uid = 10117),它不是从uid 10123导出的

发生这种情况的原因是,一旦活动结束,许可就会被撤销。根据文件https://developer.android.com/guide/topics/providers/content-provider-basics.html

  

这些是特定内容URI的权限,这些内容URI将持续到接收它们的活动完成为止。

我的问题是,有解决方法吗?就像在应用程序级别获得许可一样?

有什么替代方案可以解决这个问题?一个快速的解决方案似乎是制作所有选中图像的副本,然后上传它们,但这似乎是最后的手段。

3 个答案:

答案 0 :(得分:4)

注意:尝试从uri获取文件名错误。不要这样做!内容提供商还可以共享任何文件中不存在的任意数据或文件名可能会产生误导。例如。 content://downloads/some_secret_data可能指向不在downloads文件夹中的文件。

所以最好的办法是立即从内容提供商处读取/复制数据,然后使用该数据做任何你想做的事情。在我的情况下,我上传了它。

以前的错误答案(不要这样做!):

这就是我所做的,对我来说很好。如果有人有更好的解决方案,请分享。 我有android.permission.READ_EXTERNAL_STORAGE所以当用户选择图片时,我使用了具体的文件路径而不是uri中返回的onActivityResult()。为了检索文件路径,我使用了这个方便的类https://stackoverflow.com/a/20559175/826606并且瞧!

答案 1 :(得分:0)

  

我通过以下代码解决了这个问题:   当我们从谷歌照片应用程序获取图像时,我们需要使用realPath for image

//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();
    }
}
  

createImageTempFile:

@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 */
    );
}

答案 2 :(得分:0)

对于这个问题,有一个比接受的关于阅读/复制的答案更好的解决方案。

基本上你必须使用不同的意图并要求永久的Uri权限,这在你重新启动后仍然存在。

解决方案在这里:Permission for an image from Gallery is lost after re-launch