Android 11读取文件遇FileNotFoundException及ANR问题求助
问题根源分析
旧问题(FileNotFoundException)
Android 11引入**分区存储(Scoped Storage)**后,App无法直接通过Uri转换为File对象访问非私有目录下的文件。比如通过文档选择器、媒体库获取的Uri,对应的文件路径对App完全不可见,直接用FileReader读取必然抛出文件不存在异常——这是系统权限限制导致的,并非代码写法错误。
更新后问题(ANR)
改用getContentResolver().openInputStream()本身是正确的适配方向,但如果在主线程执行文件读取操作,一旦文件较大或IO耗时较长,就会阻塞主线程超过5秒,触发ANR(应用无响应)。
解决方案
1. 遵循Scoped Storage规范,用ContentResolver处理文件
完全放弃直接操作File对象,通过ContentResolver提供的流API读取文件内容,这是Android 11及以上版本的标准做法。
2. 耗时IO操作必须放在后台线程
所有文件读取、写入操作都要放到子线程执行,读取完成后再切回主线程更新UI(比如TextView)。推荐用Kotlin协程、Java线程池实现,避免使用已废弃的AsyncTask。
3. 权限适配要点
- Android 11及以上,通过系统文档选择器获取的Uri无需额外申请
READ_EXTERNAL_STORAGE权限,系统已临时授权该Uri的访问权限 - 如果是访问媒体库文件,Android 11及以上也无需申请
READ_EXTERNAL_STORAGE,直接通过MediaStore或ContentResolver访问即可
修正后的代码示例
Kotlin(协程实现)
// 在Activity/Fragment中调用 private fun readFileContent(uri: Uri) { lifecycleScope.launch(Dispatchers.IO) { // 后台线程执行IO操作 val content = try { contentResolver.openInputStream(uri)?.use { inputStream -> inputStream.bufferedReader().readText() } ?: "文件读取失败" } catch (e: IOException) { "读取异常: ${e.message}" } // 切回主线程更新TextView withContext(Dispatchers.Main) { textView.text = content } } }
Java(线程池实现)
private void readFileContent(Uri uri) { ExecutorService executor = Executors.newSingleThreadExecutor(); Handler handler = new Handler(Looper.getMainLooper()); executor.execute(() -> { StringBuilder content = new StringBuilder(); try (InputStream inputStream = getContentResolver().openInputStream(uri)) { if (inputStream != null) { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null) { content.append(line).append("\n"); } } else { content.append("文件读取失败"); } } catch (IOException e) { content.append("读取异常: ").append(e.getMessage()); } // 主线程更新UI handler.post(() -> textView.setText(content.toString())); executor.shutdown(); }); }
ANR错误日志解读
如果你的ANR日志显示类似以下内容,说明主线程被IO操作阻塞:
"main" prio=5 tid=1 Waiting
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:279)
at com.yourpackage.YourActivity.readFile(YourActivity.java:XX)
解决核心就是把上述阻塞代码移到后台线程执行。
Manifest配置注意事项
- 若需兼容Android 10及以下版本,可在
<application>标签添加android:requestLegacyExternalStorage="true",但Android 11及以上该属性会被系统忽略 - Android 11及以上版本,文档选择器场景无需申请
READ_EXTERNAL_STORAGE权限
内容的提问来源于stack exchange,提问作者Reginald D

