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

使用CameraX拍摄的图片无法压缩?求问题排查方案

问题根源分析

你的代码核心问题是修改的不是DCIM目录下的原拍摄文件,而是App内部存储里的副本:

  • getFile()方法把MediaStore中DCIM目录的拍摄文件,复制到了context.filesDir(App内部私有存储)下生成了一个副本
  • 你压缩后写回的是这个内部存储的副本,而用户实际查看的DCIM里的原文件完全没被修改,所以大小还是4-6MB

另外还有几个次要问题会影响压缩效果:

  • 压缩质量设为90,这个值偏高,JPEG质量90的话,1024尺寸的图片可能还是几百KB到1MB左右,要更小可以调低到60-70
  • BitmapFactory.decodeFile直接加载原图,会占用大量内存,应该用采样率优化加载逻辑
修正后的代码

方案1:直接修改DCIM里的原文件(推荐)

不需要复制文件到内部存储,直接通过Uri获取原文件的输出流进行覆盖:

fun takePhoto() {
    val FILENAME_FORMAT = "ddMMyyyy_HHmmss"

    val capturedContentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME,
            "CARAKA_"+SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()))
        put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM+"/TESTAPP")
        put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
    }

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

    imageCapture.takePicture(
        outputOptions,
        ContextCompat.getMainExecutor(context),
        object : ImageCapture.OnImageSavedCallback {
            override fun onError(exc: ImageCaptureException) {
                Toast.makeText(context,"Photo capture failed: ${exc.message}", Toast.LENGTH_SHORT).show()
            }

            override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                output.savedUri?.let { uri ->
                    try {
                        // 1. 计算采样率,避免加载大Bitmap导致OOM
                        val options = BitmapFactory.Options().apply {
                            inJustDecodeBounds = true
                            context.contentResolver.openInputStream(uri)?.use {
                                BitmapFactory.decodeStream(it, null, this)
                            }
                            inSampleSize = calculateInSampleSize(this, 1024, 1024)
                            inJustDecodeBounds = false
                        }

                        // 2. 加载缩小后的Bitmap
                        val capturedBitmap = context.contentResolver.openInputStream(uri)?.use {
                            BitmapFactory.decodeStream(it, null, options)
                        } ?: return

                        // 3. 调整尺寸到目标大小
                        val resizedBitmap = getResizedBitmap(capturedBitmap, 1024)

                        // 4. 压缩并写回原DCIM文件
                        context.contentResolver.openOutputStream(uri)?.use { fout ->
                            // 调低压缩质量,根据需求调整(60-70是比较均衡的取值)
                            resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 65, fout)
                            fout.flush()
                        }

                        // 5. 回收Bitmap释放内存
                        capturedBitmap.recycle()
                        resizedBitmap.recycle()

                        Toast.makeText(context,"Photo compressed successfully", Toast.LENGTH_SHORT).show()
                    } catch (e: IOException) {
                        e.printStackTrace()
                        Toast.makeText(context,"Compression failed: ${e.message}", Toast.LENGTH_SHORT).show()
                    }
                }
            }
        })
}

// 新增:计算Bitmap采样率的方法
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
    val height = options.outHeight
    val width = options.outWidth
    var inSampleSize = 1

    if (height > reqHeight || width > reqWidth) {
        val halfHeight = height / 2
        val halfWidth = width / 2
        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2
        }
    }
    return inSampleSize
}

// 保留原有的尺寸调整方法
fun getResizedBitmap(image: Bitmap, maxSize: Int): Bitmap {
    var width = image.width
    var height = image.height
    val bitmapRatio = width.toFloat() / height.toFloat()
    if (bitmapRatio > 1) {
        width = maxSize
        height = (width / bitmapRatio).toInt()
    } else {
        height = maxSize
        width = (height * bitmapRatio).toInt()
    }
    return Bitmap.createScaledBitmap(image, width, height, true)
}

方案2:如果必须使用文件路径操作(不推荐Android 10+)

如果你一定要通过File对象操作,需要正确获取DCIM目录下原文件的真实路径:

// 补充:Android 10+获取MediaStore文件真实路径(仅作参考,优先用Uri方案)
fun getRealPathFromUri(context: Context, uri: Uri): String? {
    val projection = arrayOf(MediaStore.Images.Media.DATA)
    val cursor = context.contentResolver.query(uri, projection, null, null, null)
    cursor?.let {
        val columnIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
        it.moveToFirst()
        val path = it.getString(columnIndex)
        it.close()
        return path
    }
    return null
}
额外优化建议
  • 压缩质量:JPEG质量值范围0-100,值越小文件越小但画质越差,建议测试60-70的效果,平衡大小和画质
  • 后台处理:图片压缩是耗时操作,建议放到CoroutineScope或WorkManager中执行,避免阻塞主线程
  • 权限:确保已经申请WRITE_EXTERNAL_STORAGE权限(Android 10+用MediaStore不需要,但Android 9及以下需要)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 07:35:36