不确定我想做的事情是否可能。我有一个“代理”文档提供程序,意思是使用 SAF 导出其他内容的别名的文档提供程序。我有一个对话框片段,允许用户通过呈现 OPEN_DOCUMENT_TREE 意图、捕获 URI、授予该 URI 的持久权限,然后将该 URI 传递给文档提供程序来呈现该内容来设置别名。
对话框片段能够读取/写入从意图接收的 URI,但提供程序部分无法读取/写入。我总是遇到的错误是:
java.lang.SecurityException: Permission Denial: reading com.android.externalstorage.ExternalStorageProvider uri
我已经阅读了人们发布的有关使用这些持久 READ_URI_PERMISSIONS 的许多相关问题和相应回复,并尝试了这些帖子中建议的所有变体。但我无法让它发挥作用。我开始认为持久 URI 权限在提供程序中不可用。是这样吗?我是否需要让提供者调用意图以便我可以授予这些权限?也许让提供者调用主要活动来访问内容。我当然宁愿直接从提供商处访问内容。我还没有找到任何地方说明一旦授予持久权限,这些权限会授予谁?应用程序的提供程序部分是一个单独的进程吗?这就是问题所在吗?
让我提供相关的代码片段:
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.connectedway.connectedsmb">
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
...
<activity ...>
...
</activity>
<provider ...
android:permission="android.permission.MANAGE_DOCUMENTS">
</provider>
</application>
</manifest>
获取 URI 的对话框片段位于主活动内,使用 URI 的代码位于提供程序内。
主活动的对话框片段使用以下方式调用 OPEN_DOCUMENT_TREE 意图:
StorageManager sm = (StorageManager)
getContext().getSystemService(Context.STORAGE_SERVICE);
StorageVolume sv = sm.getPrimaryStorageVolume();
Intent intent = sv.createOpenDocumentTreeIntent();
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
onCreateResultLauncher.launch(intent);
带有 aswsociatd onActivityResult 回调的“onCreateResultLauncher”类是:
ActivityResultLauncher<Intent> onCreateResultLauncher =
registerForActivityResult
(new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
Uri uri = data.getData() ;
getActivity().grantUriPermission(
getActivity().getPackageName(), uri,
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
Intent.FLAG_GRANT_READ_URI_PERMISSION);
final int takeFlags =
data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION);
getContext().getContentResolver().takePersistableUriPermission(
uri, takeFlags);
// PASS THE URI OFF TO THE PROVIDER
}
}
}
});
在对话框片段中,我测试 URI 以查看是否可以读取它:
DocumentFile file = DocumentFile.fromTreeUri(getContext(), uri);
if (file.canRead())
System.out.println ("Can Read");
我们总是可以读书。现在提供者尝试按如下方式访问 URI:
@Override
public Cursor queryDocument(String documentId, String[] projection)
throws FileNotFoundException {
// convert the documentId passed in to a uri. The conversion process
// results in the same URI as that received by the OPEN_DOCUMENT_TREE
// intent invoked and tested above.
DocumentFile file = DocumentFile.fromTreeUri(getContext(), uri);
if (file.canRead())
System.out.println ("Can Read");
提供者没有读取权限。因此,当提供程序最终对 URI 进行查询时,它将失败并出现上面所示的 SecurityException。
问题是:
感谢您阅读这篇文章。
我已经弄清楚了。我必须改变一些东西,但我遇到的主要问题是如何构建我传递的 URI。以下是一些最相关的更改:
应用程序读取一个 URI,该 URI 希望在作为应用程序一部分的自定义文档提供程序中公开。启动时
Intent intent = sv.createOpenDocumentTreeIntent();
intent.addFlags
(Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
然后启动意图。用户选择导出到自定义文档提供程序的目录后,应用程序将收到结果回调:
Uri uri = data.getData() ;
ContentResolver cr = getContext().getContentResolver();
cr.takePersistableUriPermission
(uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
然后将 URI 传递给文档提供者,文档提供者为其创建根。文档提供者不需要明确获取任何权限。它 只需确保构建正确的 URI。例如:
Uri treeUri = Uri.parse(otgMap.getPath().getPath());
String externalDocId = // DocId to query
Uri externalUri = DocumentsContract.buildDocumentUriUsingTree
(treeUri, externalDocId);
ContentResolver cr = getContext().getContentResolver();
Cursor cursor = cr.query(externalUri, projection,
null, null, null);
一切都如我所愿。只需要在桌子上撞几下我的头就可以了。
Rich:感谢您分享您的答案。我也面临类似的异常,想知道除了解决问题所做的操作之外,您是否还需要添加以下所有标志。
应用程序读取一个 URI,该 URI 希望在作为应用程序一部分的自定义文档提供程序中公开。启动时
Intent intent = sv.createOpenDocumentTreeIntent();
intent.addFlags
(Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
谢谢,