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
相关产品推荐
相关产品推荐

