Android CameraX 相機功能
早期 Android 的相機開發(Camera1 與 Camera2 API)因硬體相容性碎片化而極度難用。CameraX 是 Jetpack 的一部分,旨在解決這些痛點。它不僅易用,還自動處理了各種裝置的生命週期綁定與比例設定問題。
為什麼選擇 CameraX?
- API 簡潔:將功能封裝為「使用案例 (UseCases)」,程式碼量大幅減少。
- 相容性檢查:自動處理超過 98% Android 裝置的特殊行為(如預覽旋轉角度)。
- 生命週期感知:自動根據
LifecycleOwner(如 Activity/Fragment)啟動或停止相機,防止記憶體洩漏。
環境配置
在 build.gradle.kts 中加入 CameraX 核心組件:
val cameraxVersion = "1.3.4"
dependencies {
implementation("androidx.camera:camera-core:$cameraxVersion")
implementation("androidx.camera:camera-camera2:$cameraxVersion")
implementation("androidx.camera:camera-lifecycle:$cameraxVersion")
implementation("androidx.camera:camera-view:$cameraxVersion")
implementation("androidx.camera:camera-video:$cameraxVersion")
}
確保 AndroidManifest.xml 加入權限與關鍵硬體宣告:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.any" />
核心使用案例 (UseCases)
CameraX 的核心思想是將相機功能拆分為不同的 UseCase,你可以同時綁定一個或多個。
預覽 (Preview)
在 Compose 中,我們需要將傳統的 PreviewView 置入 AndroidView 中。
@Composable
fun CameraPreviewScreen() {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val previewView = remember { PreviewView(context) }
LaunchedEffect(Unit) {
val cameraProvider = ProcessCameraProvider.getInstance(context).get()
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)
} catch (e: Exception) {
Log.e("CameraX", "綁定失敗", e)
}
}
AndroidView({ previewView }, modifier = Modifier.fillMaxSize())
}
拍照 (ImageCapture)
ImageCapture 負責處理高畫質靜態影像的獲取。
val imageCapture = remember { ImageCapture.Builder().build() }
// 在呼叫 bindToLifecycle 時加入它
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageCapture)
// 執行拍照並存到媒體庫
fun takePhoto(context: Context) {
val name = "IMG_${System.currentTimeMillis()}.jpg"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, name)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
}
val outputOptions = ImageCapture.OutputFileOptions
.Builder(context.contentResolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
.build()
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(context),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
// 拍照成功!
}
override fun onError(exc: ImageCaptureException) {
// 拍照失敗
}
}
)
}
影像分析 (ImageAnalysis)
用於即時取得相機畫面 Buffer 進行分析,如 QR Code 辨識或人臉檢測。
val analysisUseCase = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
analysisUseCase.setAnalyzer(ContextCompat.getMainExecutor(context)) { imageProxy ->
// imageProxy.image 提供原始 Buffer
// 處理完成後務必釋放,否則會阻塞下一格畫面
imageProxy.close()
}
相機控制 (Camera Control)
一旦相機綁定成功,你可以透過 Camera 實例來控制硬體行為。
val camera = cameraProvider.bindToLifecycle(...)
val cameraControl = camera.cameraControl
val cameraInfo = camera.cameraInfo
// 1. 控制縮放 (Zoom)
cameraControl.setZoomRatio(2.0f)
// 2. 開啟閃光燈 (Torch)
cameraControl.enableTorch(true)
// 3. 點擊對焦 (Tap to Focus)
fun focusOnPoint(x: Float, y: Float) {
val factory = previewView.meteringPointFactory
val point = factory.createPoint(x, y)
val action = FocusMeteringAction.Builder(point).build()
cameraControl.startFocusAndMetering(action)
}
總結
CameraX 透過 UseCase 模型簡化了各種相機工作流。建議開發者總是結合 Coroutines 處理圖片保存與分析結果,並確保遵循最佳實踐:在 bindToLifecycle 之前先呼叫 unbindAll()。