Android使用Apache POI读取Excel报FileNotFoundException排查
问题根因
代码存在5处核心错误导致文件找不到、读取失败:
- 变量遮蔽:
onActivityResult回调内重新定义了局部变量Uri uri = null,类的全局成员uri始终未被赋值,后续打开文件时拿不到正确的文件地址 - 静态方法非法调用:
readExcelFromStorage被定义为静态方法,方法内直接调用非静态的getContentResolver()本身就会编译/运行错误,且方法未接收正确的Uri参数,完全依赖全局空Uri - 格式硬编码:所有文件都用
HSSFWorkbook初始化,该类仅支持.xls格式,读取.xlsx文件会直接抛异常 - 冗余权限逻辑:使用
ACTION_OPEN_DOCUMENT获取的content类型Uri自带系统临时访问授权,不需要申请READ_EXTERNAL_STORAGE权限,高版本Android上该权限已废弃,反而会导致授权逻辑失效 - 基础逻辑笔误:文件名分割正则多写了转义符、路径截取判断条件写错,会导致文件格式判断失效
修复方案
按以下步骤修改代码即可解决问题:
- 修复Uri变量遮蔽问题
删掉onActivityResult回调内的Uri uri = null;局部变量定义,直接给类的全局uri成员赋值,修正文件名截取、格式判断的笔误,拿到合法Excel文件后直接调用读取方法,移除多余的权限申请步骤。 - 重构文件读取方法
去掉readExcelFromStorage的static修饰符,将文件Uri、文件后缀作为参数传入,根据文件后缀选择对应的POI Workbook实现类,通过传入的Context打开文件输入流。 - 补全POI依赖
读取xlsx格式需要额外引入poi-ooxml包,否则会报类找不到错误。
修正后核心代码
ActivityResult回调部分
ActivityResultLauncher<Intent> launch_activity = registerForActivityResult (new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() { @Override public void onActivityResult(ActivityResult result) { if (result.getResultCode() == Activity.RESULT_OK) { Intent data = result.getData(); // 删掉局部uri定义,直接给全局成员赋值 uri = null; if (data != null) { uri = data.getData(); } if (uri != null && uri.getScheme().equals("content")) { try (Cursor returnCursor = getContentResolver().query(uri, null, null, null, null)) { if (returnCursor != null && returnCursor.moveToFirst()) { name = returnCursor.getString(returnCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)); } } } if(name == null){ if (uri != null) { name = uri.getPath(); } int cut = -1; if (name != null) { cut = name.lastIndexOf('/'); } if(cut != -1){ if (name != null) { name = name.substring(cut + 1); } } } String[] extension = null; if (name != null) { // 修正分割点号的正则 extension = name.split("\\."); } if (extension != null && (extension[extension.length - 1].equals("xls") || extension[extension.length - 1].equals("xlsx"))) { // 不需要申请权限,直接调用读取方法 readExcelFromStorage(MainActivity.this, uri, extension[extension.length - 1]); } else if (extension != null) { Toast.makeText(MainActivity.this,extension[extension.length - 1]+" 格式文件不支持",Toast.LENGTH_SHORT).show(); } } } });
文件读取方法
// 去掉static修饰,传入context、文件uri、文件后缀 public void readExcelFromStorage(Context context, Uri fileUri, String fileExtension) { InputStream fileInputStream = null; Workbook workbook = null; try { fileInputStream = context.getContentResolver().openInputStream(fileUri); if (fileInputStream == null) { Toast.makeText(context, "文件打开失败", Toast.LENGTH_SHORT).show(); return; } // 根据后缀选择对应Workbook实现 if ("xls".equals(fileExtension)) { workbook = new HSSFWorkbook(fileInputStream); } else if ("xlsx".equals(fileExtension)) { workbook = new XSSFWorkbook(fileInputStream); } if (workbook == null) return; Sheet sheet = workbook.getSheetAt(0); if (sheet == null) return; // 遍历行读取单元格 for (Row row : sheet) { // 跳过表头 if (row.getRowNum() == 0) continue; Iterator<Cell> cellIterator = row.cellIterator(); while (cellIterator.hasNext()) { Cell cell = cellIterator.next(); // 高版本POI用枚举判断单元格类型,兼容新旧版本写法 switch (cell.getCellType()) { case NUMERIC: Toast.makeText(context.getApplicationContext(), String.valueOf(cell.getNumericCellValue()),Toast.LENGTH_SHORT).show(); break; case STRING: Toast.makeText(context.getApplicationContext(), cell.getStringCellValue(),Toast.LENGTH_SHORT).show(); break; default: break; } } } } catch (Exception e) { e.printStackTrace(); Toast.makeText(context, "读取失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } finally { try { if (fileInputStream != null) fileInputStream.close(); if (workbook != null) workbook.close(); } catch (Exception ex) { ex.printStackTrace(); } } }
Gradle依赖配置
在模块级build.gradle的dependencies节点添加POI依赖:
dependencies { // 其他原有依赖保留 implementation 'org.apache.poi:poi:5.2.3' implementation 'org.apache.poi:poi-ooxml:5.2.3' }
额外说明
原代码中checkPermission、onRequestPermissionsResult里和存储权限申请、权限通过后调用读文件的逻辑可以直接删除,ACTION_OPEN_DOCUMENT返回的Uri由系统提供临时读写权限,不需要申请任何存储权限,在Android 10~14全版本都可以正常访问。
内容的提问来源于stack exchange,提问作者user19152825
相关产品推荐
相关产品推荐

