概要
公司App经常会用到调试或者某个bean类的数据作为表格、文本进行输出。每次写一个bean类输出都需要一堆编码,哪怕是cv也经常出现问题,所以写了个简单的文件输出。
表格类型xls
的输出使用的是第三方库:jxl.jar
,这个无需自己去实现
// https://mvnrepository.com/artifact/net.sourceforge.jexcelapi/jxl
implementation 'net.sourceforge.jexcelapi:jxl:2.6.12'
文本,和另一个csv
格式是直接通过FileWriter
写入的。
整体架构流程
一共就6个类:
CommOutput
使用AsyncTask
为核心类,进行流程控制与数据解析。
OutputType
规定了数据类型,组装,输出接口。
OutputBeanWrapper
封装数据Bean类的方法,标题和可能需要转换的格式。
另外三个类是 OutputType 的子类,用于实现各个实际类型的封装。
实际代码部分
首先是核心控制类:CommOutput
CommOutput
用于接受需要输出的数据,输出的文件名称,输出的文件类型。如有必要,可以定义数据Bean类的某个字段是否输出或者进行日期格式化操作。
package com.manridy.iband.factory.tool.output;
import android.content.Context;
import android.os.AsyncTask;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
/**
* 通用输出控制
* 数据组装下沉在OutputType的子类当中
* @param <T> 数据类型
* @param <P> 平台类型
* @param <I> 平台提供的写能力类型
*/
public class CommOutput<T, P, I> extends AsyncTask<P, Integer, Integer> {
private final List<List<T>> outputData;
private List<String> title = new ArrayList<>();
private final List<OutputBeanWrapper<T>> beanDataGetFunc = new ArrayList<>();
private final List<Function<T, Object>> beanMethodFunc = new ArrayList<>(0);
private final List<Method> methods = new ArrayList<>();
private Class<T> reflectClazz;
public Function<Integer, String> pageTitleMapper = String::valueOf;
private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
private final OutputType<P, I> outputType;
private final File targetOutputFile;
/**
* 如果不对数据类型的内容做删减,并且不进行日期转换操作,输出完完整整的数据类,请使用这个构造。
* 注意!实际实现是通过反射拿bean类的getter方法,如果你的bean类没有getter方法就不会有输出;getter方法顺序混乱(不和字段属性顺序一样),输出的结果也会混乱
* @param context 上下文
* @param outputData 输出数据,一页是一个List<T>,默认多页
* @param clazz 数据类型
* @param fileName 输出的文件名称
* @param outputType 输出的文件类型
*/
public CommOutput(Context context, List<List<T>> outputData,Class<T> clazz,List<String> title, String fileName, OutputType<P, I> outputType) {
File externalCacheDir = context.getCacheDir();
targetOutputFile = new File(externalCacheDir.getAbsolutePath(),fileName+outputType.getType());
this.outputData = outputData;
this.outputType = outputType;
this.reflectClazz = clazz;
this.title.addAll(title);
outputType.setContext(context).setFile(targetOutputFile);
}
/**
* 需要对bean类输出的字段做删减,则提供OutputBeanWrapper包装对象输出对应属性
* @param context 上下文
* @param outputData 输出数据,一页是一个List<T>,默认多页
* @param beanDataGetFunc 数据包装类,包含数据bean类的转换到表格当中的标题与提取对象的数据,数据转换操作
* @param fileName 输出的文件名称
* @param outputType 输出的文件类型
*/
public CommOutput(Context context, List<List<T>> outputData, List<OutputBeanWrapper<T>> beanDataGetFunc, String fileName, OutputType<P, I> outputType) {
File externalCacheDir = context.getCacheDir();
targetOutputFile = new File(externalCacheDir.getAbsolutePath(),fileName+outputType.getType());
this.outputData = outputData;
this.beanDataGetFunc.addAll(beanDataGetFunc);
this.outputType = outputType;
outputType.setContext(context).setFile(targetOutputFile);
}
@SafeVarargs
@Override
protected final Integer doInBackground(P... ps) {
P platform = ps[0];
if (platform == null) return -1;
initTitle();
for (int i = 0; i < outputData.size(); i++) {
List<T> ts = outputData.get(i);
I platformCapacity = outputType.getPlatformCapacity(platform, pageTitleMapper.apply(i), i);
outputType.beforeWrite(platform, platformCapacity);
outputType.initTitle(platformCapacity, title);
List<List<String>> dataOfPage;
if (beanDataGetFunc.isEmpty()){
dataOfPage = expandDataWithReflect(ts,reflectClazz);
}else {
dataOfPage = expandData(ts);
}
for (int j = 0; j < dataOfPage.size(); j++) {
outputType.initBody(platformCapacity, j + 1, dataOfPage.get(j));
}
outputType.releasePage(platformCapacity);
}
outputType.notifyWriteFinished();
outputType.releasePlatform(platform);
return 0;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onPostExecute(Integer integer) {
outputType.onPostExecute(integer);
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
private List<List<String>> expandData(List<T> beanOfPage) {
List<List<String>> dataOfPage = new ArrayList<>();
List<String> rowOfPage = new ArrayList<>();
for (int i = 0; i < beanOfPage.size(); i++) {
rowOfPage.clear();
for (OutputBeanWrapper<T> tOutputBeanWrapper : beanDataGetFunc) {
if (tOutputBeanWrapper.needTransform()) {
sdf.applyPattern(tOutputBeanWrapper.getPatternTrans());
rowOfPage.add(sdf.format(tOutputBeanWrapper.getMethodFunction().apply(beanOfPage.get(i))));
} else {
rowOfPage.add(String.valueOf(tOutputBeanWrapper.getMethodFunction().apply(beanOfPage.get(i))));
}
}
dataOfPage.add(rowOfPage);
}
return dataOfPage;
}
private List<List<String>> expandDataWithReflect(List<T> beanOfPage,Class<T> clazz) {
if (beanMethodFunc.isEmpty()) initBeanMethod(clazz);
if (beanMethodFunc.size() != methods.size()) throw new IllegalArgumentException();
List<List<String>> dataOfPage = new ArrayList<>();
List<String> rowOfPage = new ArrayList<>();
for (int i = 0; i < beanOfPage.size(); i++) {
T t = beanOfPage.get(i);
rowOfPage.clear();
for (int j = 0; j < beanMethodFunc.size(); j++) {
Function<T, Object> tObjectFunction = beanMethodFunc.get(j);
Object data = tObjectFunction.apply(t);
rowOfPage.add(String.valueOf(data));
}
dataOfPage.add(rowOfPage);
}
return dataOfPage;
}
/**
* 如不进行配置Bean类的包装类型,就走这个反射拿数据
* @param clazz 数据类型
*/
private void initBeanMethod(Class<T> clazz) {
Method[] allMethods = clazz.getDeclaredMethods();
for (Method method : allMethods) {
if (method.getName().startsWith("get") || method.getName().startsWith("is")){
this.methods.add(method);
beanMethodFunc.add(t -> {
try {
return method.invoke(t,null);//勿需强转,否则错误
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
});
}
}
}
private void initTitle() {
for (OutputBeanWrapper<T> tOutputBeanWrapper : beanDataGetFunc) {
title.add(tOutputBeanWrapper.getColumnName());
}
title = outputType.onTitleInitialCompletion(title);
}
public void execute() {
super.execute(outputType.createPlatform(targetOutputFile));
}
}
先无需了解出现所谓的平台
等难以理解的字眼,实际上是我也不知道如何命名想出来了的名称。
接下来是接口:OutputType
接口可以根据你的需求进行扩展,但是注意稍后可能因为拓展类过多,导致修改接口需要进行大量改动
import android.content.Context;
import java.io.File;
import java.util.List;
public abstract class OutputType<P,I> {
Context context;
File file;
/**
* create some Capacity that can supplier .rw with file
* @param file targetFile
* @return Platform
*/
public abstract P createPlatform(File file);
/**
* 取得平台的读写能力
* @param platform
* @return
*/
public abstract I getPlatformCapacity(P platform,String pageName,int index);
public abstract void beforeWrite(P platform,I platformCapacity);
/**
* 为页面设置开头第一行
* @param page
* @param rowData
*/
public abstract void initTitle(I page, List<String> rowData);
/**
* 标题拦截重组
* @param title 标题
* @return 重组标题
*/
public List<String> onTitleInitialCompletion(List<String> title){
return title;
}
/**
* 为页面组装数据
* @param page
* @param yIndex
* @param rowData
*/
public abstract void initBody(I page,int yIndex, List<String> rowData);
/**
* 分享
* @param integer
*/
public abstract void onPostExecute(Integer integer);
/**
* 每当完成一个页面调用,进行某些操作
* @param page
*/
public void releasePage(I page){}
/**
* 每当完成所有页面,进行某些操作
* @param platform
*/
public abstract void releasePlatform(P platform);
public abstract void notifyWriteFinished();
/**
* 获取输出文件后缀,带.
* @return
*/
abstract String getType();
public OutputType<P, I> setContext(Context context) {
this.context = context;
return this;
}
public OutputType<P, I> setFile(File file) {
this.file = file;
return this;
}
}
最后的数据包装类型 OutputBeanWrapper
。用于存放列名,数据类方法,转换样式
import android.text.TextUtils;
import java.util.function.Function;
public class OutputBeanWrapper<T> {
private Function<T, Object> methodFunction = null;
private String columnName = "";
private String patternTrans = null;
public OutputBeanWrapper(Function<T, Object> methodFunction, String columnName) {
this.methodFunction = methodFunction;
this.columnName = columnName;
}
public OutputBeanWrapper(Function<T, Object> methodFunction, String columnName, String patternTrans) {
this.methodFunction = methodFunction;
this.columnName = columnName;
this.patternTrans = patternTrans;
}
public boolean needTransform(){
return !TextUtils.isEmpty(patternTrans);
}
public Function<T, Object> getMethodFunction() {
return methodFunction;
}
public String getColumnName() {
return columnName;
}
public String getPatternTrans() {
return patternTrans;
}
}
对于OutputType的实现
Xls风格:
XlsType
用于输出xls类型文件
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import androidx.core.content.FileProvider;
import com.manridy.iband.factory.application.App;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import jxl.Workbook;
import jxl.write.Label;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import jxl.write.WriteException;
/**
* .xls 文件输出
*/
public class XlsType extends OutputType<WritableWorkbook,WritableSheet>{
@Override
public WritableWorkbook createPlatform(File file) {
try {
return Workbook.createWorkbook(new FileOutputStream(file));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public WritableSheet getPlatformCapacity(WritableWorkbook platform, String pageName, int index) {
return platform.createSheet(pageName,index);
}
@Override
public void beforeWrite(WritableWorkbook platform, WritableSheet platformCapacity) {
//nothing
}
@Override
public void initTitle(WritableSheet platform, List<String> rowData) {
for (int i = 0; i < rowData.size(); i++) {
try {
platform.addCell(new Label(i, 0, rowData.get(i)));
} catch (WriteException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void initBody(WritableSheet platform, int yIndex, List<String> rowData) {
for (int i = 0; i < rowData.size(); i++) {
try {
platform.addCell(new Label(i, yIndex + 1, rowData.get(i)));
} catch (WriteException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void onPostExecute(Integer integer) {
if (!file.exists()) return;
final Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(App.instance, context.getPackageName() + ".provider", file);
} else {
uri = Uri.fromFile(file);
}
Intent intent = new Intent(Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(uri,context.getContentResolver().getType(uri));
context.startActivity(Intent.createChooser(intent, file.getName()));
}
@Override
public void releasePlatform(WritableWorkbook platform) {
try {
platform.write();
platform.close();
} catch (IOException | WriteException e) {
throw new RuntimeException(e);
}
}
@Override
public void notifyWriteFinished() {
}
@Override
public String getType() {
return ".xls";
}
}
Txt风格
TxtType
用于输出Txt文档
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import androidx.core.content.FileProvider;
import com.manridy.iband.factory.application.App;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
public class TxtType extends OutputType<File, FileWriter> {
private FileWriter fileWriter;
@Override
public File createPlatform(File file) {
return file;
}
@Override
public FileWriter getPlatformCapacity(File platform, String pageName, int index) {
try {
if (fileWriter == null){
fileWriter = new FileWriter(platform);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return fileWriter;
}
@Override
public void beforeWrite(File platform, FileWriter platformCapacity) {
try {
platformCapacity.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void initTitle(FileWriter page, List<String> rowData) {
try {
for (String rowDatum : rowData) {
page.write(rowDatum + " ");
}
page.write("\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void initBody(FileWriter page, int yIndex, List<String> rowData) {
try {
for (String rowDatum : rowData) {
page.write(rowDatum + " ");
}
page.write("\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void onPostExecute(Integer integer) {
if (!file.exists()) return;
final Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(App.instance, context.getPackageName() + ".provider", file);
} else {
uri = Uri.fromFile(file);
}
Intent intent = new Intent(Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(uri,context.getContentResolver().getType(uri));
context.startActivity(Intent.createChooser(intent, file.getName()));
}
@Override
public void releasePlatform(File platform) {
//do nothing
}
@Override
public void notifyWriteFinished() {
try {
fileWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
String getType() {
return ".txt";
}
}
Csv风格
CsvType
用于输出csv类型文件
csv实际上是对Txt类型的重写而已
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
public class CsvType extends TxtType{
@Override
public void initTitle(FileWriter page, List<String> rowData) {
for (String rowDatum : rowData) {
try {
page.write(rowDatum+",");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
try {
page.write("\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void initBody(FileWriter page, int yIndex, List<String> rowData) {
for (String rowDatum : rowData) {
try {
page.write(rowDatum+",");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
try {
page.write("\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
String getType() {
return ".csv";
}
}
使用
- 场景1:仅仅输出整个数据Bean类,不对bean类当中的属性做删减或者转换操作(注意!实际实现是通过反射拿bean类的getter方法,如果你的bean类没有getter方法就不会有输出;getter方法顺序混乱(不和字段属性顺序一样),输出的结果也会混乱):
List<List<Test>> data = new ArrayList<>();
data.add(Arrays.asList());//...
List<String> title = new ArrayList<>();
title.add("...");//...
CommOutput<Test, WritableWorkbook, WritableSheet> commOutput = new CommOutput<>(
this,
data,
Test.class,
title,
"输出文件名称",
new XlsType()
);
commOutput.execute();
- 场景2:选择性输出某些属性,并且对Long类型的时间字段进行转换:
List<List<Test>> data = new ArrayList<>();
data.add(Arrays.asList());//...
CommOutput2<Test, WritableWorkbook, WritableSheet> commOutput = new CommOutput2<>(
this,
data,
Arrays.asList(
new OutputBeanWrapper<>(Test::getStep, "步数"),
new OutputBeanWrapper<>(Test::getNotWorn, "未佩戴次数"),
new OutputBeanWrapper<>(Test::getLightTime1, "浅睡一期"),
new OutputBeanWrapper<>(Test::getLightTime2, "浅睡二期"),
new OutputBeanWrapper<>(Test::getDeepTime, "深睡"),
new OutputBeanWrapper<>(Test::getSleep_test, "戒指使用时间"),
new OutputBeanWrapper<>(Test::getTime, "数据生成时间", "yyyy-MM-dd HH:mm:ss")
),
"测试数据",
new XlsType()
);
commOutput.execute();