Android端使用Apache POI打开.xlsx文件抛出异常求助
Android端POI读取.xlsx文件抛出SAXNotRecognizedException异常问题
问题现象
- Android应用开发中,通过
Intent.ACTION_OPEN_DOCUMENT拉起系统文件选择器选取Excel文件,获取返回的文件Uri后,通过ContentResolver打开文件输入流传入WorkbookFactory.create(inputStream)读取文件:.xls格式文件可正常解析,无异常.xlsx格式文件解析时直接抛出SAXNotRecognizedException、POIXMLException崩溃
异常日志
D/test: uri: content://com.android.externalstorage.documents/document/0C19-391B%3Atest.xlsx E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.myexceloperatesample, PID: 20662 org.apache.poi.ooxml.POIXMLException: http://xml.org/sax/properties/declaration-handler at org.apache.poi.ooxml.POIXMLFactory.createDocumentPart(POIXMLFactory.java:66) at org.apache.poi.ooxml.POIXMLDocumentPart.read(POIXMLDocumentPart.java:657) at org.apache.poi.ooxml.POIXMLDocument.load(POIXMLDocument.java:180) at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:286) at org.apache.poi.xssf.usermodel.XSSFWorkbookFactory.createWorkbook(XSSFWorkbookFactory.java:88) at org.apache.poi.xssf.usermodel.XSSFWorkbookFactory.createWorkbook(XSSFWorkbookFactory.java:135) at java.lang.reflect.Method.invoke(Native Method) at org.apache.poi.ss.usermodel.WorkbookFactory.createWorkbook(WorkbookFactory.java:339) at org.apache.poi.ss.usermodel.WorkbookFactory.createXSSFWorkbook(WorkbookFactory.java:314) at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:232) at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:198) at com.example.myexceloperatesample.utility.ExcelDataProcessor.getWorkbook(ExcelDataProcessor.kt:67) at com.example.myexceloperatesample.utility.ExcelDataProcessor.execute(ExcelDataProcessor.kt:36) at com.example.myexceloperatesample.list.ListCheckFragment.startForResult$lambda-0(ListCheckFragment.kt:45) at com.example.myexceloperatesample.list.ListCheckFragment.$r8$lambda$mjTpkbLSgZW3CDTcSV4joyxwpyA(Unknown Source:0) at com.example.myexceloperatesample.list.ListCheckFragment$$ExternalSyntheticLambda2.onActivityResult(Unknown Source:4) at androidx.activity.result.ActivityResultRegistry$1.onStateChanged(ActivityResultRegistry.java:148) at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:360) at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:271) at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:313) at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:151) at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:134) at androidx.fragment.app.Fragment.performStart(Fragment.java:3058) at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:587) at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:279) at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113) at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1374) at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2841) at androidx.fragment.app.FragmentManager.dispatchStart(FragmentManager.java:2791) at androidx.fragment.app.Fragment.performStart(Fragment.java:3062) at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:587) at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:279) at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113) at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1374) at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2841) at androidx.fragment.app.FragmentManager.dispatchStart(FragmentManager.java:2791) at androidx.fragment.app.FragmentController.dispatchStart(FragmentController.java:273) at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:486) at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:246) at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1435) at android.app.Activity.performStart(Activity.java:8018) at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3475) at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221) at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201) E/AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7656) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) Caused by: java.lang.reflect.InvocationTargetException at java.lang.reflect.Constructor.newInstance0(Native Method) at java.lang.reflect.Constructor.newInstance(Constructor.java:343) at org.apache.poi.xssf.usermodel.XSSFFactory.createDocumentPart(XSSFFactory.java:56) at org.apache.poi.ooxml.POIXMLFactory.createDocumentPart(POIXMLFactory.java:63) ... 52 more Caused by: java.lang.RuntimeException: http://xml.org/sax/properties/declaration-handler at org.apache.xmlbeans.impl.store.Locale$SaxLoader.<init>(Locale.java:3389) at org.apache.xmlbeans.impl.store.Locale$XmlReaderSaxLoader.<init>(Locale.java:3076) at org.apache.xmlbeans.impl.store.Locale.getSaxLoader(Locale.java:3061) at org.apache.xmlbeans.impl.store.Locale.parseToXmlObject(Locale.java:1272) at org.apache.xmlbeans.impl.store.Locale.parseToXmlObject(Locale.java:1259) at org.apache.xmlbeans.impl.schema.SchemaTypeLoaderBase.parse(SchemaTypeLoaderBase.java:345) at org.openxmlformats.schemas.drawingml.x2006.main.ThemeDocument$Factory.parse(Unknown Source:6) at org.apache.poi.xssf.model.ThemesTable.<init>(ThemesTable.java:86) ... 56 more Caused by: org.xml.sax.SAXNotRecognizedException: http://xml.org/sax/properties/declaration-handler at org.apache.harmony.xml.ExpatReader.setProperty(ExpatReader.java:170) at org.apache.xmlbeans.impl.store.Locale$SaxLoader.<init>(Locale.java:3383) ... 63 more
问题复现代码
Excel解析逻辑
class ExcelDataProcessor( private val activity: FragmentActivity, private val result: ActivityResult, private val excelViewModel: ExcelViewModel ) { fun execute() { val uri = result.data!!.data val workbook: Workbook = getWorkbook(uri!!) } private fun getWorkbook(uri: Uri): Workbook{ return if (uri.toString().endsWith(".xlsx")) { Log.d("test", "uri: $uri") val inputStream = activity.contentResolver?.openInputStream(uri) WorkbookFactory.create(inputStream) } else { val inputStream = activity.contentResolver?.openInputStream(uri) WorkbookFactory.create(inputStream) } } }
文件选择逻辑
class ListCheckFragment : Fragment() { private var _binding : FragmentListCheckBinding? = null private val binding get() = _binding!! private val excelViewModel: ExcelViewModel by activityViewModels() private val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ result: ActivityResult? -> if (result?.resultCode == RESULT_OK) { ExcelDataProcessor(requireActivity(), result, excelViewModel) .execute() } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View { _binding = FragmentListCheckBinding.inflate(inflater,container,false) binding.fileImportButton.setOnClickListener { openFileProvider() } return binding.root } private fun openFileProvider() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "*/*" putExtra(Intent.EXTRA_MIME_TYPES, arrayOf( "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ) ) } startForResult.launch(intent, null) } }
项目依赖配置
android { compileSdk 32 defaultConfig { applicationId "com.example.myexceloperatesample" minSdk 26 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } viewBinding { enabled = true } dataBinding { enabled = true } } dependencies { implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.6.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.4.2' implementation 'androidx.navigation:navigation-ui-ktx:2.4.2' implementation "org.apache.poi:poi:4.0.1" implementation "org.apache.poi:poi-ooxml:4.0.1" implementation 'org.apache.xmlbeans:xmlbeans:3.0.2' implementation "org.apache.poi:poi-ooxml-schemas:1.4" implementation 'xerces:xercesImpl:2.8.0' implementation 'com.fasterxml:aalto-xml:1.2.2' implementation files('libs/javax.xml.stream-3.0.1.jar') implementation files('libs/dom4j-2.0.3.jar') kapt 'org.apache.logging.log4j:log4j-core:2.17.2' }
问题根因
- 格式解析逻辑差异:
.xls是BIFF二进制格式,POI解析时不需要XML解析组件;.xlsx是OOXML压缩包格式,内部所有结构都是XML文件,解析时依赖xmlbeans组件调用SAX解析器处理XML内容。 - 系统解析器能力限制:Android系统内置的
org.apache.harmony.xml.ExpatReaderSAX解析器不支持http://xml.org/sax/properties/declaration-handler属性,xmlbeans初始化SAX加载器时尝试设置该属性直接抛出异常。 - 依赖冲突导致兼容方案失效:现有依赖存在版本不匹配问题:
poi-ooxml-schemas:1.4是POI 1.x版本的组件,和当前引入的POI 4.0.1完全不兼容;额外引入的xerces、aalto-xml、手动导入的jar包没有被设置为默认SAX驱动,类加载时优先加载了系统内置的ExpatReader,导致兼容的解析器没有生效。
修复方案
- 清理冗余冲突依赖:删除所有POI相关的旧依赖,包括
poi、poi-ooxml、xmlbeans、poi-ooxml-schemas、xerces、aalto-xml、手动导入的javax.xml.stream、dom4j、log4j相关kapt依赖,替换为适配Android平台的POI依赖组合,从根源解决SAX解析器兼容问题:
使用上述依赖需要在项目根目录的仓库配置中添加jitpack仓库源。implementation 'org.apache.poi:poi:5.2.3' implementation 'org.apache.poi:poi-ooxml:5.2.3' implementation 'com.github.SUPERCILEX.poi-android:poi:5.2.3' - 优化文件读取逻辑:删除无意义的格式判断分支,使用
use扩展函数自动关闭输入流,避免资源泄漏:private fun getWorkbook(uri: Uri): Workbook { return activity.contentResolver?.openInputStream(uri)?.use { inputStream -> WorkbookFactory.create(inputStream) } ?: throw IllegalStateException("无法打开目标文件输入流") } - 备选方案(不引入第三方适配包):如果不想使用适配版依赖,可以保留和POI版本匹配的xerces依赖,在Application的
onCreate生命周期中手动指定默认SAX驱动,强制xmlbeans使用支持对应属性的解析器,跳过系统内置的ExpatReader:
该方案需要严格匹配xerces和POI的版本,否则会出现其他类冲突问题。override fun onCreate() { super.onCreate() System.setProperty("org.xml.sax.driver", "org.apache.xerces.parsers.SAXParser") }
内容的提问来源于stack exchange,提问作者tofu
相关产品推荐
相关产品推荐

