Android使用Apache POI读取外部存储XLSM文件遇技术难题求助
Android解析本地XLSM文件解决方案
核心问题梳理
- XLSM文件的官方MIME类型
application/vnd.ms-excel.sheet.macroEnabled.12在部分系统文件管理器中不被识别,只能用*/*或application/*唤起文件选择 - 直接传递Content URI的
InputStream给WorkbookFactory可能因流生命周期、权限问题失败 - 复制文件到缓存目录时未确保流完全写入、未正确关闭资源
解决方案步骤
1. 权限配置
在AndroidManifest.xml中添加读取外部存储权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- Android 13+ 访问文档类文件仍需申请该权限 -->
针对Android 6.0+动态申请权限:
private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted -> if (isGranted) { // 权限授予后执行文件读取逻辑 } } // 在按钮点击或初始化时触发权限申请 requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
2. 直接访问固定下载目录文件(推荐,无需文件选择器)
因文件固定在下载文件夹,可直接通过文件路径访问,跳过Content URI流程:
fun readFixedXLSM() { val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) val xlsmFile = File(downloadDir, "你的目标文件名.xlsm") if (!xlsmFile.exists()) { Log.d("xlsm", "目标文件不存在") return } try { FileInputStream(xlsmFile).use { inputStream -> val xlWb = WorkbookFactory.create(inputStream) val xlWs = xlWb.getSheet("Daily Recording") ?: run { Log.d("xlsm", "指定工作表不存在") return } for (j in 44..50) { val row = xlWs.getRow(j) ?: continue for (i in 0..11) { val cell = row.getCell(i) Log.d("xlsm", cell?.toString() ?: "空单元格") } } } } catch (e: IOException) { Log.e("xlsm", "读取文件失败: ${e.message}") } }
3. 修复文件选择器的Content URI处理
若仍需使用文件选择器,确保正确复制文件到缓存并读取:
val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> uri ?: return@registerForActivityResult try { val cacheFile = File(cacheDir, "temp_xlsm.xlsm") // 复制Content URI指向的文件到缓存 contentResolver.openInputStream(uri).use { inputStream -> FileOutputStream(cacheFile).use { outputStream -> inputStream?.copyTo(outputStream) } } // 读取缓存中的文件 FileInputStream(cacheFile).use { inputStream -> val xlWb = WorkbookFactory.create(inputStream) val xlWs = xlWb.getSheet("Daily Recording") ?: run { Log.d("xlsm", "指定工作表不存在") return@registerForActivityResult } for (j in 44..50) { val row = xlWs.getRow(j) ?: continue for (i in 0..11) { val cell = row.getCell(i) Log.d("xlsm", cell?.toString() ?: "空单元格") } } } // 读取完成后删除缓存文件(可选) cacheFile.delete() } catch (e: IOException) { Log.e("xlsm", "处理文件失败: ${e.message}") } }
4. 关键优化点
- 使用
use函数自动关闭流,避免资源泄漏 - 添加空值判断(工作表、行、单元格),防止空指针崩溃
- 捕获
IOException并打印错误日志,便于排查问题 - 缓存文件使用后可删除,避免占用存储空间
MIME类型说明
部分Android系统的文件管理器无法正确识别XLSM的官方MIME类型,此时使用*/*或application/*即可正常唤起文件选择器,不影响后续读取逻辑。
内容的提问来源于stack exchange,提问作者DelitaRagout
相关产品推荐
相关产品推荐

