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

Android Kotlin开发:相机APP存图片提示“No such file or directory”

解决Android相机存储"No such file or directory"及公共存储目录创建失败问题

从Android 10(API 29)开始,系统启用Scoped Storage机制,限制了应用对外部存储的直接访问,这是你遇到两个问题的核心原因:

  • 直接调用Environment.getExternalStoragePublicDirectory()创建目录失败,因为Scoped Storage不允许应用直接操作公共存储的自定义目录;
  • 相机存储失败可能是FileProvider路径配置不符合Scoped Storage规范,或者目标目录未正确创建、权限未正确授予。

一、先修复FileProvider的配置错误

你的AndroidManifest中provider标签存在笔误:ndroid:exported="false"应改为android:exported="false",这个错误可能导致FileProvider无法正常工作。


二、解决方案选择

根据存储需求,分两种场景给出方案:

场景1:推荐使用应用私有外部存储(无需特殊权限)

应用私有外部存储路径下的文件仅当前应用可访问,卸载应用时会自动删除,完全符合Scoped Storage规范,且无需额外权限:

  1. 修改provider_paths.xml
    将原来的<external-path>替换为<external-files-path>,对应应用私有外部存储的路径:

    <?xml version="1.0" encoding="utf-8"?>
    <paths>
        <external-files-path name="external_files" path="Pictures"/>
    </paths>
    

    这里path="Pictures"对应context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)的路径,可根据需求调整子目录。

  2. 创建目标文件并生成Uri

    // 获取应用私有外部存储的Pictures目录
    val picturesDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    val targetDir = File(picturesDir, "MT/63301")
    // 确保目录存在(私有存储下mkdirs()会成功)
    if (!targetDir.exists()) {
        targetDir.mkdirs()
    }
    val targetFile = File(targetDir, "000.jpg")
    
    // 生成FileProvider的Uri
    val imageUri = FileProvider.getUriForFile(
        context,
        "${context.packageName}.provider",
        targetFile
    )
    
  3. 启动相机并授予权限
    启动相机时必须给Uri授予临时写入权限:

    val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
    // 授予相机APP临时写入权限
    takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
    // 启动相机
    context.startActivityForResult(takePictureIntent, REQUEST_CODE_TAKE_PHOTO)
    

场景2:必须存储到公共存储(如Pictures下的自定义目录)

如果需要将图片存到所有应用可见的公共存储,必须使用MediaStore API,不能直接操作File对象:

  1. 创建公共存储的图片Uri

    fun createPublicImageUri(context: Context, fileName: String): Uri? {
        val contentValues = ContentValues().apply {
            put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
            put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
            // 指定存储到Pictures目录下的自定义子目录
            put(MediaStore.Images.Media.RELATIVE_PATH, "${Environment.DIRECTORY_PICTURES}/MT/63301")
        }
        return context.contentResolver.insert(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            contentValues
        )
    }
    
  2. 启动相机并传入Uri

    val imageUri = createPublicImageUri(context, "000.jpg") ?: return
    val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
    takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
    context.startActivityForResult(takePictureIntent, REQUEST_CODE_TAKE_PHOTO)
    

三、额外注意事项

  • 如果应用需要兼容Android 9及以下,仍需在Manifest中声明WRITE_EXTERNAL_STORAGE权限,并在运行时申请;
  • 禁止使用Environment.getExternalStoragePublicDirectory()创建目录,该API在Android 10+已废弃且受限;
  • 私有存储场景下,需确保启动相机前目标目录已正确创建;公共存储场景下,必须通过MediaStore生成Uri,不能直接操作File。

内容的提问来源于stack exchange,提问作者Erik Johansson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 21:57:43