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

