Android拖拽外部文件至应用读取时权限异常问题求助
问题描述
我希望在Android应用中实现拖拽功能:从文件管理器拖拽文件至应用内,读取并展示文件内容,不使用文件选择器,仅通过OnDragListener中的ClipData获取Uri来实现。请问这一方案是否可行?
当我尝试读取文件时触发了权限异常,提示需要通过ACTION_OPEN_DOCUMENT或相关API获取权限。
报错代码与异常信息
读取文件的核心代码:
FileInputStream inputStream = getContentResolver().openInputStream(uri);
抛出的异常信息:
java.lang.SecurityException: UID 10366 does not have permission to content://com.android.externalstorage.documents/document/... [user 0]; you could obtain access using ACTION_OPEN_DOCUMENT or related APIs
java.lang.SecurityException: Permission Denial: opening provider com.android.externalstorage.ExternalStorageProvider from ProcessRecord{ecf77b0 23430:com.app.package.name/u0a366} (pid=23430, uid=10366) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
相关实现代码
拖拽事件监听代码:
dropTarget.setOnDragListener((v, event) -> { switch (event.getAction()) { ... case DragEvent.ACTION_DROP: if (event.getClipDescription().hasMimeType("application/json")) { ClipData.Item item = event.getClipData().getItemAt(0); readFile(item.getUri()); } return true; } return false; });
文件读取方法:
void readFile(Uri uri){ FileInputStream inputStream = getContentResolver().openInputStream(uri); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); //Reading file ... }
解决方案
这个方案完全可行,问题出在没有正确获取拖拽操作的临时权限,修复步骤如下:
核心原因
通过拖拽获取的content://类型Uri默认没有读取权限,Android要求必须通过系统授予的临时权限才能访问这类Uri,而拖拽场景下可以直接通过DragEvent申请临时权限。
代码修复
在ACTION_DROP事件分支中,先调用requestDragAndDropPermissions获取临时权限,再读取文件:
case DragEvent.ACTION_DROP: if (event.getClipDescription().hasMimeType("application/json")) { // 获取拖拽操作的临时权限 DragAndDropPermissions permissions = requestDragAndDropPermissions(event); ClipData.Item item = event.getClipData().getItemAt(0); readFile(item.getUri()); // 权限可主动释放,也会在Activity销毁时自动失效 // permissions.release(); } return true;
关键说明
requestDragAndDropPermissions是Android 8.0(API 26)引入的方法,会为当前拖拽的Uri授予临时读取权限,权限有效期到Activity销毁或主动调用release()为止。- 无需申请
READ_EXTERNAL_STORAGE这类危险权限,临时权限由系统通过拖拽操作直接授予。 - 若需兼容API 26以下版本,可通过
event.getClipDescription().getIntent()获取包含权限的Intent,但推荐优先适配高版本系统。
内容的提问来源于stack exchange,提问作者SlaVcE

