CameraX is Jetpack's camera API that abstracts camera2's complexity into lifecycle-aware use cases. You compose use cases — Preview, ImageCapture, ImageAnalysis, VideoCapture — and bind them to a lifecycle.
Core Concepts
- Use case: a unit of camera work (Preview, ImageCapture, ImageAnalysis, VideoCapture)
- CameraSelector: chooses which physical camera (LENS_FACING_BACK, LENS_FACING_FRONT)
- ProcessCameraProvider: manages use case lifecycle and camera resource
bindToLifecycle(): ties camera to a LifecycleOwner — camera opens/closes automatically
Setup
// implementation("androidx.camera:camera-core:1.3.2")
// implementation("androidx.camera:camera-camera2:1.3.2")
// implementation("androidx.camera:camera-lifecycle:1.3.2")
// implementation("androidx.camera:camera-view:1.3.2")
// implementation("androidx.camera:camera-video:1.3.2")
// implementation("androidx.camera:camera-mlkit-vision:1.3.2")
Preview Use Case
class CameraFragment : Fragment() {
private val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
private lateinit var camera: Camera
fun startCamera(previewView: PreviewView) {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.build()
.also { it.setSurfaceProvider(previewView.surfaceProvider) }
cameraProvider.unbindAll()
camera = cameraProvider.bindToLifecycle(
viewLifecycleOwner, // ← lifecycle-aware: camera closes when fragment stops
cameraSelector,
preview
)
}, ContextCompat.getMainExecutor(requireContext()))
}
}
ImageCapture Use Case
val imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) // or MAXIMIZE_QUALITY
.setTargetRotation(Surface.ROTATION_0)
.build()
// Bind alongside preview
cameraProvider.bindToLifecycle(viewLifecycleOwner, cameraSelector, preview, imageCapture)
// Take photo
fun takePhoto(outputDir: File) {
val photoFile = File(outputDir, "${System.currentTimeMillis()}.jpg")
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(requireContext()),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
onPhotoCaptured(savedUri)
}
override fun onError(exception: ImageCaptureException) {
Timber.e(exception, "Photo capture failed")
}
}
)
}
ImageAnalysis Use Case (ML / QR Scanning)
val imageAnalysis = ImageAnalysis.Builder()
.setTargetResolution(Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) // drop frames if analyzer is slow
.build()
imageAnalysis.setAnalyzer(
Executors.newSingleThreadExecutor(),
QrCodeAnalyzer { qrCode ->
// Called on executor thread — post to main for UI
mainHandler.post { onQrCodeDetected(qrCode) }
}
)
class QrCodeAnalyzer(private val onDetected: (String) -> Unit) : ImageAnalysis.Analyzer {
private val scanner = BarcodeScanning.getClient()
@androidx.camera.core.ExperimentalGetImage
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image ?: return imageProxy.close()
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
scanner.process(image)
.addOnSuccessListener { barcodes ->
barcodes.firstOrNull()?.rawValue?.let(onDetected)
}
.addOnCompleteListener { imageProxy.close() } // always close!
}
}
VideoCapture Use Case
val recorder = Recorder.Builder()
.setQualitySelector(QualitySelector.from(Quality.HIGHEST))
.build()
val videoCapture = VideoCapture.withOutput(recorder)
// Bind
cameraProvider.bindToLifecycle(viewLifecycleOwner, cameraSelector, preview, videoCapture)
// Record
lateinit var recording: Recording
fun startRecording(outputFile: File) {
val outputOptions = FileOutputOptions.Builder(outputFile).build()
recording = videoCapture.output
.prepareRecording(requireContext(), outputOptions)
.withAudioEnabled() // requires RECORD_AUDIO permission
.start(ContextCompat.getMainExecutor(requireContext())) { event ->
when (event) {
is VideoRecordEvent.Finalize -> onRecordingFinalized(event)
is VideoRecordEvent.Status -> updateRecordingDuration(event.recordingStats.recordedDurationNanos)
}
}
}
fun stopRecording() = recording.stop()
Use Case Limits
Not all combinations are supported simultaneously on all devices:
| Combination | Support |
|---|---|
| Preview + ImageCapture | Guaranteed |
| Preview + ImageAnalysis | Guaranteed |
| Preview + VideoCapture | Most devices |
| Preview + ImageCapture + ImageAnalysis | Limited — check isUseCaseCombinationSupported() |
Key Takeaways
| Use case | When to use |
|---|---|
Preview | Show live viewfinder |
ImageCapture | Take still photos; low-latency or high-quality mode |
ImageAnalysis | Real-time ML, QR scanning; use STRATEGY_KEEP_ONLY_LATEST |
VideoCapture | Record video; always close Recording when done |
bindToLifecycle | Always bind use cases here; camera opens/closes automatically |