Android集成POI-Android读取Word文档遇SchemaTypeLoaderException错误求助
解决Apache POI-Android读取Word文件时的SchemaTypeLoaderException错误
这个错误本质是XMLBeans在Android环境下的类加载冲突,加上StAX库配置不当、POI依赖不完整导致的。下面是适配Android API22的完整可运行解决方案:
一、修正Gradle配置
首先调整项目和应用级的Gradle文件,确保依赖完整且无冲突,同时开启MultiDex(API22方法数限制必须):
项目级build.gradle
buildscript { ext.kotlin_version = '1.3.72' repositories { google() jcenter() } ext { poiVersion = '3.17' } dependencies { classpath 'com.android.tools.build:gradle:3.5.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { google() jcenter() maven { url 'https://jitpack.io' } } } task clean(type: Delete) { delete rootProject.buildDir }
应用级build.gradle
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 29 buildToolsVersion "29.0.3" defaultConfig { applicationId "com.example.diffchecker" minSdkVersion 21 targetSdkVersion 22 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true // 关键:API22必须开启,避免方法数超限 } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.core:core-ktx:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'pub.devrel:easypermissions:2.0.1' implementation 'androidx.multidex:multidex:2.0.1' // 添加MultiDex依赖 // 完整添加POI-Android依赖,排除冲突的StAX库 implementation ("com.github.SUPERCILEX.poi-android:poi:$poiVersion") { exclude group: 'stax', module: 'stax-api' exclude group: 'javax.xml.stream', module: 'stax-api' } implementation ("com.github.SUPERCILEX.poi-android:poi-ooxml:$poiVersion") { exclude group: 'stax', module: 'stax-api' exclude group: 'javax.xml.stream', module: 'stax-api' } }
二、完整Activity代码(适配API22)
包含权限申请、文件选择、Word读取的完整逻辑,注意资源自动关闭和错误处理:
import android.Manifest import android.content.Intent import android.net.Uri import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import org.apache.poi.xwpf.extractor.XWPFWordExtractor import org.apache.poi.xwpf.usermodel.XWPFDocument import pub.devrel.easypermissions.EasyPermissions import java.io.IOException class MainActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks { private val REQUEST_CODE_PICK_FILE = 1001 private val STORAGE_PERMISSIONS = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 必须在使用POI前初始化StAX配置,避免XML解析冲突 System.setProperty("org.apache.poi.javax.xml.stream.XMLInputFactory", "com.fasterxml.aalto.stax.InputFactoryImpl") System.setProperty("org.apache.poi.javax.xml.stream.XMLOutputFactory", "com.fasterxml.aalto.stax.OutputFactoryImpl") System.setProperty("org.apache.poi.javax.xml.stream.XMLEventFactory", "com.fasterxml.aalto.stax.EventFactoryImpl") // 检查权限后启动文件选择 if (EasyPermissions.hasPermissions(this, *STORAGE_PERMISSIONS)) { launchWordPicker() } else { EasyPermissions.requestPermissions( this, "需要读取存储权限来访问Word文件", 100, *STORAGE_PERMISSIONS ) } } private fun launchWordPicker() { val pickIntent = Intent(Intent.ACTION_GET_CONTENT).apply { // 指定只选择docx格式的Word文件 type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document" addCategory(Intent.CATEGORY_OPENABLE) } startActivityForResult(pickIntent, REQUEST_CODE_PICK_FILE) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_PICK_FILE && resultCode == RESULT_OK) { val fileUri = data?.data ?: return readWordContent(fileUri) } } private fun readWordContent(uri: Uri) { var doc: XWPFDocument? = null var extractor: XWPFWordExtractor? = null try { // 使用Kotlin的use函数自动关闭输入流,避免资源泄漏 contentResolver.openInputStream(uri).use { inputStream -> doc = XWPFDocument(inputStream) extractor = XWPFWordExtractor(doc) val content = extractor.text // 这里可以替换为你自己的文本处理逻辑 Toast.makeText(this, "读取成功!内容预览:\n${content.take(100)}...", Toast.LENGTH_LONG).show() println("完整Word内容:\n$content") } } catch (e: Exception) { e.printStackTrace() Toast.makeText(this, "读取失败:${e.message}", Toast.LENGTH_SHORT).show() } finally { // 手动关闭剩余资源 try { extractor?.close() doc?.close() } catch (ioe: IOException) { ioe.printStackTrace() } } } // EasyPermissions回调实现 override fun onPermissionsGranted(requestCode: Int, perms: MutableList<String>) { if (requestCode == 100) { launchWordPicker() } } override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) { Toast.makeText(this, "权限被拒绝,无法读取Word文件", Toast.LENGTH_SHORT).show() } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) } }
三、关键注意点
- MultiDex必须开启:API22的Dalvik虚拟机限制了单个DEX文件的方法数,POI依赖大量类,不开MultiDex会直接崩溃。
- 依赖完整性:必须同时添加
poi和poi-ooxml依赖,前者处理旧版doc,后者处理新版docx,你需要的docx读取依赖后者。 - StAX初始化时机:必须在创建XWPFDocument之前设置系统属性,确保使用Aalto的StAX实现,避免Android原生XML解析库的冲突。
- 资源自动管理:使用Kotlin的
use函数处理输入流,确保流自动关闭,同时在finally块手动关闭文档和提取器,避免内存泄漏。 - 权限处理:API22需要动态申请
READ_EXTERNAL_STORAGE权限,使用EasyPermissions简化权限申请流程。
内容的提问来源于stack exchange,提问作者Adam Lee
相关产品推荐
相关产品推荐

