1、前言
最近要用Android Studio和Java实现多种文件的导入和读取,包括常见的文本文件txt、doc、docx、xls和xlsx。其中txt用输入输出流操作即可。经过搜索查找,确定了使用Apache POI库进行其余文件类型的读写。在此记录从开始在apache官网上下载导入包后尝试读取doc便报错,到打release包后安装闪退期间遇到的问题和解决办法。
有错误欢迎大家指出,有更好的办法也欢迎大家评论和私信,共同进步。
2、Apache POI简介
“关于POI可以访问Apache POI的官网获取详细的信息。”
(本章节参照参考链接1写,参考链接1在4.1中)
由于项目中只是用到了doc、docx、xls和xlsx的组件,下面也只是介绍这四个组件的使用
通过官网 ->Getting Involved,可以看到构建项目的方式(也可以自行搜索,看更详细的教程),如下图
通过官网 ->Component APIs,可以看到 doc、docx、xls和xlsx文件分别对应着组件HWPF、XWPF、HSSF和XSSF,而HWPF、XWPF、HSSF和XSSF则对应着poi-scratchpad、poi-ooxml、poi和poi-ooxml
文件类型 | 组件名 | MavenId |
---|---|---|
doc | HWPF | poi-scratchpad |
docx | XWPF | poi-ooxml |
xls | HSSF | poi |
xlsx | XSSF | poi-ooxml |
如下图
3、正确打开方式
回头看整理下,我认为不走那么多弯路的开发过程。
(参考链接均在4中)
3.1 下载并导入Apache POI包
1、下载压缩包。下载地址:https://github.com/ljliu1985/AndroidPoiForReadExcelXlsx
2、解压缩之后将AndroidPoiForReadExcelXlsx-master\app\libs里面的包全部拷贝到项目中app\libs中
3、导入包。不太熟悉导入可以看看这个:Android Studio导入jar包教程
3.2 读取和写入
以下读取和写入均先用代码测试是否能正常运行及正确读取或写入。如果可以,再根据实际需求自定义补充修改代码。
3.2.1 docx的读取和写入
3.2.1.1 读取
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadDocxFile {
public void readDocx(String[] args) {
String filePath = "path/to/your/document.docx"; // 替换为你的文件路径
try (FileInputStream fis = new FileInputStream(filePath);
XWPFDocument document = new XWPFDocument(fis)) {
// 遍历文档中的段落
for (XWPFParagraph paragraph : document.getParagraphs()) {
Log.d("test",paragraph.getText());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2.1.2 写入
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import java.io.FileOutputStream;
import java.io.IOException;
public class WriteDocxFile {
public void writeDocx(String[] args) {
String filePath = "path/to/your/newDocument.docx"; // 替换为你想要创建的文件路径
try (XWPFDocument document = new XWPFDocument()) {
// 创建段落
XWPFParagraph paragraph = document.createParagraph();
XWPFRun run = paragraph.createRun();
run.setText("Hello, this is the first paragraph.");
// 继续添加更多段落
XWPFParagraph secondParagraph = document.createParagraph();
XWPFRun run2 = secondParagraph.createRun();
run2.setText("This is the second paragraph.");
// 将文档写入文件
try (FileOutputStream fos = new FileOutputStream(filePath)) {
document.write(fos);
}
Log.d("test", "Document created successfully!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2.2 doc的读取和写入
3.2.2.1 读取
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.DocumentPart;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadDocFile {
public void readDoc(String[] args) {
String filePath = "path/to/your/document.doc"; // 替换为你的文件路径
try (FileInputStream fis = new FileInputStream(filePath);
HWPFDocument document = new HWPFDocument(fis)) {
Range range = document.getRange();
// 遍历文档中的段落
for (int i = 0; i < range.numParagraphs(); i++) {
Paragraph paragraph = range.getParagraph(i);
Log.d("test", paragraph.text());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2.2.2 写入
doc的写入遇到了问题,"在使用POI写word doc文件的时候我们必须要先有一个doc文件才行,因为我们在写doc文件的时候是通过HWPFDocument来写的,而HWPFDocument是要依附于一个doc文件的。所以通常的做法是我们先在硬盘上准备好一个内容空白的doc文件,然后建立一个基于该空白文件的HWPFDocument。之后我们就可以往HWPFDocument里面新增内容了,然后再把它写入到另外一个doc文件中,这样就相当于我们使用POI生成了word doc文件。"(参考链接1)
我没有找到好办法,用了一种很笨的办法。如果大家有好办法可以在评论区分享或私信,共同进步。
在此使用了XWPFDocument创建的一个空白doc文件,“这段代码会创建一个名为blank.doc
的空白Word文档。注意,这里使用的是.doc
而非.docx
扩展名,但是实际上创建的是一个OOXML(.docx)格式的文档。”容易造成混淆。
“Apache POI库支持两种不同的格式来创建Word文档:.doc
和 .docx
。尽管这两种格式在内部实现和功能上有所不同,但它们都可以用来创建和操作Word文件。”
由此又导致了读取时扩展名是doc但实际是docx文档时报错,以及doc和docx互相更改后缀名导致的读取报错。
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import java.io.FileOutputStream;
public class BlankWordFile {
public void blankWord(String[] args) {
try (XWPFDocument document = new XWPFDocument()) {
// 创建一个空白的段落
XWPFParagraph paragraph = document.createParagraph();
// 将文档保存到硬盘
try (FileOutputStream out = new FileOutputStream("blank.doc")) {
document.write(out);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
读取时扩展名是doc但实际是docx文档时报错
读取扩展名为doc的文件时先用HWPFDocument读,如果捕获到异常就用XWPFDocument读,这种情况下便不会再报错。同理,读取docx扩展名时捕获到异常先用HWPFDocument读。xls和xlsx同理。下面代码结合了3.2.1docx的读取和3.2.2doc的读取。
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.DocumentPart;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadDocFile {
public void readDoc(String[] args) {
String filePath = "path/to/your/document.doc"; // 替换为你的文件路径
try (FileInputStream fis = new FileInputStream(filePath);
HWPFDocument document = new HWPFDocument(fis)) {
Range range = document.getRange();
// 遍历文档中的段落
for (int i = 0; i < range.numParagraphs(); i++) {
Paragraph paragraph = range.getParagraph(i);
Log.d("test", paragraph.text());
}
} catch (IOException e) {
//e.printStackTrace();
try (FileInputStream fis = new FileInputStream(filePath);
XWPFDocument document = new XWPFDocument(fis)) {
// 遍历文档中的段落
for (XWPFParagraph paragraph : document.getParagraphs()) {
Log.d("test",paragraph.getText());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
写入多段可以用下面这段代码测试
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class DocWriter {
public void writeDocFile(File file) {
try (XWPFDocument document = new XWPFDocument()) {
// Add multiple paragraphs
addParagraph(document, "Hello, World!");
addParagraph(document, "This is the second line.");
addParagraph(document, "And this is the third line.");
// Write the document to the output stream
try (OutputStream outputStream = new FileOutputStream(file)) {
document.write(outputStream);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void addParagraph(XWPFDocument document, String text) {
XWPFParagraph paragraph = document.createParagraph();
XWPFRun run = paragraph.createRun();
run.setText(text);
}
}
3.2.3 xls和xlsx的读取和写入
3.2.3.1 读取
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadExcelToString {
public void readExcel(String[] args) {
String filePath = "path/to/your/excelFile.xls"; // 替换为你的文件路径
StringBuilder stringBuilder = new StringBuilder();
try (FileInputStream fis = new FileInputStream(filePath)) {
Workbook workbook;
// 根据文件扩展名选择工作簿类型
if (filePath.endsWith(".xlsx")) {
workbook = new XSSFWorkbook(fis);
} else if (filePath.endsWith(".xls")) {
workbook = new HSSFWorkbook(fis);
} else {
throw new IllegalArgumentException("Invalid file format");
}
// 读取第一个工作表
Sheet sheet = workbook.getSheetAt(0);
// 遍历行
for (Row row : sheet) {
// 遍历单元格
for (Cell cell : row) {
switch (cell.getCellType()) {
case STRING:
stringBuilder.append(cell.getStringCellValue()).append("\t");
break;
case NUMERIC:
stringBuilder.append(cell.getNumericCellValue()).append("\t");
break;
case BOOLEAN:
stringBuilder.append(cell.getBooleanCellValue()).append("\t");
break;
default:
stringBuilder.append("UNKNOWN\t");
}
}
stringBuilder.append("\n"); // 每行结束后换行
}
} catch (IOException e) {
e.printStackTrace();
}
// 输出结果
String result = stringBuilder.toString();
Log.d("test", result);
}
}
3.2.4.2 写入
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class WriteExcelBasedOnExtension {
public void WriteExcelBase(String[] args) {
String filePathXlsx = "output.xlsx"; // 输出的 XLSX 文件路径
String filePathXls = "output.xls"; // 输出的 XLS 文件路径
// 写入 XLSX 文件
writeExcel(filePathXlsx);
// 写入 XLS 文件
writeExcel(filePathXls);
}
private static void writeExcel(String filePath) {
Workbook workbook = null;
// 根据文件后缀选择工作簿类型
if (filePath.endsWith(".xlsx")) {
workbook = new XSSFWorkbook(); // 创建 XLSX 工作簿
} else if (filePath.endsWith(".xls")) {
workbook = new HSSFWorkbook(); // 创建 XLS 工作簿
} else {
throw new IllegalArgumentException("Invalid file format: " + filePath);
}
try (FileOutputStream fileOut = new FileOutputStream(filePath)) {
Sheet sheet = workbook.createSheet("Sheet1"); // 创建工作表
// 写入数据
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue("Hello, " + (filePath.endsWith(".xlsx") ? "XLSX" : "XLS") + "!");
workbook.write(fileOut); // 写入文件
Log.d("test", filePath + " 文件写入成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (workbook != null) {
try {
workbook.close(); // 关闭工作簿
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.3 打包
以上步骤通过后,开始打包生成安装包测试。如果是用release,会报错java.lang.ExceptionInInitializerError。需要“和其他引入的第三方jar一样,要忽略掉这些jar代码混淆!”。
“首先,需要在app/proguard-rules.pro文件中,添加混淆文件;
然后,在build.gradle中去开启混淆:”(参考链接4,下面代码是参考链接5,其中的txt和pro文件需要修改为自己本地项目的对应文件)
buildTypes {
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
zipAlignEnabled true
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.myConfig
}
}
最后,点击“运行,看看有问题否,能不能成功。
结果按此方法,在运行下发现问题,去除错误的混淆,解决掉问题,最终混淆release打包成功。”(参考链接4)
配置完文件后点击运行,如果运行后有问题,用参考链接6中忽略所有引用包,再次点击运行测试。下图是我的忽略混淆配置
4、问题和解决办法
4.1 下载和导包
之前从官网上Apache POI™ - Download Release Artifacts(参考链接1)下载包,发现高版本会报错,就用了教程中的3.16版本读取doc,的确可以。之后开发doc、docx和xls都能正确读取,但在读取xlsx时报错未能解决。
又开始搜索,终于找到了能用的包:https://github.com/ljliu1985/AndroidPoiForReadExcelXlsx。(参考链接2)
即3.17版本的包。“使用Poi读取高版本Excel xlsx文件, 这里主要是rt.jar包解决了以下两个错误:”java.lang.NoClassDefFoundError和javax.xml.stream.FactoryConfigurationError。
大家可以自行选择,如果想一步到位可以点击github上的包下载,亲测可用
Window系统下载Apache POI包步骤:
1、点击Archives of all prior release
2、点击Binary Artifacts
3、点击按照链接下载包,“linux系统选择.tar.gz windows系统选择.zip”(参考链接1)
参考链接1:Android使用ApachePOI组件读写Worddoc和docx文件 - 简书
参考链接2:Android 中读写Excel数据 - 简书
4.2 文件读取和写入
刚开始读取doc的时候总是报错,最后发现是因为直接更改后缀名导致的。原因如下:
‘读写前注意:Apache POI 提供的HWPFDocument类只能读写规范的.doc文件,也就是说假如你使用修改 后缀名 的方式生成doc文件或者直接以命名的方式创建,将会出现错误“Your file appears not to be a valid OLE2 document”’(参考链接1)
Invalid header signature; read 0x7267617266202E31, expected 0xE11AB1A1E011CFD0 - Your file appears not to be a valid OLE2 document
最后导出,也就是doc写入的时候遇到了问题,创建doc文件时HWPFDocument document = new HWPFDocument();标红。继续搜索,最后只找到了用XWPFDocument去生成扩展名为doc的docx文档。会产生混淆。所以之后读取时,扩展名doc和docx捕获到异常先分别用XWPFDocument和DWPFDocument读取,这种情况下不会报错。以防万一,xls和xlsx也做了类似的处理。如果大家有更好的办法,欢迎在评论区留言或私信,共同进步。
4.3 打包
用release打包后读取或写入报错,搜到了一个精准匹配的答案(参考链接3)。小白才知道正确的方向,根据步骤忽略掉doc的混淆后,doc能正常读取了。开始比葫芦画瓢忽略其他文件类型的混淆,但没成功,而且每次设置完忽略混淆都要重新打包安装测试,很麻烦。最后找到了
“首先,需要在app/proguard-rules.pro文件中,添加混淆文件;
然后,在build.gradle中去开启混淆:”(参考链接4)
(下方代码是参考链接5)
buildTypes {
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
zipAlignEnabled true
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.myConfig
}
}
最后点击“运行,看看有问题否,能不能成功。结果按此方法,在运行下发现问题,去除错误的混淆,解决掉问题,最终混淆release打包成功。”(参考链接4)
配置完文件后点击运行,发现的确无法运行成功,开始找忽略混淆的方法,刚开始把引入的包中的根目录下的第一级包都忽略混淆还是报错,最后找到了直接把引用包混淆的教程(参考链接6),试了下,终于运行成功了。下图是我的忽略混淆配置