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

Android 13(API 33)PDF运行时权限请求问题及跨版本适配方案

Android 全API版本文件访问权限适配方案(解决API33权限弹窗不显示问题)

问题根源

  1. API33权限变更:Android13起废弃了READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE的弹窗请求,系统直接默认"拒绝"这类权限申请,导致你看不到权限对话框。
  2. 无需权限的场景:通过ACTION_GET_CONTENT调用系统文件选择器访问PDF时,根本不需要存储权限,文件选择器会自动处理文件访问逻辑。
  3. 路径获取失效:getRealPathFromURI在Android10+的Scoped Storage机制下已无法获取真实路径,依赖该方法会导致文件名获取失败或崩溃。

适配方案

1. 权限动态适配逻辑

  • API33+:
    • 访问图片:请求READ_MEDIA_IMAGES权限
    • 访问PDF:无需任何权限,直接打开文件选择器
  • API33及以下:
    • 统一请求READ_EXTERNAL_STORAGE权限(读写权限已合并,只需要读权限即可)

2. 替换文件名获取方法

不再依赖真实路径,通过ContentResolver直接从Uri查询文件名。

3. 修正权限请求与文件选择逻辑


修改后的完整代码

// 所有与从相册选择图片、PDF相关的变量和方法
private val REQUEST_PERMISSIONS_CODE: Int = 123
private var lType: Int = 0 // 确保变量已初始化

// 处理文件选择结果
private val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        val data: Intent? = result.data
        data?.data?.let { uri ->
            when(lType) {
                1 -> {
                    // 处理PDF文件
                    val fileName = getFileNameFromUri(uri)
                    binding.imgGstName.text = fileName
                }
                2 -> {
                    // 处理图片文件(原代码重复赋值多个Uri,可根据实际需求调整)
                    val fileName = getFileNameFromUri(uri)
                    binding.imgCancelledChequeName.text = fileName
                }
            }
        }
    }
}

// 通过ContentResolver从Uri获取文件名
private fun getFileNameFromUri(uri: Uri): String {
    var fileName = "未知文件"
    val projection = arrayOf(MediaStore.MediaColumns.DISPLAY_NAME)
    contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
        if (cursor.moveToFirst()) {
            val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
            fileName = cursor.getString(columnIndex)
        }
    }
    return fileName
}

// 多权限请求处理器
private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
    val granted = permissions.values.all { it }
    if (granted) {
        openFileSelector()
    } else {
        // 权限被拒绝,可提示用户前往设置开启权限
        Toast.makeText(this, "权限被拒绝,无法选择文件", Toast.LENGTH_SHORT).show()
    }
}

// 根据类型打开对应的文件选择器
private fun openFileSelector() {
    val intent = when(lType) {
        1 -> {
            Intent(Intent.ACTION_GET_CONTENT).apply {
                type = "application/pdf"
                addCategory(Intent.CATEGORY_OPENABLE)
            }
        }
        2 -> {
            Intent(Intent.ACTION_GET_CONTENT).apply {
                type = "image/*"
                addCategory(Intent.CATEGORY_OPENABLE)
            }
        }
        else -> return
    }
    getContent.launch(intent)
}

// GST证书上传点击事件
binding.uploadGstCertificate.setOnClickListener {
    lType = 1
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        // API33+访问PDF无需权限,直接打开选择器
        openFileSelector()
    } else {
        // API33以下检查读存储权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            openFileSelector()
        } else {
            requestPermissionLauncher.launch(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE))
        }
    }
}

// 作废支票上传点击事件
binding.uploadCancelledCheque.setOnClickListener {
    lType = 2
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        // API33+访问图片需要请求READ_MEDIA_IMAGES权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) == PackageManager.PERMISSION_GRANTED) {
            openFileSelector()
        } else {
            requestPermissionLauncher.launch(arrayOf(Manifest.permission.READ_MEDIA_IMAGES))
        }
    } else {
        // API33以下检查读存储权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            openFileSelector()
        } else {
            requestPermissionLauncher.launch(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE))
        }
    }
}

关键修改说明

  1. 权限逻辑优化:针对API33+和低版本分别处理,PDF无需权限、图片用新权限。
  2. 文件名获取:用MediaStore.MediaColumns.DISPLAY_NAME查询文件名,兼容所有Android版本。
  3. 代码复用:提取openFileSelector方法避免重复代码,逻辑更清晰。
  4. 移除无效权限:删除了WRITE_EXTERNAL_STORAGE请求,文件选择器模式下不需要写入权限。

内容的提问来源于stack exchange,提问作者Waseem Idrisi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 23:05:05