在onResponse中调用createNewFile时OkHttp抛出IOException异常
从你的日志和代码来看,核心问题是目标文件夹未成功创建,导致后续调用createNewFile()时找不到对应的目录,最终抛出No such file or directory异常。下面分点分析原因并给出针对性修复方案:
1. 权限缺失(Android 6.0+)
Android 6.0(API 23)及以上版本,访问公共外部存储需要动态申请WRITE_EXTERNAL_STORAGE权限;如果应用目标SDK是29(Android 10)及以上,访问公共目录还需要额外申请MANAGE_EXTERNAL_STORAGE权限。若未获取权限,系统会直接禁止文件操作。
修复步骤:
- 在
AndroidManifest.xml中声明基础权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Android 10+ 访问公共目录需添加 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" />
- 在代码中动态申请权限(Java示例):
private static final int REQUEST_STORAGE_PERMISSION = 100; // 检查权限,未授权则发起申请 if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_STORAGE_PERMISSION); return; // 权限未通过前终止文件操作 }
- 在
onRequestPermissionsResult中处理授权结果,仅当权限通过后再执行下载逻辑。
2. 文件夹创建逻辑存在漏洞
你当前使用folder.mkdir()创建文件夹,但mkdir()只能创建单级目录(若父目录不存在会直接失败);且未判断文件夹是否创建成功,即使创建失败仍继续执行后续操作。
修复代码:
// 规范路径拼接:避免子目录前加/,防止出现//Meidio这类冗余路径 File folder = new File(Environment.getExternalStorageDirectory(), "meidio"); if (!folder.exists()) { // 用mkdirs()替代mkdir(),可自动创建所有不存在的父目录(更健壮) boolean folderCreated = folder.mkdirs(); if (!folderCreated) { LoggerManager.handlesDebug("openPdfFromUrl", "Failed to create folder: " + folder.getAbsolutePath()); BusProvider.getInstance().post(new ShowHideLoadingEvent(false, false)); return; // 文件夹创建失败,终止后续流程 } }
3. 路径大小写不匹配
你描述的目标路径是/storage/emulated/0/meidio/document.pdf(小写m),但代码中创建的文件夹是Meidio(大写M)。Android文件系统区分大小写,这会导致实际创建的文件夹与预期路径不匹配,后续操作自然找不到目录。
修复:
统一文件夹名称的大小写,比如都改为小写meidio(与你预期的路径保持一致)。
4. FileProvider配置冗余混乱
你的paths.xml中有大量重复的路径配置(比如多个external-path),这会导致FileProvider的路径映射逻辑混乱,只需保留实际需要的配置即可。
简化后的paths.xml:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 映射外部根目录下的meidio文件夹 --> <external-path name="meidio_files" path="meidio" /> <!-- 应用私有外部存储(如需访问应用专属目录) --> <external-files-path name="app_external_files" path="." /> </paths>
5. 推荐:改用应用私有外部存储(Android 10+)
如果应用目标SDK是29及以上,建议优先使用应用私有外部存储,无需申请WRITE_EXTERNAL_STORAGE权限,且符合Android存储规范:
// 路径示例:/storage/emulated/0/Android/data/你的包名/files/meidio File folder = new File(context.getExternalFilesDir(null), "meidio"); if (!folder.exists()) { folder.mkdirs(); // 系统允许应用自由访问自身私有存储,无需额外权限 }
最终优化后的核心代码片段
public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException { if (response.isSuccessful()) { // 先确保权限已获取(此处省略权限检查逻辑,需自行补充) File folder = new File(Environment.getExternalStorageDirectory(), "meidio"); if (!folder.exists()) { boolean folderCreated = folder.mkdirs(); if (!folderCreated) { LoggerManager.handlesDebug("openPdfFromUrl", "Failed to create folder"); BusProvider.getInstance().post(new ShowHideLoadingEvent(false, false)); return; } } File file = new File(folder, "document.pdf"); if (file.exists()) { boolean fileDeleted = file.delete(); LoggerManager.handlesDebug("openPdfFromUrl", "File deleted: " + fileDeleted); } // 父目录已确保存在,直接创建文件 boolean fileCreated = file.createNewFile(); // 后续FileProvider与文件写入逻辑 Uri uriCreated; if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { uriCreated = FileProvider.getUriForFile(MDApplication.getCurrentAppContext(), MDApplication.getCurrentAppContext().getPackageName() + ".provider", file); } else { uriCreated = Uri.fromFile(file); } BufferedSink sink = Okio.buffer(Okio.sink(file)); sink.writeAll(response.body().source()); sink.close(); if (fileCreated) { Intent target = new Intent(Intent.ACTION_VIEW); target.setData(uriCreated); target.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); context.startActivity(target); LoggerManager.handlesDebug("openPdfFromUrl", "Response OK"); } else { LoggerManager.handlesDebug("openPdfFromUrl", "Response OK, file already exists"); } BusProvider.getInstance().post(new ShowHideLoadingEvent(false, false)); } }
内容的提问来源于stack exchange,提问作者Víctor Martín

