You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android 13(API33)文件写入权限申请与Excel保存失败排查

问题:Android 13(API33)下Excel文件保存失败
  • 应用目标版本为Android13(API33),可从列表生成Excel文件,但在API33设备上保存失败,低版本系统可正常运行。
  • 错误日志提示java.io.FileNotFoundException,显示文件或目录不存在。

使用的依赖库

implementation 'org.apache.poi:poi:4.0.0'
implementation 'org.apache.poi:poi-ooxml:4.0.0'

Manifest声明的权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

权限检查代码

private void checkPermission() {
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
        if (getApplicationContext().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
            String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
            requestPermissions(permissions, 1);
        } else {
            importData();
        }
    } else {
        importData();
    }
}

Excel生成与保存代码

private void importData() {
    // 模拟数据
    try {
        userList.add(new SheetModel("kawcher", "0181238383", "nmkawcher112@gmail.com"));
        userList.add(new SheetModel("shuvo", "0171238383", "demo1@gmail.com"));
        userList.add(new SheetModel("hasan", "0161238383", "demo2@gmail.com"));
        userList.add(new SheetModel("habib", "0151238383", "demo3@gmail.com"));
        userList.add(new SheetModel("selim", "0131238383", "demo4@gmail.com"));
        userList.add(new SheetModel("tayeb", "0121238383", "demo5@gmail.com"));
        userList.add(new SheetModel("abul kalam", "0191238383", "demo6@gmail.com"));
        userList.add(new SheetModel("Kamal", "0101238383", "demo7@gmail.com"));
    }catch (Exception e){e.printStackTrace();}

    if (userList.size() > 0) {
        createXlFile();
    } else {
        Toast.makeText(this, "列表为空", Toast.LENGTH_SHORT).show();
    }
}

private void createXlFile() {
    Workbook wb = new HSSFWorkbook();
    Cell cell = null;
    Sheet sheet = wb.createSheet("Demo Excel Sheet");
    
    // 创建表头
    Row row = sheet.createRow(0);
    cell = row.createCell(0);
    cell.setCellValue("姓名");
    cell = row.createCell(1);
    cell.setCellValue("手机号");
    cell = row.createCell(2);
    cell.setCellValue("邮箱");
    
    // 设置列宽
    sheet.setColumnWidth(0, (20 * 200));
    sheet.setColumnWidth(1, (30 * 200));
    sheet.setColumnWidth(2, (30 * 200));
    
    // 填充数据行
    for (int i = 0; i < userList.size(); i++) {
        Row row1 = sheet.createRow(i + 1);
        cell = row1.createCell(0);
        cell.setCellValue(userList.get(i).getName());
        cell = row1.createCell(1);
        cell.setCellValue((userList.get(i).getPhoneNo()));
        cell = row1.createCell(2);
        cell.setCellValue(userList.get(i).getEmail());
        // 重复设置列宽,可优化
        sheet.setColumnWidth(0, (20 * 200));
        sheet.setColumnWidth(1, (30 * 200));
        sheet.setColumnWidth(2, (30 * 200));
    }
    
    String folderName = "Import Excel";
    String fileName = folderName + System.currentTimeMillis() + ".xls";
    String path = Environment.getExternalStorageDirectory() + File.separator + folderName + File.separator + fileName;

    File file = new File(Environment.getExternalStorageDirectory() + File.separator + folderName);
    if (!file.exists()) {
        file.mkdirs();
    }

    FileOutputStream outputStream = null;
    try {
        outputStream = new FileOutputStream(path);
        wb.write(outputStream);
        Toast.makeText(getApplicationContext(), "Excel已保存至: " + path, Toast.LENGTH_SHORT).show();
    } catch (IOException e) {
        e.printStackTrace();
        Toast.makeText(getApplicationContext(), "保存失败", Toast.LENGTH_LONG).show();
        try {
            outputStream.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

错误日志

/System.err: java.io.FileNotFoundException: /storage/emulated/0/Import Excel/Import Excel1681376608447.xls: open failed: ENOENT (No such file or directory)
        at libcore.io.IoBridge.open(IoBridge.java:574)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:236)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:125)
        at com.roomack.amlaak.view.activity.MainActivity.createXlFile(MainActivity.java:1125)
        at com.roomack.amlaak.view.activity.MainActivity.importData(MainActivity.java:1051)
        at com.roomack.amlaak.view.activity.MainActivity.checkPermission(MainActivity.java:1020)
        at com.roomack.amlaak.view.activity.MainActivity.access$300(MainActivity.java:100)
        at com.roomack.amlaak.view.activity.MainActivity$18.onClick(MainActivity.java:821)
        at android.view.View.performClick(View.java:7560)
        at android.view.View.performClickInternal(View.java:7533)
        at ndroid.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
        at android.view.View$PerformClick.run(View.java:29754)
W/System.err:     at android.os.Handler.handleCallback(Handler.java:942)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:211)
        at android.os.Looper.loop(Looper.java:300)
        at android.app.ActivityThread.main(ActivityThread.java:8143)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1028)
    Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory)
        at libcore.io.Linux.open(Native Method)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:563)
        at libcore.io.BlockGuardOs.open(BlockGuardOs.java:274)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:563)
        at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:8019)
        at libcore.io.IoBridge.open(IoBridge.java:560)

解决方案

1. 适配Android 13存储权限变更

Android13(API33)彻底废弃了WRITE_EXTERNAL_STORAGE权限,针对不同场景有两种处理方式:

  • 非媒体文件(如Excel):优先使用应用专属存储,无需申请任何权限
  • 必须保存到公共目录:需要申请MANAGE_EXTERNAL_STORAGE权限,且需用户手动授权

2. 快速修复:改用应用专属存储

修改createXlFile中的路径逻辑,无需权限即可保存:

// 获取应用专属外部存储目录,无需权限
File appRootFolder = getExternalFilesDir(null);
String excelFolderName = "Import Excel";
File excelFolder = new File(appRootFolder, excelFolderName);

// 确保文件夹创建成功
if (!excelFolder.exists()) {
    boolean isFolderCreated = excelFolder.mkdirs();
    if (!isFolderCreated) {
        Toast.makeText(this, "文件夹创建失败", Toast.LENGTH_SHORT).show();
        return;
    }
}

String fileName = "Excel_" + System.currentTimeMillis() + ".xls";
File excelFile = new File(excelFolder, fileName);

// 写入文件
try (FileOutputStream outputStream = new FileOutputStream(excelFile)) {
    wb.write(outputStream);
    Toast.makeText(getApplicationContext(), "Excel已保存至: " + excelFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
} catch (IOException e) {
    e.printStackTrace();
    Toast.makeText(getApplicationContext(), "保存失败", Toast.LENGTH_LONG).show();
}

3. 如需保存到公共目录:适配MANAGE_EXTERNAL_STORAGE权限

更新权限检查代码

private void checkPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        // Android10+ 需要检查全存储权限
        if (!Environment.isExternalStorageManager()) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
            Uri uri = Uri.fromParts("package", getPackageName(), null);
            intent.setData(uri);
            startActivityForResult(intent, 101);
        } else {
            importData();
        }
    } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
        // Android6-9 检查WRITE权限
        if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
            requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        } else {
            importData();
        }
    } else {
        // Android5及以下直接执行
        importData();
    }
}

// 处理权限请求结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 101) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if (Environment.isExternalStorageManager()) {
                importData();
            } else {
                Toast.makeText(this, "需要授予全存储权限才能保存到公共目录", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

4. 代码优化点

  • 移除循环中重复设置列宽的代码,仅在表头后设置一次即可
  • 使用try-with-resources自动关闭输出流,避免资源泄漏

内容的提问来源于stack exchange,提问作者Amin Azizzadeh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 09:35:02