compileSdk 32下读取CSV文件遭遇EACCES权限拒绝问题求助
问题分析与解决方案
核心问题:错误的文件读取方式
你通过ActivityResultContracts.GetContent()获取的是内容URI,而非直接的文件系统路径。此时调用getRealPathFromURI()试图转换为真实文件路径,再用FileReader直接访问的方式,在Android 10(API 29)及以上版本会被系统权限拦截——哪怕开启了android:requestLegacyExternalStorage,这种直接访问文件路径的操作依然受限制,因为内容URI对应的文件可能不在应用沙箱范围内,且MediaStore.Files.FileColumns.DATA字段在Android Q之后已被废弃,getRealPathFromURI()拿到的路径本身就可能无效或无访问权限。
次要问题:权限请求的异步逻辑错误
点击FloatingActionButton时,你先调用requestPermission(),紧接着就检查权限状态并决定是否启动文件选择器。但权限请求是异步操作,调用后不会立刻完成授权,此时的权限检查结果还是之前的状态,可能导致权限未真正授权就执行后续操作(不过你的异常核心原因还是文件读取方式问题)。
修复方案
1. 改用ContentResolver读取文件内容
直接通过ContentResolver打开URI对应的输入流,无需转换为文件路径,这是Android 10+推荐的文件操作方式:
private void readCSV(Uri result) { Log.d(TAG, "readCSV: reading.."); // 使用try-with-resources自动关闭流 try (InputStream inputStream = getContentResolver().openInputStream(result); CSVReader reader = new CSVReader(new InputStreamReader(inputStream))) { String[] nextLine; while ((nextLine = reader.readNext()) != null) { Log.d(TAG, "readCSV: data is " + nextLine[0]); } } catch (IOException e) { Log.d(TAG, "readCSV: e " + e.getMessage()); } catch (CsvValidationException e) { e.printStackTrace(); Log.d(TAG, "readCSV: " + e.getMessage()); } }
同时删除无用的getRealPathFromURI()方法。
2. 修正权限请求的逻辑
将文件选择器的启动逻辑移到权限请求的回调中,确保权限授权完成后再执行:
floatingActionButton.setOnClickListener(view -> { requestPermission(); }); // 修改权限请求的回调逻辑 permissionResultLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> { Boolean isGranted = result.get(Manifest.permission.READ_EXTERNAL_STORAGE); isReadPermissionGranded = isGranted != null && isGranted; if (isReadPermissionGranded) { getCSV.launch("*/*"); } else { Log.d(TAG, "onActivityResult: no permission granted"); } }); // 调整权限请求方法 private void requestPermission() { List<String> permissionRequest = new ArrayList<>(); if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { permissionRequest.add(Manifest.permission.READ_EXTERNAL_STORAGE); } if (!permissionRequest.isEmpty()) { permissionResultLauncher.launch(permissionRequest.toArray(new String[0])); } else { // 权限已存在,直接启动文件选择器 getCSV.launch("*/*"); } }
3. 额外说明
android:requestLegacyExternalStorage="true"仅在Android 10(API 29)生效,Android 11(API 30)及以上版本该属性被忽略,系统强制使用分区存储。所以即使你配置了这个属性,在compileSdk32环境下,直接访问外部文件路径依然会被限制,必须使用内容URI操作文件。- 若需适配Android 11+,建议完全遵循分区存储规范,避免依赖真实文件路径的操作。
内容的提问来源于stack exchange,提问作者Prasath S
相关产品推荐
相关产品推荐

