Android 10/11/12下从URI获取PDF文件触发两类SecurityException问题
解决方案:修复Android 10-12下从URI生成PDF的权限异常
异常原因分析
1. WhatsApp MediaProvider权限拒绝
这个异常是因为WhatsApp的MediaProvider未对外导出(exported=false),直接调用contentResolver.query()访问其Uri时,你的App没有被授予临时访问权限。通常这种Uri如果是通过系统文件选择器或带权限的分享Intent获取的,系统会自动授予临时权限,但如果Uri来源不规范(比如剪贴板、未传递权限的第三方分享),就会触发权限拒绝。
2. 外部存储Downloads目录访问权限不足
Android 10+启用分区存储后,直接访问content://media/external/downloads/下的Uri需要对应的读取权限,且如果Uri不是通过系统文件选择器获取的,即使申请了READ_EXTERNAL_STORAGE权限,也可能因为分区存储的限制无法访问。同时代码中直接使用asset!!会导致空指针风险,且未处理权限异常。
具体修复步骤
1. 确保Uri的合法获取方式
所有用于生成PDF的Uri必须通过以下方式获取:
- 使用系统文件选择器Intent(
ACTION_OPEN_DOCUMENT),系统会自动授予临时读写权限:val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/pdf" // 可选:允许持久化权限 flags = flags or Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION } startActivityForResult(intent, REQUEST_CODE_PICK_PDF) - 如果是第三方应用分享的Uri,确保对方应用在Intent中添加了
FLAG_GRANT_READ_URI_PERMISSION标记,否则无法访问。
2. 替换Query逻辑,安全获取文件名
不要直接调用contentResolver.query(),改用DocumentFile安全获取文件名,避免权限异常:
val documentFile = DocumentFile.fromSingleUri(activity, pdfFileNames) displayName = documentFile?.name ?: uriString.split("/").last()
3. 权限检查与异常处理
- 针对Android 10-12,确保已申请
READ_EXTERNAL_STORAGE权限(Android 13+改用READ_MEDIA_DOCUMENTS); - 对
openInputStream()添加异常捕获,避免直接使用!!引发空指针; - 添加强制的权限检查和异常处理逻辑。
4. 持久化Uri权限(可选)
如果需要长期访问选中的PDF文件,在获取Uri后调用:
activity.contentResolver.takePersistableUriPermission( pdfFileNames, Intent.FLAG_GRANT_READ_URI_PERMISSION )
修复后的完整函数代码
fun getPdfFileFromUri(activity: Context, pdfUri: Uri): File? { return try { var displayName: String? = null val uriString = pdfUri.toString() if (uriString.startsWith("content://")) { // 安全获取文件名,避免query权限问题 val documentFile = DocumentFile.fromSingleUri(activity, pdfUri) displayName = documentFile?.name ?: uriString.split("/").last() val myFile = File(activity.cacheDir, displayName) FileOutputStream(myFile).use { output -> // 捕获权限异常,避免崩溃 activity.contentResolver.openInputStream(pdfUri)?.use { input -> val buffer = ByteArray(1024) var size: Int while (input.read(buffer).also { size = it } != -1) { output.write(buffer, 0, size) } } ?: throw IOException("无法打开输入流") } myFile } else { File(uriString).takeIf { it.exists() } } } catch (e: SecurityException) { // 处理权限异常,可上报日志或提示用户 e.printStackTrace() null } catch (e: IOException) { // 处理文件操作异常 e.printStackTrace() null } }
额外注意事项
- 永远不要假设Uri一定可访问,必须添加异常捕获;
- 避免直接操作外部存储路径,优先使用系统提供的ContentResolver和DocumentFile;
- 针对Android 13+,需要适配新的媒体权限(
READ_MEDIA_DOCUMENTS),替代READ_EXTERNAL_STORAGE。
内容的提问来源于stack exchange,提问作者Laksh Lathiya
相关产品推荐
相关产品推荐

