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

API 29以下版本无法移动照片到指定文件夹的技术求助

API 29以下版本MediaStore保存照片后无法移动的问题解决

问题根源

你用File(Environment.getExternalStorageDirectory(), name)定位照片是错误的——通过MediaStore保存的照片,默认不会直接存在外部存储根目录,而是会被系统存到DCIM或Pictures这类默认媒体目录下,所以你创建的photo对象实际指向的是一个不存在的文件,renameTo自然返回false。

另外,API 29以下版本中,WRITE_EXTERNAL_STORAGE是危险权限,除了在Manifest中声明,还需要动态申请权限,如果没有获取到权限,文件操作也会失败。

解决方案

有两种可行的处理方式:

方式一:通过SavedUri获取真实路径后移动照片

通过ContentResolver查询savedUri对应的真实文件路径,再执行移动操作:

override fun onImageSaved(output: ImageCapture.OutputFileResults) {
    val savedUri = output.savedUri ?: return
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
        // 1. 查询照片真实路径
        val projection = arrayOf(MediaStore.Images.Media.DATA)
        requireContext().contentResolver.query(savedUri, projection, null, null, null)?.use { cursor ->
            if (cursor.moveToFirst()) {
                val realPath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))
                val originalFile = File(realPath)
                // 2. 创建目标文件夹
                val albumFolder = File(
                    Environment.getExternalStorageDirectory(), "$appPicturesPath/$albumName"
                )
                if (!albumFolder.exists()) {
                    albumFolder.mkdirs()
                }
                // 3. 移动文件
                val targetFile = File(albumFolder, originalFile.name)
                val isMoved = originalFile.renameTo(targetFile)
                if (isMoved) {
                    // 4. 更新MediaStore数据库,避免媒体库中残留旧路径
                    val updateValues = ContentValues().apply {
                        put(MediaStore.Images.Media.DATA, targetFile.absolutePath)
                    }
                    requireContext().contentResolver.update(savedUri, updateValues, null, null)
                    Log.d(TAG, "照片移动成功")
                } else {
                    Log.e(TAG, "照片移动失败")
                }
            }
        }
    }
    // 后续跳转、广播等代码保持不变
}

方式二:保存时直接指定目标路径(无需事后移动)

API 29以下可以直接通过MediaStore.Images.Media.DATA字段指定保存路径,一步到位:

private fun takePhoto() {
    val name = SimpleDateFormat(FILENAME, Locale.US).format(System.currentTimeMillis())
    val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, name)
        put(MediaStore.MediaColumns.MIME_TYPE, PHOTO_TYPE)
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
            put(MediaStore.Images.Media.RELATIVE_PATH, "$appPicturesPath/$albumName")
        } else {
            // API29以下直接指定完整保存路径
            val albumFolder = File(
                Environment.getExternalStorageDirectory(), "$appPicturesPath/$albumName"
            )
            if (!albumFolder.exists()) {
                albumFolder.mkdirs()
            }
            val targetFile = File(albumFolder, name)
            put(MediaStore.Images.Media.DATA, targetFile.absolutePath)
        }
    }

    val outputOptions = ImageCapture.OutputFileOptions.Builder(
        requireContext().contentResolver,
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        contentValues
    ).build()

    // 后续拍照回调代码保持不变
}

注意事项

  1. 动态权限申请:API 23(Android 6.0)及以上版本,必须在代码中动态申请WRITE_EXTERNAL_STORAGE权限,否则文件操作会被系统拒绝。
  2. MediaStore数据库更新:方式一中移动文件后,一定要更新MediaStore的记录,否则系统媒体库中会残留旧的文件路径,导致照片无法被正确检索。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 18:26:05