You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 03:21:04