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

如何将CameraX拍摄的图片保存到共享存储以在相册中访问

如何将CameraX拍摄的图片保存到共享存储并在系统相册显示

要实现将CameraX拍摄的图片保存到共享存储并让系统相册识别,你需要修改takePhoto方法的实现,改用基于MediaStore的文件保存逻辑,而不是仅在内存中处理Bitmap。以下是具体的修改步骤和完整代码:


1. 权限适配

确保你的应用拥有必要的存储权限:

  • Android 9(API 28及以下):需要申请WRITE_EXTERNAL_STORAGE权限
  • Android 10+(API 29及以上):无需申请写入权限,直接通过MediaStore插入媒体文件即可

在hasRequiredPermissions()方法中补充权限检查逻辑:

private fun hasRequiredPermissions(): Boolean {
    return if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
        ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
    } else {
        // Android 10+ 无需写入权限,只需确保相机权限已获取
        ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
    }
}

2. 构建MediaStore保存参数

创建方法生成图片的保存路径和元数据,确保系统相册能识别:

private fun createImageContentValues(): ContentValues {
    val displayName = "CameraX_${System.currentTimeMillis()}.jpg"
    return ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
        put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/CameraX")
            put(MediaStore.MediaColumns.IS_PENDING, 1)
        }
    }
}

3. 修改takePhoto方法,使用文件保存回调

替换原来的OnImageCapturedCallback,改用takePicture的重载方法,传入OutputFileOptions并处理保存结果:

private fun takePhoto(
    controller: LifecycleCameraController,
    onPhotoTaken: (Bitmap, Uri) -> Unit // 新增Uri参数,返回保存后的图片路径
) {
    if (!hasRequiredPermissions()) {
        return
    }

    // 1. 生成MediaStore内容参数
    val contentValues = createImageContentValues()
    val imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
        ?: run {
            Log.e("Camera", "Failed to create image URI")
            return
        }

    // 2. 构建OutputFileOptions
    val outputOptions = ImageCapture.OutputFileOptions.Builder(contentResolver, imageUri, contentValues).build()

    // 3. 调用takePicture保存到文件
    controller.takePicture(
        outputOptions,
        ContextCompat.getMainExecutor(applicationContext),
        object : ImageCapture.OnImageSavedCallback {
            override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                // 4. 处理图片旋转(保持你原来的翻转逻辑)
                val imageBitmap = MediaStore.Images.Media.getBitmap(contentResolver, imageUri)
                val matrix = Matrix().apply {
                    if (controller.cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
                        postScale(1f, 1f)
                    } else {
                        // 前置摄像头水平翻转
                        postScale(-1f, 1f)
                    }
                }
                val rotatedBitmap = Bitmap.createBitmap(
                    imageBitmap,
                    0,
                    0,
                    imageBitmap.width,
                    imageBitmap.height,
                    matrix,
                    true
                )

                // 5. 将旋转后的Bitmap写回文件
                contentResolver.openOutputStream(imageUri)?.use { outputStream ->
                    rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
                }

                // 6. 通知系统相册刷新(Android 10+ 需要移除IS_PENDING标记)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    contentValues.clear()
                    contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0)
                    contentResolver.update(imageUri, contentValues, null, null)
                } else {
                    // 低版本发送广播刷新相册
                    sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, imageUri))
                }

                // 回调返回处理后的Bitmap和保存路径
                onPhotoTaken(rotatedBitmap, imageUri)
                imageBitmap.recycle()
                rotatedBitmap.recycle()
            }

            override fun onError(exception: ImageCaptureException) {
                Log.e("Camera", "Couldn't save photo: ", exception)
                // 保存失败时删除临时Uri
                contentResolver.delete(imageUri, null, null)
            }
        }
    )
}

关键说明

  • MediaStore优势:直接通过系统媒体库保存,无需手动创建文件夹,系统相册会自动识别
  • 图片旋转处理:保持了你原来的前后摄像头翻转逻辑,确保图片方向正确
  • 相册刷新:Android 10+ 通过更新IS_PENDING标记通知系统,低版本发送广播触发扫描
  • 资源回收:及时回收Bitmap避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 07:16:08