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

在onResponse中调用createNewFile时OkHttp抛出IOException异常

解决Android中创建PDF文件时抛出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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 14:37:34