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
相关产品推荐
相关产品推荐

