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

Android Kotlin:如何将应用内图片复制到图库指定文件夹?

将应用内图片复制到图库自定义文件夹的正确实现

我尝试把应用文件夹里的图片复制到图库的自定义文件夹,修改了分享代码但保存逻辑失效,代码如下:

val extension = when (requireNotNull(pictureResult).format) {
                PictureFormat.JPEG -> "jpg"
                PictureFormat.DNG -> "dng"
                else -> throw RuntimeException("Unknown format.")
}
val timestamp = System.currentTimeMillis()  
val namePhoto = "picture_"+timestamp+"."+extension;
val destFile = File(filesDir, namePhoto)
val folder = "/CustomFolder"

CameraUtils.writeToFile(requireNotNull(pictureResult?.data), destFile) { file ->
            if (file != null) {

                // Code to share - it works
                /*
                val context = this@PicturePreviewActivity
                val intent = Intent(Intent.ACTION_SEND)
                intent.type = "image/*"
                val uri = FileProvider.getUriForFile(context, context.packageName + ".provider", file)
                intent.putExtra(Intent.EXTRA_STREAM, uri)
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                startActivity(intent)
                */

                // Code to save image to gallery - doesn't work :(
                val photoDirectory = File(Environment.DIRECTORY_PICTURES+folder, namePhoto)
                val sourcePath = Paths.get(file.toURI())
                Log.i("LOG","sourcePath : "+sourcePath.toString()) // /data/user/0/com.app.demo/files/picture_1663772068143.jpg
                val targetPath = Paths.get(photoDirectory.toURI())
                Log.i("LOG","targetPath : "+targetPath.toString()) // /Pictures/CustomFolder/picture_1663772068143.jpg
                Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING)

                // Error here but I don't know why

                
            } else {
                Toast.makeText(this@PicturePreviewActivity, "Error while writing file.", Toast.LENGTH_SHORT).show()
            }
        }

问题分析

  • 目标路径错误:Environment.DIRECTORY_PICTURES只是图库目录的名称,不是完整文件路径,直接拼接会导致路径无效。
  • 未处理文件夹创建:如果CustomFolder不存在,直接移动文件会触发IO异常。
  • Scoped Storage 限制:Android 10+ 禁止直接访问外部存储文件,Files.move会因权限被拒绝。
  • 未通知图库刷新:即使文件复制成功,图库也不会自动识别新图片。

解决方案

方案1:Android 10+ 推荐的 MediaStore 实现(无需存储权限)

符合Scoped Storage规范,无需申请传统存储权限:

val extension = when (requireNotNull(pictureResult).format) {
    PictureFormat.JPEG -> "jpg"
    PictureFormat.DNG -> "dng"
    else -> throw RuntimeException("Unknown format.")
}
val timestamp = System.currentTimeMillis()
val namePhoto = "picture_$timestamp.$extension"
val destFile = File(filesDir, namePhoto)

CameraUtils.writeToFile(requireNotNull(pictureResult?.data), destFile) { file ->
    if (file != null) {
        val contentValues = ContentValues().apply {
            put(MediaStore.Images.Media.DISPLAY_NAME, namePhoto)
            put(MediaStore.Images.Media.MIME_TYPE, if (extension == "jpg") "image/jpeg" else "image/x-adobe-dng")
            put(MediaStore.Images.Media.RELATIVE_PATH, "${Environment.DIRECTORY_PICTURES}/CustomFolder")
            put(MediaStore.Images.Media.IS_PENDING, 1)
        }

        val resolver = contentResolver
        val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)

        uri?.let {
            resolver.openOutputStream(it).use { outputStream ->
                file.inputStream().use { inputStream ->
                    inputStream.copyTo(outputStream!!)
                }
            }

            // 标记文件完成并通知图库刷新
            contentValues.clear()
            contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
            resolver.update(it, contentValues, null, null)

            Toast.makeText(this@PicturePreviewActivity, "图片已保存到图库", Toast.LENGTH_SHORT).show()
        } ?: run {
            Toast.makeText(this@PicturePreviewActivity, "保存失败", Toast.LENGTH_SHORT).show()
        }
    } else {
        Toast.makeText(this@PicturePreviewActivity, "写入文件失败", Toast.LENGTH_SHORT).show()
    }
}

方案2:Android 9及以下兼容方案(需存储权限)

需先申请WRITE_EXTERNAL_STORAGE权限,再进行文件操作:

val extension = when (requireNotNull(pictureResult).format) {
    PictureFormat.JPEG -> "jpg"
    PictureFormat.DNG -> "dng"
    else -> throw RuntimeException("Unknown format.")
}
val timestamp = System.currentTimeMillis()
val namePhoto = "picture_$timestamp.$extension"
val destFile = File(filesDir, namePhoto)
val folder = "CustomFolder"

CameraUtils.writeToFile(requireNotNull(pictureResult?.data), destFile) { file ->
    if (file != null) {
        // 检查存储权限(Android 9及以下需要)
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1001)
            return@writeToFile
        }

        // 获取图库根目录并创建自定义文件夹
        val picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
        val customDir = File(picturesDir, folder)
        if (!customDir.exists()) {
            customDir.mkdirs()
        }

        val targetFile = File(customDir, namePhoto)
        try {
            // 复制文件(用copyTo避免误删原文件)
            file.copyTo(targetFile, overwrite = true)
            // 通知图库刷新
            MediaScannerConnection.scanFile(this, arrayOf(targetFile.absolutePath), null) { _, _ ->
                Toast.makeText(this@PicturePreviewActivity, "图片已保存到图库", Toast.LENGTH_SHORT).show()
            }
        } catch (e: IOException) {
            e.printStackTrace()
            Toast.makeText(this@PicturePreviewActivity, "保存失败:${e.message}", Toast.LENGTH_SHORT).show()
        }
    } else {
        Toast.makeText(this@PicturePreviewActivity, "写入文件失败", Toast.LENGTH_SHORT).show()
    }
}

注意事项

  • Android 10+ 优先使用MediaStore方案,无需存储权限,符合系统规范。
  • 若使用文件操作方案兼容Android 10+,需在AndroidManifest.xml中添加android:requestLegacyExternalStorage="true",但仅作为临时兼容方案。
  • 无论哪种方案,都必须通知图库刷新,否则新图片不会立刻显示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 02:55:23