Android 13(API 33)PDF运行时权限请求问题及跨版本适配方案
Android 全API版本文件访问权限适配方案(解决API33权限弹窗不显示问题)
问题根源
- API33权限变更:Android13起废弃了
READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE的弹窗请求,系统直接默认"拒绝"这类权限申请,导致你看不到权限对话框。 - 无需权限的场景:通过
ACTION_GET_CONTENT调用系统文件选择器访问PDF时,根本不需要存储权限,文件选择器会自动处理文件访问逻辑。 - 路径获取失效:
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)) } } }
关键修改说明
- 权限逻辑优化:针对API33+和低版本分别处理,PDF无需权限、图片用新权限。
- 文件名获取:用
MediaStore.MediaColumns.DISPLAY_NAME查询文件名,兼容所有Android版本。 - 代码复用:提取
openFileSelector方法避免重复代码,逻辑更清晰。 - 移除无效权限:删除了
WRITE_EXTERNAL_STORAGE请求,文件选择器模式下不需要写入权限。
内容的提问来源于stack exchange,提问作者Waseem Idrisi
相关产品推荐
相关产品推荐

