Android 10 Kotlin应用读取外部存储文件权限异常后恢复问题求助
问题分析与解决方案
从你的描述和代码对比来看,导致两种情况出现差异的核心原因主要有两个,我来逐一拆解:
1. 权限申请回调未处理,首次授权后未触发读取逻辑
你的setupPermissions()方法只在onCreate()中调用了一次:
- 当应用首次启动时,权限未被授予,会触发
ActivityCompat.requestPermissions()弹出授权对话框; - 用户点击同意后,系统会自动回调
onRequestPermissionsResult()方法,但你的代码中没有重写这个方法来重新执行文件读取逻辑; - 这时候你看到的"读取失败"其实是首次
onCreate()中权限未通过时的结果,而授权成功后并没有再次尝试读取文件。
而你修改代码后再次测试时,大概率是重新启动了应用——这时候权限已经被系统记住,setupPermissions()会直接进入权限已授予的分支,自然能成功读取文件。
2. Android 10 Scoped Storage 限制(次要但需注意)
Android 10(API 29)默认启用了Scoped Storage,即使申请了READ_EXTERNAL_STORAGE权限,直接访问外部存储根目录的非媒体文件(比如你的number.txt)也会被系统限制,除非你在AndroidManifest.xml的<application>标签中添加兼容配置:
android:requestLegacyExternalStorage="true"
不过这个点不是你两种代码差异的核心原因(否则第二种情况也会失败),但如果后续你升级到更高版本的Android系统,这个配置会直接影响你的文件访问逻辑。
修复方案
方案一:处理权限申请回调,授权成功后重新读取文件
重写onRequestPermissionsResult()方法,当权限被授予时再次执行文件读取逻辑,同时把读取逻辑抽成单独方法方便复用:
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == 101) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 权限授予成功,执行文件读取 readNumberFile() } else { // 权限被拒绝,给用户友好提示 Toast.makeText(this, "需要读取存储权限才能获取文件内容", Toast.LENGTH_SHORT).show() } } } // 单独抽离文件读取逻辑 private fun readNumberFile() { val file = File("/storage/emulated/0/number.txt") try { val pln = file.readText() plnText.text = pln Log.i("IKO_APP", pln) } catch (e: Exception) { Log.e("IKO_APP", "读取文件失败:${e.message}") } } // 修改原setupPermissions方法 private fun setupPermissions() { val permission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) if (permission == PackageManager.PERMISSION_GRANTED) { readNumberFile() } else{ ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 101) } }
方案二:适配Scoped Storage(长期推荐)
如果你想遵循Android最新的存储规范,避免依赖requestLegacyExternalStorage兼容配置,可以使用**存储访问框架(SAF)**让用户手动选择文件,这种方式不需要申请READ_EXTERNAL_STORAGE权限:
// 启动系统文件选择器 private fun openFilePicker() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "text/*" // 指定选择文本文件 } startActivityForResult(intent, 102) } // 处理文件选择结果 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == 102 && resultCode == RESULT_OK) { data?.data?.let { uri -> contentResolver.openInputStream(uri)?.use { inputStream -> val pln = inputStream.bufferedReader().readText() plnText.text = pln Log.i("IKO_APP", pln) } } } }
内容的提问来源于stack exchange,提问作者astroboy




