获取权限拒绝异常

24

我在我的应用中有一个活动,允许用户逐个从设备上选择多个文件,我正在使用类似于以下的意图:

Intent intent = new Intent();
intent.setType("*/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_attachments_activity_chooser_label)), SELECT_PICTURE);

一切正常,我成功获取了所选文件的Uri,它们看起来是这样的:

content://com.android.providers.media.documents/document/image%3A42555

然后,如果文件是图像,我会使用以下方式进行解码:

InputStream streamForDecodeBitmap = MyApp.getContext().getContentResolver().openInputStream(uri);
Bitmap bitmap = BitmapFactory.decodeStream(streamForDecodeBitmap, null, options);

当用户点击按钮时,我通过意图将Uri列表传递给另一个活动,并在此活动中的AsyncTask中对文件进行Base64编码以将其发送到网络上:

InputStream is = MyApp.getContext().getContentResolver().openInputStream(uri);
byte[] inputData = getBytes(is);
is.close();
return Base64.encodeToString(inputData, Base64.DEFAULT);

问题在于当我打开inputStream时,有时它可以正常工作,但大多数情况下我会遇到这个异常:

E/AndroidRuntime(22270): Caused by: java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{42858fe0 22270:co.uk.manifesto.freeagentapp/u0a246} (pid=22270, uid=10246) requires android.permission.MANAGE_DOCUMENTS or android.permission.MANAGE_DOCUMENTS

这是我清单中的所有权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS"/>

我正在使用安卓4.4的设备(API 19)进行测试。

6个回答

37

请查看以下相关问题:
Android KitKat 尝试读取 MediaStore 时出现 SecurityException
Permission denial: 打开提供程序

为了解决KitKat上的同样问题,我使用了这个选项/解决方法。我在Stack Overflow链接中找到的,您可以从“下载/最近”中选择文件。

public static final int KITKAT_VALUE = 1002;

Intent intent;

if (Build.VERSION.SDK_INT < 19) {
    intent = new Intent();
    intent.setAction(Intent.ACTION_GET_CONTENT);
    intent.setType("*/*");
    startActivityForResult(intent, KITKAT_VALUE);
} else {
    intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("*/*");
    startActivityForResult(intent, KITKAT_VALUE);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == KITKAT_VALUE ) {
        if (resultCode == Activity.RESULT_OK) {
            // do something here
        }
    }
}

参考: Android Gallery on KitKat returns different Uri for Intent.ACTION_GET_CONTENT http://developer.android.com/guide/topics/providers/document-provider.html#client


22

当控制权和数据返回到您的活动中时

protected void onActivityResult(int reqCode, int resultCode, Intent data)

同时,您的活动被授予权限以访问传递意图中的图像URL。您的活动将能够读取意图中的URL并显示图像。但是,您无法将任何权限传递给其他活动或新的执行线程。这就是为什么您收到android.permission.MANAGE_DOCUMENTS的SecurityException。

避免权限问题的一种方法就是在拥有权限的活动中获取数据。在您的情况下,从具有权限的活动中进行编码。

InputStream is = getContentResolver().openInputStream(uri);
byte[] inputData = getBytes(is);
is.close();
String encoded = Base64.encodeToString(inputData, Base64.DEFAULT);

一旦您获得了编码字符串,您可以将其传递给可能需要它的任何其他活动,或者您可以将其传递给AsyncTask以发送到其他地方。


非常好的答案,我已经重构了我的代码,但还没有在引发此问题的设备上进行测试。这很有道理,因为在我的情况下,我正在使用ACTION_NEW_PICTURE和ACTION_NEW_VIDEO在BroadcastReceiver中监听新照片/视频。我已经将构建媒体查询的操作移动到UI线程中,然后将光标传递给后台任务进行处理。 - Meanman
有没有官方文档说明权限不会传递给“新的执行线程”? - Mark

10

请确保在请求内容的Activity中使用ContentResolver,例如:

activity.getContentResolver()  

不要使用MyApp.getContext().getContentResolver()

在我的情况下显然不需要将android.permission.MANAGE_DOCUMENTS添加到清单中。

使用以下方式调用意图:

if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
    intent.setAction(Intent.ACTION_GET_CONTENT);
} else {
    intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
}

更多信息:https://developer.android.com/guide/topics/providers/document-provider.html#client


6

ACTION_OPEN_DOCUMENT并不能完全解决问题。即使使用此操作,在重新启动后,您的URI权限也会消失。

   if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            intent.setAction(Intent.ACTION_GET_CONTENT);
        } else {
            intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
        }

为了补充Alécio Carvalho的解答,可以在同一页上查看关于URI权限和如何使其持久化的文档。该文档有详细的解释。
只需将以下内容添加到您的onActivityResult方法中,您的权限就可以持久存在设备重启后。(已在Google Pixel上进行过测试。)
override fun onActivityResult(requestCode:Int, resultCode:Int, intent:Intent?) {
if(requestCode == SELECT_PICTURE && intent != null){
    val uri = intent.getData()
    val takeFlags = intent.getFlags() and (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
    activity.getContentResolver().takePersistableUriPermission(uri, takeFlags)
    PreferenceHelper.setHeaderImageUri(activity, uri.toString())
  }
}

(抱歉,kotlin爱好者...java版本在链接内)


0
值得注意的是,如果用户手动删除您之前保存的 Uri 的权限,则会出现相同的异常。

Android 设置 -> 应用程序 -> 应用程序名称 -> 存储 -> 清除访问权限

在使用 Uri 时,您需要检查是否仍然具有访问权限。

1
当您需要再次访问时,您如何重新获取访问权限? - mparkes
您将需要再次请求用户的许可。 - Knossos

-3
在 Kotlin 中,通过在 Intent 后面放置运算符 "?" = (nullable) 来解决问题。
代码如下所示:
override func onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 
    super.onActivityResult(requestCode, resultCode, data)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接