3. Camera2拍照
任务说明
实现一个基于 Camera2 API 的拍照功能:点击按钮后捕获单帧静态图片,将 JPEG 数据保存到应用私有目录(context.filesDir),并在界面上显示该图片的缩略图。本案例聚焦于拍照链路的最小完整实现,不涉及视频录制、前后摄切换、变焦等扩展功能。
实现方案
使用 CameraCaptureSession.CaptureCallback 的 onCaptureCompleted 回调获取已捕获的 ImageReader 帧,从中读取 JPEG 字节流写入文件。缩略图通过 BitmapFactory 解码文件后缩放显示在 ImageView 中。核心流程:打开相机 → 创建预览会话 → 触发 capture() → 在回调中保存图片 → 更新 UI。
代码实现
// Camera2PhotoCapture.kt
class Camera2PhotoCapture(private val context: Context) {
private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
private var cameraDevice: CameraDevice? = null
private var captureSession: CameraCaptureSession? = null
private var imageReader: ImageReader? = null
private var previewSurface: Surface? = null
private var cameraId = "0"
// 保存回调
var onPhotoSaved: ((File) -> Unit)? = null
// 打开相机
fun openCamera() {
if (checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) return
cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
override fun onOpened(camera: CameraDevice) {
cameraDevice = camera
createSession()
}
override fun onDisconnected(camera: CameraDevice) { camera.close() }
override fun onError(camera: CameraDevice, error: Int) { camera.close() }
}, null)
}
// 创建捕获会话(预览 + 拍照)
private fun createSession() {
val device = cameraDevice ?: return
val size = cameraManager.getCameraCharacteristics(cameraId)
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
.getOutputSizes(ImageFormat.JPEG)[0]
imageReader = ImageReader.newInstance(size.width, size.height, ImageFormat.JPEG, 2)
imageReader?.setOnImageAvailableListener({ reader ->
val image = reader.acquireLatestImage() ?: return@setOnImageAvailableListener
val buffer = image.planes[0].buffer
val bytes = ByteArray(buffer.remaining())
buffer.get(bytes)
image.close()
// 保存到私有目录
val file = File(context.filesDir, "photo_${System.currentTimeMillis()}.jpg")
FileOutputStream(file).use { it.write(bytes) }
onPhotoSaved?.invoke(file)
}, null)
previewSurface = Surface(imageReader!!.surface)
device.createCaptureSession(listOf(previewSurface!!), object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
captureSession = session
// 设置预览请求(此处仅用于维持会话,不实际显示预览)
val request = device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
addTarget(previewSurface!!)
}
session.setRepeatingRequest(request.build(), null, null)
}
override fun onConfigureFailed(session: CameraCaptureSession) { /* 可在此提示用户 */ }
}, null)
}
// 触发拍照
fun takePhoto() {
val device = cameraDevice ?: return
val session = captureSession ?: return
val request = device.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE).apply {
addTarget(previewSurface!!)
}
session.capture(request.build(), object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
// JPEG 数据已由 ImageReader 回调处理
}
}, null)
}
fun close() {
captureSession?.close()
cameraDevice?.close()
imageReader?.close()
}
}
界面与调用
// MainActivity.kt(关键部分)
class MainActivity : AppCompatActivity() {
private lateinit var photoCapture: Camera2PhotoCapture
private lateinit var imageView: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
imageView = findViewById(R.id.thumbnail)
photoCapture = Camera2PhotoCapture(this)
findViewById<Button>(R.id.btn_capture).setOnClickListener {
photoCapture.takePhoto()
}
photoCapture.onPhotoSaved = { file ->
runOnUiThread {
val bmp = BitmapFactory.decodeFile(file.absolutePath)
val scaled = Bitmap.createScaledBitmap(bmp, 200, 200, true)
imageView.setImageBitmap(scaled)
}
}
}
override fun onResume() {
super.onResume()
if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
photoCapture.openCamera()
} else {
requestPermissions(arrayOf(Manifest.permission.CAMERA), 1)
}
}
override fun onDestroy() {
super.onDestroy()
photoCapture.close()
}
}
布局文件
<!-- activity_main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<ImageView
android:id="@+id/thumbnail"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#EEE"
android:contentDescription="缩略图"/>
<Button
android:id="@+id/btn_capture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拍照"
android:layout_marginTop="20dp"/>
</LinearLayout>
依赖与权限
<!-- AndroidManifest.xml 中添加 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
// build.gradle (Module) 中无需额外依赖,Camera2 为系统 API
验证方法
- 在真机上运行应用(Camera2 不支持模拟器预览)。
- 授予相机权限后,点击“拍照”按钮。
- 观察 ImageView 是否出现 200×200 的缩略图。
- 通过
adb shell run-as com.your.package ls files/查看私有目录下是否生成photo_*.jpg文件。 - 判定标准:缩略图正常显示且文件存在,文件大小大于 10KB(JPEG 有效数据),即视为拍照链路成功。
预期输出:点击拍照后,files/ 目录下新增 JPEG 文件,界面显示对应缩略图。若相机权限未授予或设备无相机,则无输出且应用不崩溃。