--- id: android-camerax-patterns title: Android CameraX — 카메라 / 이미지 분석 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [android, camera, camerax, vibe-coding] tech_stack: { language: "Kotlin / CameraX 1.4+", applicable_to: ["Android"] } applied_in: [] aliases: [Camera2, ImageCapture, ImageAnalysis, Preview, lifecycle camera] --- # Android CameraX > Camera2 의 lifecycle-aware wrapper. **Preview / ImageCapture / ImageAnalysis / VideoCapture** 4종 use case. 디바이스 호환성 자동 처리. ## 📖 핵심 개념 - ProcessCameraProvider: 부팅. - UseCase: Preview / ImageCapture / ImageAnalysis / VideoCapture. - bindToLifecycle: lifecycle 에 묶어 자동 시작/정지. ## 💻 코드 패턴 ### 의존성 ```kotlin implementation("androidx.camera:camera-core:1.4.0") implementation("androidx.camera:camera-camera2:1.4.0") implementation("androidx.camera:camera-lifecycle:1.4.0") implementation("androidx.camera:camera-view:1.4.0") ``` ### Preview + Capture (Compose) ```kotlin @Composable fun CameraScreen() { val ctx = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current val previewView = remember { PreviewView(ctx) } var imageCapture by remember { mutableStateOf(null) } LaunchedEffect(Unit) { val provider = ProcessCameraProvider.getInstance(ctx).get() val preview = Preview.Builder().build().also { it.setSurfaceProvider(previewView.surfaceProvider) } val capture = ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build() imageCapture = capture try { provider.unbindAll() provider.bindToLifecycle( lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, preview, capture ) } catch (e: Exception) { log.error("camera bind failed", e) } } Box { AndroidView(factory = { previewView }) Button(onClick = { imageCapture?.takePictureToDisk(ctx) }) { Text("촬영") } } } fun ImageCapture.takePictureToDisk(ctx: Context) { val file = File(ctx.cacheDir, "shot_${System.currentTimeMillis()}.jpg") val output = ImageCapture.OutputFileOptions.Builder(file).build() takePicture(output, ContextCompat.getMainExecutor(ctx), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(o: ImageCapture.OutputFileResults) { /* file ready */ } override fun onError(e: ImageCaptureException) { log.error("capture failed", e) } }) } ``` ### ImageAnalysis — ML / barcode ```kotlin val analyzer = ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() .also { it.setAnalyzer(Executors.newSingleThreadExecutor()) { imageProxy -> val mediaImage = imageProxy.image ?: return@setAnalyzer val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees) barcodeScanner.process(image) .addOnSuccessListener { codes -> codes.forEach { onScan(it.rawValue) } } .addOnCompleteListener { imageProxy.close() } // 반드시 close } } provider.bindToLifecycle(lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, preview, analyzer) ``` ### Permission ```kotlin val permission = rememberPermissionState(Manifest.permission.CAMERA) LaunchedEffect(Unit) { permission.launchPermissionRequest() } if (!permission.status.isGranted) return // PermissionRationale UI ``` ### Video capture ```kotlin val recorder = Recorder.Builder() .setQualitySelector(QualitySelector.from(Quality.HD)) .build() val videoCapture = VideoCapture.withOutput(recorder) provider.bindToLifecycle(lifecycleOwner, selector, preview, videoCapture) val output = MediaStoreOutputOptions.Builder(...).build() recording = videoCapture.output.prepareRecording(ctx, output) .start(ContextCompat.getMainExecutor(ctx)) { event -> ... } // 종료 recording?.stop() ``` ## 🤔 의사결정 기준 | 사용 | UseCase 조합 | |---|---| | 단순 사진 | Preview + ImageCapture | | QR / barcode 스캔 | Preview + ImageAnalysis | | ML 분석 (얼굴, OCR) | Preview + ImageAnalysis | | 비디오 녹화 | Preview + VideoCapture | | 사진 + 비디오 동시 | Preview + ImageCapture + VideoCapture (제한 있음) | ## ❌ 안티패턴 - **imageProxy.close() 누락**: backpressure → analyzer 멈춤. - **lifecycle 안 묶음**: 백그라운드에서 카메라 점유 → 배터리. - **메인 스레드에서 ML 처리**: UI 멈춤. 별도 executor. - **여러 use case 가 디바이스 한계 초과**: bind 실패. 한 번에 적게. - **Preview rotation 안 처리**: 회전 시 이상. - **권한 거부 후 silent**: 가이드 UI. ## 🤖 LLM 활용 힌트 - CameraX + lifecycle bind + 단일 executor analyzer 패턴 표준. - ML Kit 결합 자주. ## 🔗 관련 문서 - [[Android_Lifecycle_Aware_Components]] - [[iOS_Background_Tasks]]