Android10/11下选取Excel文件Uri转File出现FileNotFoundException问题咨询
问题原因
ACTION_OPEN_DOCUMENT、ACTION_GET_CONTENT返回的是content://开头的内容URI,不是本地文件的真实物理路径,uri.getPath()拿到的是系统内容提供者的虚拟路径,直接用new File()访问当然找不到对应文件。- Android 10(API29)及以上版本已经限制了应用直接访问共享存储的文件路径,即便你通过其他方式拿到了文件的真实路径,也没有权限直接访问。你测试时静态路径能正常访问,是因为测试文件刚好在你应用有权限访问的目录,或者你申请了旧版的存储权限,无法适配所有用户场景。
- 额外的逻辑问题:你给Intent设的
setType("*/*")会允许用户选择所有类型文件,后续的Excel文件判断逻辑容错成本更高。
可行解决方案
方案1:修改Excel工具类支持输入流(推荐)
不需要把URI转成File对象,直接用系统提供的ContentResolver打开URI对应的输入流传给解析逻辑,是Android官方推荐的适配方式,性能最好,不需要额外拷贝文件。
第一步:优化文件选择器唤起逻辑
不需要分版本使用不同Action,统一用ACTION_OPEN_DOCUMENT适配所有Android 4.4及以上版本,同时限制只能选择Excel类型文件:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); // 限制可选文件类型为Excel intent.setType("*/*"); String[] mimeTypes = {"application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}; intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); // 申请读取权限 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivityForResult(intent, 1);
第二步:修改回调处理逻辑
先调整ExcelUtils.readExcel方法的参数,把原来的File类型改为InputStream,再修改回调代码:
protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 1 && resultCode == Activity.RESULT_OK && data != null) { Uri uri = data.getData(); if (uri == null) return; // 获取选中文件的文件名 String fileName = null; Cursor cursor = getContentResolver().query(uri, null, null, null, null); if (cursor != null && cursor.moveToFirst()) { fileName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)); cursor.close(); } if (ExcelUtils.checkIfExcelFile(fileName)) { try { // 直接打开URI对应的输入流 InputStream inputStream = getContentResolver().openInputStream(uri); excelUtils.readExcel(inputStream, FirstRowHeader); inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } // 如果需要长期访问该文件,可以保留你原有申请持久URI权限的逻辑 } }
方案2:拷贝文件到应用私有目录生成File
如果你不想修改原有ExcelUtils的代码,可以把选中的文件内容拷贝到你的应用私有目录(比如getCacheDir()、getFilesDir()),生成应用有权限直接访问的本地File后再调用原有方法:
// 在onActivityResult获取到uri后调用该方法得到本地File对象 private File getFileFromUri(Uri uri, String fileName) throws IOException { File outputFile = new File(getCacheDir(), fileName); InputStream inputStream = getContentResolver().openInputStream(uri); FileOutputStream outputStream = new FileOutputStream(outputFile); byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); } inputStream.close(); outputStream.close(); return outputFile; }
拷贝完成后直接用返回的File对象调用原有readExcel方法即可。
内容的提问来源于stack exchange,提问作者MKhan
相关产品推荐
相关产品推荐

