Android Camera2如何将捕获照片保存为JPG至DCIM目录?
实现相机捕获图像保存到DCIM目录的方案
我来帮你搞定把拍摄的图片保存到/sdcard/DCIM/目录的需求!咱们一步步修改现有代码,核心是通过ImageReader获取图像数据,再写入到DCIM目录的JPG文件:
1. 确保ImageReader配置正确
首先,你的ImageReader需要初始化为JPEG格式,这样才能直接获取到可保存的JPG字节数据。在初始化相机的环节添加这段代码(比如在打开相机前):
private var mImageReader: ImageReader? = null // 初始化ImageReader,替换成你相机支持的合适尺寸 fun initImageReader(cameraSize: Size) { // 参数:宽、高、图像格式、最大图像缓存数 mImageReader = ImageReader.newInstance( cameraSize.width, cameraSize.height, ImageFormat.JPEG, 1 ) // 设置图像可用监听器,捕获到图像后会回调这里 mImageReader?.setOnImageAvailableListener(mOnImageAvailableListener, null) }
2. 实现图像数据的处理与保存
添加ImageReader.OnImageAvailableListener的实现,这里是获取图像并写入文件的核心逻辑:
private val mOnImageAvailableListener = ImageReader.OnImageAvailableListener { reader -> // 获取最新捕获的图像 val image = reader.acquireLatestImage() ?: return@OnImageAvailableListener // 从Image中提取JPEG字节数据 val plane = image.planes[0] val buffer = plane.buffer val imageBytes = ByteArray(buffer.remaining()) buffer.get(imageBytes) // 保存到DCIM目录 saveImageToDCIM(imageBytes) // 务必关闭Image,避免内存泄漏! image.close() } // 保存图片到DCIM目录的工具函数 private fun saveImageToDCIM(imageBytes: ByteArray) { // 获取系统DCIM目录 val dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) // 用时间戳命名,避免文件名重复 val fileName = "IMG_${System.currentTimeMillis()}.jpg" val targetFile = File(dcimDir, fileName) try { // 写入文件 FileOutputStream(targetFile).use { outputStream -> outputStream.write(imageBytes) outputStream.flush() } // 通知系统媒体库更新,这样图库能立刻看到新图片 MediaScannerConnection.scanFile( applicationContext, arrayOf(targetFile.absolutePath), arrayOf("image/jpeg"), null ) Toast.makeText(applicationContext, "图片已保存到DCIM", Toast.LENGTH_SHORT).show() } catch (e: IOException) { e.printStackTrace() Toast.makeText(applicationContext, "保存失败:${e.message}", Toast.LENGTH_SHORT).show() } }
3. 权限配置不能忘
- 静态权限声明:在
AndroidManifest.xml里添加必要权限:
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <!-- Android 10+ 可以用MediaStore替代,或者申请MANAGE_EXTERNAL_STORAGE权限(不推荐) -->
- 动态权限申请:Android 6.0(API 23)及以上,需要在代码中动态申请
CAMERA和WRITE_EXTERNAL_STORAGE权限,确保用户授权后再打开相机。
4. 检查原有代码的适配
你原来的takePicture和mSessionCallback逻辑基本没问题,只要确保ImageReader的surface被正确添加到捕获请求中(你已经做到了)。另外,mCaptureCallback的onCaptureCompleted可以保持原样,用来关闭会话。
额外注意(Android 10+ 适配)
如果你的应用目标SDK是29及以上,由于Scoped Storage限制,直接写入DCIM目录需要特殊处理,更合规的方式是用MediaStore插入图片:
// Android 10+ 用MediaStore保存的示例 private fun saveImageToDCIMForAndroidQ(imageBytes: ByteArray) { val contentValues = ContentValues().apply { put(MediaStore.Images.Media.DISPLAY_NAME, "IMG_${System.currentTimeMillis()}.jpg") put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM) } val resolver = applicationContext.contentResolver val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) uri?.let { resolver.openOutputStream(it)?.use { outputStream -> outputStream.write(imageBytes) outputStream.flush() } Toast.makeText(applicationContext, "图片已保存到DCIM", Toast.LENGTH_SHORT).show() } ?: run { Toast.makeText(applicationContext, "保存失败", Toast.LENGTH_SHORT).show() } }
这样修改后,点击拍摄按钮捕获的图像就会自动保存到DCIM目录啦!
内容的提问来源于stack exchange,提问作者hyunwookcho




