安卓开发使用POI导出Excel表格

安卓开发使用POI导出Excel表格

关键词【安卓开发】【导出EXCEL表格】【XLS】【XLSX】

在开始之前我先介绍一下在安卓上导出Excel的多种方案

1.JXL

JXL(Java Excel API)则专注于处理Excel文件,它是Apache POI的一个轻量级替代方案。JXL库的API更为简洁,对于只涉及Excel操作的应用程序来说,它可能更受青睐。

但是我使用下来有几个痛点不得不放弃JXL

优点上JXL使用简单,可以设计出样式丰富的xls表格,满足只需要少量数据导出表格的需求

缺点只支持XLS导出(单sheet最多65535行)不满足客户几十万条数据单表导出的需求,导出性能较差占用内存过高

总体:如果你需要导出的Excel数据量不大,需要快速接入可以选择JXL进行开发,这里我放一个可以参考的链接

作者:motosheep 链接:关于安卓jxl的excel操作(一)_安卓开发处理excel-CSDN博客

2.POI for android

这是我最终使用的,在github上有个老外大神基于Apache POI 修改最终用于Android 5+的Excel导出库,这里我先放个链接:

作者:andruhon 仓库链接:andruhon/android5xlsx: Reading and Writing XLSX and XLS on Android 5+ with Apache POI

使用 Apache POI 在 Android 5 上读取和写入 XLSX 和 XLS

在 Android 上使用 Apache POI 和 Dalvik VM 是一项相当具有挑战性的任务。在 Android 5+ 上使用 Apache POI 与 ART VM 和 Build Tools 21+ 相比,要容易得多。

基于这个库我实现了在Android5.1、Android7.1、Android11上运行并导出Excel,并且性能由于采用了SXSSFWorkbook

SXSSFWorkbook是Apache POI库的一部分,专门用于处理大数据量的Excel导出。它采用了流式处理的方式,只会将最新的Excel行保存在内存中,而之前的行会被写入到硬盘的临时文件中。这种方式通过牺牲部分功能(如公式求值、Sheet克隆等)来换取内存的高效使用,避免在处理大数据量时发生内存溢出。
SXSSFWorkbook:
属于Apache POI库(3.8+版本),专为大数据量导出(.xlsx格式) 设计。通过流式处理将数据分批写入磁盘,仅保留少量行在内存中,避免OOM。适合百万级数据导出136。

WritableWorkbook:
属于JExcelAPI(JXL库),主要处理旧版.xls格式(Excel 97-2003)。全内存操作,数据量超过65536行会报错,且性能在大数据量下显著下降(如20万行卡顿)

内存占用也相较于JXL有大幅度降低

实测POI导出162000条数据
内存占用30MB浮动
Excel生成时间37978 ms

应该能满足大部分开发者的需求

  • SXSSFWorkbook 如果
    ✅ 需导出 >1万行数据(尤其是.xlsx);
    ✅ 内存有限,避免OOM;
    ✅ 接受牺牲部分功能(如公式计算)。
  • WritableWorkbook 如果
    ✅ 处理 <6.5万行的小型.xls文件
    ✅ 依赖旧版JXL库且不能导出XLSX

开始使用POI

1.引入jar

implementation files('libs\\poi-3.12-android-20240110-fixed.jar')
implementation files('libs\\poi-ooxml-schemas-3.12-20150511.jar')

2.添加ExcelUtils工具类

这个类是我写在自己项目上的,格式、调用方式只能当作参考,代码逻辑或许不是很好,仅供参考:

/**
 * Excel导出工具
 *
 * @author Ganfandw
 * @date : 2025/05/29 修改
 */
public class ExcelUtils {
    //Excel文件存储路径,可以自行修改方法 实现动态路径
    private static String FILE_PATH = Environment.getExternalStorageDirectory() + File.separator + "Demo" + "/Excel/";
    //数据Bean 里面存放导出的数据,这里我会添加一个内部类用于演示
    private DataBean bean;
    private Cell cell;
    private static String fileName = "";
    private static String sheetName = "";
    private static SXSSFWorkbook wb;
    private static Sheet sheet;
    private static String[] title = {"用户id", "时间(年/月/日/时间)", "数据1", "数据2"};
    private static File excelFile;
    private static final int CELL_WIDTH = 512;
    private static int lastRowNum = 2;
    private static FileOutputStream fileOutputStream = null;
    private static long startTime;
    private static ExcelUtils instance = null;
    private ProgressInterface mProgressBar = null;
    private static CellStyle cellStyle_cell = null;
    private boolean cancelTask = false;
    private String TAG = "ExcelUtils";
    private Context mContext;

    // 私有构造方法,防止外部直接实例化
    private ExcelUtils(Context context) {
        // 初始化操作
        mContext = context;
    }

    // 获取单例实例的方法
    public static ExcelUtils getInstance(Context context) {
        if (instance == null) {
            synchronized (ExcelUtils.class) {
                if (instance == null) {
                    instance = new ExcelUtils(context);
                }
            }
        }
        return instance;
    }

    /**
     * 初始化Excel表格文件名、sheet名、第一列
     */
    public ExcelUtils build(String fileName, String sheetName, String medicalCode, String userName, String userGender, String doctorName) {
        try {
            startTime = System.currentTimeMillis(); //起始时间
            cancelTask = false;
            //初始化Excel文件目录
            initExcelPath();
            setFileName(fileName);
            setSheetName(sheetName);
            File dir = new File(FILE_PATH);
            if (!dir.exists()) dir.mkdirs();
            excelFile = new File(FILE_PATH + fileName + ".xlsx");
            // 创建excel xlsx格式
            wb = new SXSSFWorkbook(100);
            cellStyle_cell = wb.createCellStyle();
            cellStyle_cell.setBorderTop(CellStyle.BORDER_THIN);
            cellStyle_cell.setBorderBottom(CellStyle.BORDER_THIN);
            cellStyle_cell.setBorderLeft(CellStyle.BORDER_THIN);
            cellStyle_cell.setBorderRight(CellStyle.BORDER_THIN);
            cellStyle_cell.setAlignment(CellStyle.ALIGN_CENTER);
            cellStyle_cell.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
            // 创建工作表
            sheet = wb.createSheet(sheetName);
            //创建行对象
            Row row = sheet.createRow(0);
            row.setHeight((short) (3 * 256));
            // 创建单元格并应用样式
            Cell cell0 = row.createCell(0);
            cell0.setCellValue("病例号");
            cell0.setCellStyle(cellStyle_cell);

            Cell cell1 = row.createCell(1);
            cell1.setCellValue(medicalCode);
            cell1.setCellStyle(cellStyle_cell);

            Cell cell2 = row.createCell(2);
            cell2.setCellValue("姓名");
            cell2.setCellStyle(cellStyle_cell);

            Cell cell3 = row.createCell(3);
            cell3.setCellValue(userName);
            cell3.setCellStyle(cellStyle_cell);

            Cell cell4 = row.createCell(4);
            cell4.setCellValue("性别");
            cell4.setCellStyle(cellStyle_cell);

            Cell cell5 = row.createCell(5);
            cell5.setCellValue(userGender);
            cell5.setCellStyle(cellStyle_cell);

            Cell cell6 = row.createCell(6);
            cell6.setCellValue("医生");
            cell6.setCellStyle(cellStyle_cell);

            Cell cell7 = row.createCell(7);
            cell7.setCellValue(doctorName);
            cell7.setCellStyle(cellStyle_cell);

            //创建行对象
            Row row1 = sheet.createRow(1);
            row1.setHeight((short) (3 * 256));
            // 设置有效数据的行数和列数
            int colNum = title.length;
            for (int i = 0; i < colNum; i++) {
                sheet.setColumnWidth(i, CELL_WIDTH * title[i].length());  // 显示8个字符的宽度
                cell = row1.createCell(i);
                cell.setCellStyle(cellStyle_cell);
                cell.setCellValue(title[i]);//第一行
            }
            lastRowNum = 2;
            fileOutputStream = new FileOutputStream(excelFile);
        } catch (Exception e) {

        }
        return instance;
    }

    //同步锁  synchronization lock
    private static ReentrantLock reentrantLock = new ReentrantLock(true);

    /**
     * 写数据到Excel
     *
     * @param listData
     * @return
     */
    public ExcelUtils write(List<DataBean> listData) {
        try {
            reentrantLock.lock();
            Row row = null;
            // 导入数据
            Log.d(TAG, "开始写入数据,当前总行数" + sheet.getLastRowNum() + " / " + lastRowNum);
            for (int rowNum = 0; rowNum < listData.size(); rowNum++, lastRowNum++) {
                if (cancelTask) {
                    Log.d(TAG, "取消任务");
                    return instance;
                }
                // 之所以rowNum + 1 是因为要设置第二行单元格
                row = sheet.createRow(lastRowNum);
                // 设置单元格显示宽度
                row.setHeightInPoints(26f);

                bean = listData.get(rowNum);
                for (int j = 0; j < title.length; j++) {
                    cell = row.createCell(j);
                    cell.setCellStyle(cellStyle_cell);
                    //要和title[]一一对应
                    switch (j) {
                        case 0:
                            //实时时间
                            cell.setCellValue(bean.getUserId());
                            break;
                        case 1:
                            //实时压力
                            cell.setCellValue(bean.getData1());
                            break;
                        case 2:
                            //累计高压时长
                            cell.setCellValue(bean.getData2());
                            break;
                        case 3:
                            //实时时间
                            cell.setCellValue(bean.getRealTime());
                            break;
                    }
                }

            }
        } catch (Exception e) {
            Log.e(TAG, "WRITE EXCEL" + e.getMessage() + e.getStackTrace());
        } finally {
            long endTime = System.currentTimeMillis(); //结束时间
            long runTime = endTime - startTime;
            Log.e("Excel", String.format("生成Excel使用时间 %d ms", runTime));
            reentrantLock.unlock();
        }
        return instance;
    }

    public void cancelTask() {
        cancelTask = true;
    }

    /**
     * 初始化Excel表格文件名、sheet名、第一列
     */
    public ExcelUtils progress(ProgressInterface progressInterface) {
        this.mProgressBar = progressInterface;
        return instance;
    }



    /**
     * 这里我设置的-1是导出成功,-2是导出报错
     * 如果要添加导出进度,你可以在activity里添加自定义弹窗,根据数据库里面的数据进行分页,给progress设置max值
     * 每执行一次write方法,进度+1 实时设置上去 直到读取到-1 就导出完成
     * */
    public interface ProgressInterface {
        void OnProgress(int value);
    }

    private static boolean commitSuccess = false;

    /**
     * 提交数据到文件中
     *
     * @return boolean true 写入成功
     */
    @SuppressLint("WrongConstant")
    public boolean commit() {
        commitSuccess = false;
        if (cancelTask) {
            Log.d(TAG, "取消任务");
            return false;
        }
        //动态获取权限
        //初始化软件的动态权限 Initializes dynamic permissions for software
        PermissionX.init((FragmentActivity) mContext)
                .permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
                .request(new RequestCallback() {
                    @Override
                    public void onResult(boolean allGranted, @NonNull List<String> grantedList, @NonNull List<String> deniedList) {
                        if (allGranted) {
                            try {
                                wb.write(fileOutputStream);
                                wb.close();
                                long endTime = System.currentTimeMillis(); //结束时间
                                long runTime = endTime - startTime;
                                commitSuccess = true;
                                if (mProgressBar != null) mProgressBar.OnProgress(-1);
                                Log.e(TAG, String.format("Excel制作完成使用时间 %d ms", runTime));
                            } catch (IOException e) {
                                if (e.toString().contains("EPERM")) {
                                    commitSuccess = true;
                                    if (mProgressBar != null) mProgressBar.OnProgress(-1);
                                } else {
                                    if (mProgressBar != null) mProgressBar.OnProgress(-2);
                                    Log.e(TAG, e.toString());
                                    commitSuccess = false;
                                }
                            } finally {
                                if (fileOutputStream != null) {
                                    try {
                                        fileOutputStream.close();
                                    } catch (IOException e) {
                                        throw new RuntimeException(e);
                                    }
                                    fileOutputStream = null;
                                }
                            }
                        } else {
                            Log.e(TAG, "未赋予存储权限,Excel导出失败");
                            if (mProgressBar != null) mProgressBar.OnProgress(-2);
                        }
                    }
                });

        return commitSuccess;
    }

    /**
     * 初始化Excel文件路径
     */
    private static void initExcelPath() {
        Log.e("Excel", "检测文件路径");
        File PATH = new File(FILE_PATH);
        // 目录不存在则自动创建目录
        if (!PATH.exists()) {
            PATH.mkdirs();
        }
    }

    public static void setFileName(String fileName) {
        ExcelUtils.fileName = fileName;
    }

    public static void setSheetName(String sheetName) {
        ExcelUtils.sheetName = sheetName;
    }


    public static class DataBean {
        private Long id;
        //配置名称 用户自定义id
        private Long userId;
        //配置名称 演示数据1
        private String data1;
        //配置名称 演示数据2
        private String data2;
        //实时时间
        private long realTime;

        public DataBean(Long id, Long userId, String data1, String data2,
                        long realTime) {
            this.id = id;
            this.userId = userId;
            this.data1 = data1;
            this.data2 = data2;
            this.realTime = realTime;
        }

        public DataBean() {
        }

        public Long getId() {
            return this.id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public Long getUserId() {
            return this.userId;
        }

        public void setUserId(Long userId) {
            this.userId = userId;
        }

        public String getData1() {
            return data1;
        }

        public String getData2() {
            return data2;
        }

        public void setData1(String data1) {
            this.data1 = data1;
        }

        public void setData2(String data2) {
            this.data2 = data2;
        }

        public long getRealTime() {
            return this.realTime;
        }

        public void setRealTime(long realTime) {
            this.realTime = realTime;
        }
    }

}

3.调用工具类并导出Excel

new Thread(new Runnable() {
                    @Override
                    public void run() {
                        excelUtils = ExcelUtils.getInstance(PwmControl.this);
                        excelUtils.build("演示参考", "Record", "SOS", "囧.史密斯", "18", "凉宫").progress(new ExcelUtils.ProgressInterface() {
                            @Override
                            public void OnProgress(int value) {
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        if (value == -1) {
                                            Log.e("ExcelUtils", "导出成功");
                                            //这里做延迟是因为安卓开发板读写性能很差,需要一段时间才能彻底写入,如果是手机可以取消掉handler
                                            new Handler().postDelayed(new Runnable() {
                                                @Override
                                                public void run() {
                                                    //这个自己去设计逻辑 在哪里取消
                                                    if (!cancelTask) {
//                                                        progressDialog.setText(getString(R.string.text_export_success) + fileName);
//                                                        progressDialog.hideProgress();
//                                                        progressDialog.setCancelVisible(false);
//                                                        progressDialog.setConfirmVisible(true);
                                                    }
                                                }
                                            }, 5 * 1000);
                                        } else if (value == -2) {
                                            runOnUiThread(new Runnable() {
                                                @Override
                                                public void run() {
//                                                    cancelTask = true;
//                                                    progressDialog.setText("Excel导出失败,请检查系统日志。");
//                                                    progressDialog.hideProgress();
                                                }
                                            });

                                        }
                                    }
                                });

                            }
                        });
                        //progress = 0;
                        List<ExcelUtils.DataBean> chunk = new ArrayList<>();
//                            chunk = daoSession.getDataBeanDao().queryBuilder()
//                                    .where(DataBeanDao.Properties.UserId.eq(userBean.getId()))
//                                    .offset(offset)
//                                    .limit(pageSize)
//                                    .list();
                        for (int i = 0; i < 1000; i++) {
                            chunk.add(new ExcelUtils.DataBean(1L, (long) i + 1000, "演示数据1" + i, "演示数据2" + i, 1));
                        }

//                        if (chunk.isEmpty()) {
//                            break;
//                        }
                        if (cancelTask) {
                            excelUtils.cancelTask();
                            return;
                        }
                        excelUtils.write(chunk);
                        //progress++;
//                            runOnUiThread(new Runnable() {
//                                @Override
//                                public void run() {
//                                    progressDialog.setProgress(progress);
//                                }
//                            });
                        chunk.clear();
                        chunk = null;
//                            offset += pageSize;
                        excelUtils.commit();
                    }
                }).start();
            }
        });

这些代码只是用于参考,并不是实际生产环境的代码,如果要监听进度可以自行实现progress 弹窗,建议导出Excel时 不要做其他操作

执行完成后导出Excel,在Windows上打开的效果,具体格式自行调整
在这里插入图片描述

如果你有更好的建议欢迎评论

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值