Android CameraX en 2026 : Capture Photo et Vidéo avec Questions d'Entretien

Guide complet CameraX 1.6 pour Android : architecture lifecycle-aware, capture photo, enregistrement vidéo, intégration Jetpack Compose et questions d'entretien technique.

Android CameraX en 2026 : Capture Photo et Vidéo avec Questions d'Entretien

CameraX simplifie le développement caméra Android en abstrayant la complexité matérielle derrière une API lifecycle-aware. Avec la version 1.6.2 désormais stable et la migration vers CameraPipe achevée, CameraX offre la même pile caméra haute performance qui équipe l'application appareil photo Pixel.

Changements Clés CameraX 1.6

CameraX 1.6 a migré vers CameraPipe, utilise Media3 Muxer par défaut pour VideoCapture et a stabilisé l'API SessionConfig. Les builders DSL Kotlin (preview { }, imageCapture { }, videoCapture { }) réduisent le boilerplate de 40%.

Architecture CameraX et Modèle de Cas d'Usage

CameraX fournit quatre cas d'usage principaux qui peuvent s'exécuter simultanément : Preview, ImageCapture, ImageAnalysis et VideoCapture. Chaque cas d'usage se lie à un LifecycleOwner, de sorte que la caméra démarre et s'arrête automatiquement avec l'activité ou le fragment.

Le CameraProvider agit comme une factory qui instancie ces cas d'usage et les lie à la caméra. Ce découplage signifie que le même code fonctionne sur des appareils de différents fabricants sans se soucier des particularités spécifiques à chaque constructeur.

CameraSetup.ktkotlin
class CameraSetup(
    private val context: Context,
    private val lifecycleOwner: LifecycleOwner
) {
    private lateinit var cameraProvider: ProcessCameraProvider
    private var imageCapture: ImageCapture? = null
    private var videoCapture: VideoCapture<Recorder>? = null
    
    suspend fun initialize() {
        // ProcessCameraProvider lie les cas d'usage au lifecycle
        cameraProvider = ProcessCameraProvider.getInstance(context).await()
    }
    
    fun bindUseCases(previewView: PreviewView) {
        // Délier les cas d'usage existants avant de relier
        cameraProvider.unbindAll()
        
        // Cas d'usage Preview affiche le flux caméra
        val preview = Preview.Builder()
            .build()
            .apply { setSurfaceProvider(previewView.surfaceProvider) }
        
        // ImageCapture pour les photos
        imageCapture = ImageCapture.Builder()
            .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
            .build()
        
        // VideoCapture avec Recorder
        val recorder = Recorder.Builder()
            .setQualitySelector(QualitySelector.from(Quality.FHD))
            .build()
        videoCapture = VideoCapture.withOutput(recorder)
        
        // Lier tous les cas d'usage au lifecycle
        cameraProvider.bindToLifecycle(
            lifecycleOwner,
            CameraSelector.DEFAULT_BACK_CAMERA,
            preview,
            imageCapture,
            videoCapture
        )
    }
}

L'appel bindToLifecycle connecte les cas d'usage à la fois au matériel caméra et au lifecycle. Lorsque le LifecycleOwner entre dans l'état STARTED, la caméra s'ouvre. Lorsqu'il entre dans l'état STOPPED, la caméra se ferme automatiquement.

Implémentation de la Capture Photo avec ImageCapture

ImageCapture propose deux modes de capture : CAPTURE_MODE_MINIMIZE_LATENCY pour une réponse rapide du déclencheur, et CAPTURE_MODE_MAXIMIZE_QUALITY pour les prises de vue nécessitant un traitement intensif. Le choix dépend de la priorité de l'application : vitesse (applications sociales) ou qualité (scanners de documents).

PhotoCaptureManager.ktkotlin
class PhotoCaptureManager(
    private val context: Context,
    private val imageCapture: ImageCapture
) {
    private val mainExecutor = ContextCompat.getMainExecutor(context)
    
    fun capturePhoto(onResult: (Uri?) -> Unit) {
        // Générer un nom de fichier unique avec horodatage
        val filename = "IMG_${System.currentTimeMillis()}.jpg"
        
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
            // Stocker dans Pictures/CameraX sur Android 10+
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX")
            }
        }
        
        val outputOptions = ImageCapture.OutputFileOptions.Builder(
            context.contentResolver,
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            contentValues
        ).build()
        
        imageCapture.takePicture(
            outputOptions,
            mainExecutor,
            object : ImageCapture.OnImageSavedCallback {
                override fun onImageSaved(results: ImageCapture.OutputFileResults) {
                    // results.savedUri contient l'URI MediaStore
                    onResult(results.savedUri)
                }
                
                override fun onError(exception: ImageCaptureException) {
                    Log.e("PhotoCapture", "Échec de capture : ${exception.message}")
                    onResult(null)
                }
            }
        )
    }
}

CameraX 1.5 a ajouté le support de capture DNG (RAW) pour les applications nécessitant des données brutes du capteur. Il convient de vérifier les capacités de l'appareil avec ImageCapture.getImageCaptureCapabilities() avant d'activer la sortie RAW.

Enregistrement Vidéo avec VideoCapture et Recorder

VideoCapture utilise un objet Recorder pour gérer l'encodage et le muxing. L'API Recorder suit un pattern prepare-start-stop qui gère automatiquement la synchronisation audio.

VideoRecordingManager.ktkotlin
class VideoRecordingManager(
    private val context: Context,
    private val videoCapture: VideoCapture<Recorder>
) {
    private var activeRecording: Recording? = null
    
    fun startRecording(onEvent: (VideoRecordEvent) -> Unit): Boolean {
        // Vérifier si un enregistrement est déjà en cours
        if (activeRecording != null) return false
        
        val filename = "VID_${System.currentTimeMillis()}.mp4"
        
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
            put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX")
            }
        }
        
        val mediaStoreOutput = MediaStoreOutputOptions.Builder(
            context.contentResolver,
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI
        )
            .setContentValues(contentValues)
            .build()
        
        // Préparer l'enregistrement avec audio si permission accordée
        val pendingRecording = videoCapture.output
            .prepareRecording(context, mediaStoreOutput)
        
        if (hasAudioPermission()) {
            pendingRecording.withAudioEnabled()
        }
        
        // Démarrer l'enregistrement et stocker la référence
        activeRecording = pendingRecording.start(
            ContextCompat.getMainExecutor(context)
        ) { event ->
            onEvent(event)
            
            // Effacer la référence quand l'enregistrement termine
            if (event is VideoRecordEvent.Finalize) {
                activeRecording = null
            }
        }
        
        return true
    }
    
    fun stopRecording() {
        activeRecording?.stop()
    }
    
    fun pauseRecording() {
        activeRecording?.pause()
    }
    
    fun resumeRecording() {
        activeRecording?.resume()
    }
    
    private fun hasAudioPermission(): Boolean {
        return ContextCompat.checkSelfPermission(
            context,
            Manifest.permission.RECORD_AUDIO
        ) == PackageManager.PERMISSION_GRANTED
    }
}

CameraX 1.5 a ajouté le support vidéo au ralenti. Les capacités peuvent être interrogées avec Recorder.getHighSpeedVideoCapabilities() avant d'activer l'enregistrement à 120fps ou 240fps.

Prêt à réussir tes entretiens Android ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

CameraX avec Jetpack Compose

CameraX 1.5 a introduit l'artifact camera-compose avec un composable CameraXViewfinder. Pour les applications utilisant encore PreviewView basé sur View, AndroidView fournit l'interopérabilité.

CameraPreviewComposable.ktkotlin
@Composable
fun CameraPreview(
    modifier: Modifier = Modifier,
    onPreviewViewCreated: (PreviewView) -> Unit
) {
    AndroidView(
        modifier = modifier.fillMaxSize(),
        factory = { context ->
            PreviewView(context).apply {
                implementationMode = PreviewView.ImplementationMode.COMPATIBLE
                scaleType = PreviewView.ScaleType.FILL_CENTER
                onPreviewViewCreated(this)
            }
        }
    )
}

@Composable
fun CameraScreen(viewModel: CameraViewModel = viewModel()) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    
    LaunchedEffect(Unit) {
        viewModel.initializeCamera(context, lifecycleOwner)
    }
    
    Box(modifier = Modifier.fillMaxSize()) {
        CameraPreview(
            onPreviewViewCreated = { previewView ->
                viewModel.bindPreview(previewView)
            }
        )
        
        // Boutons de capture en overlay
        Row(
            modifier = Modifier
                .align(Alignment.BottomCenter)
                .padding(32.dp),
            horizontalArrangement = Arrangement.spacedBy(24.dp)
        ) {
            IconButton(
                onClick = { viewModel.capturePhoto() }
            ) {
                Icon(Icons.Default.Camera, "Prendre une photo")
            }
            
            IconButton(
                onClick = { viewModel.toggleRecording() }
            ) {
                Icon(
                    if (viewModel.isRecording) Icons.Default.Stop
                    else Icons.Default.Videocam,
                    "Enregistrer une vidéo"
                )
            }
        }
    }
}

Pour les implémentations Compose-first, le guide sur les questions d'entretien Jetpack Compose couvre les patterns d'intégration ViewModel qui s'appliquent directement à la gestion d'état de la caméra.

Gestion des Permissions Caméra

Android requiert la permission CAMERA au runtime. RECORD_AUDIO n'est nécessaire que lors de l'enregistrement vidéo avec son. Le flux de permission doit expliquer pourquoi l'accès est nécessaire avant de le demander.

PermissionHandler.ktkotlin
class CameraPermissionHandler(private val activity: ComponentActivity) {
    private val requiredPermissions = arrayOf(
        Manifest.permission.CAMERA,
        Manifest.permission.RECORD_AUDIO
    )
    
    private val permissionLauncher = activity.registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        val cameraGranted = permissions[Manifest.permission.CAMERA] == true
        val audioGranted = permissions[Manifest.permission.RECORD_AUDIO] == true
        
        if (cameraGranted) {
            onCameraPermissionGranted(audioGranted)
        } else {
            onCameraPermissionDenied()
        }
    }
    
    fun checkAndRequestPermissions() {
        when {
            hasAllPermissions() -> onCameraPermissionGranted(hasAudioPermission())
            shouldShowRationale() -> showPermissionRationale()
            else -> permissionLauncher.launch(requiredPermissions)
        }
    }
    
    private fun hasAllPermissions(): Boolean {
        return requiredPermissions.all { permission ->
            ContextCompat.checkSelfPermission(
                activity, permission
            ) == PackageManager.PERMISSION_GRANTED
        }
    }
    
    private fun hasAudioPermission(): Boolean {
        return ContextCompat.checkSelfPermission(
            activity,
            Manifest.permission.RECORD_AUDIO
        ) == PackageManager.PERMISSION_GRANTED
    }
    
    private fun shouldShowRationale(): Boolean {
        return requiredPermissions.any { permission ->
            ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)
        }
    }
}

Questions d'Entretien CameraX

CameraX apparaît dans les entretiens Android lors de discussions sur l'intégration caméra, la gestion du lifecycle ou l'abstraction matérielle. Ces questions testent la compréhension de l'architecture et les détails d'implémentation pratiques.

Pourquoi utiliser CameraX plutôt que Camera2 ?

CameraX abstrait les particularités spécifiques aux appareils que Camera2 expose directement. Une implémentation Camera2 nécessite de gérer des centaines de contournements spécifiques aux appareils. CameraX intègre ces corrections dans la bibliothèque, testées contre le CameraX Test Lab qui couvre plus de 150 modèles d'appareils.

La liaison lifecycle-aware élimine la gestion manuelle des ressources. Camera2 nécessite des appels explicites open/close qui provoquent des fuites mémoire s'ils ne sont pas gérés correctement dans chaque callback de lifecycle.

Comment CameraProvider lie-t-il les cas d'usage au lifecycle ?

CameraProvider utilise le pattern LifecycleObserver en interne. Lors de l'appel à bindToLifecycle(), il enregistre un observateur sur le LifecycleOwner fourni. Cet observateur reçoit les événements ON_START et ON_STOP pour ouvrir et fermer la caméra.

Le point clé : CameraX ne démarre pas la caméra immédiatement au binding. Il attend que le lifecycle atteigne l'état STARTED, ce qui correspond au timing de Activity.onStart().

Que se passe-t-il lors de la liaison de cas d'usage incompatibles ?

Toutes les combinaisons de cas d'usage ne fonctionnent pas sur tous les appareils. Preview + VideoCapture + ImageAnalysis + ImageCapture peut échouer sur du matériel ancien. CameraX lance une IllegalArgumentException au moment du binding, pas au runtime.

La solution consiste à vérifier CameraProvider.hasCamera() et les capacités de l'appareil avant de lier des combinaisons complexes. Pour les applications critiques, tester explicitement sur des appareils d'entrée de gamme.

Expliquer la différence entre CAPTURE_MODE_MINIMIZE_LATENCY et CAPTURE_MODE_MAXIMIZE_QUALITY

MINIMIZE_LATENCY réduit le délai du déclencheur en utilisant des pipelines de traitement plus rapides, en sautant possiblement certains post-traitements. MAXIMIZE_QUALITY applique le traitement complet HDR+, la réduction de bruit et la capture multi-frames sur les appareils supportés.

Le compromis : le mode latence capture en 100-200ms, le mode qualité peut prendre 500ms-1s selon l'éclairage. Les applications sociales choisissent typiquement la latence. Les applications de scan de documents choisissent la qualité.

Comment CameraX gère-t-il la rotation de l'appareil ?

CameraX gère automatiquement la rotation via le paramètre target rotation. Par défaut, il utilise la rotation du Display, mais les applications peuvent surcharger avec setTargetRotation(). Les données EXIF de l'image de sortie contiennent le tag d'orientation correct.

Pour PreviewView, le scaleType gère les différences de ratio d'aspect. FILL_CENTER recadre pour remplir, FIT_CENTER affiche des bandes noires. Le choix dépend de si l'application privilégie l'aperçu plein écran ou évite le recadrage.

Dépendances et Configuration pour CameraX 1.6

La bibliothèque CameraX est divisée en plusieurs artifacts. Inclure uniquement ce que l'application utilise pour minimiser la taille de l'APK.

build.gradle.kts (Module: app)kotlin
dependencies {
    val cameraxVersion = "1.6.2"
    
    // Bibliothèque core requise pour tous les cas d'usage
    implementation("androidx.camera:camera-core:$cameraxVersion")
    
    // Implémentation Camera2 (requise)
    implementation("androidx.camera:camera-camera2:$cameraxVersion")
    
    // Intégration Lifecycle
    implementation("androidx.camera:camera-lifecycle:$cameraxVersion")
    
    // Cas d'usage VideoCapture
    implementation("androidx.camera:camera-video:$cameraxVersion")
    
    // PreviewView et composants UI caméra
    implementation("androidx.camera:camera-view:$cameraxVersion")
    
    // Optionnel: Extensions (Mode nuit, HDR, etc.)
    implementation("androidx.camera:camera-extensions:$cameraxVersion")
    
    // Optionnel: Effets temps réel
    implementation("androidx.camera:camera-effects:$cameraxVersion")
}

android {
    // CameraX nécessite le bytecode Java 11
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
}

Le Manifest nécessite les déclarations de fonctionnalités et les permissions :

xml
<!-- AndroidManifest.xml -->
<manifest>
    <!-- Exigence matérielle caméra -->
    <uses-feature android:name="android.hardware.camera.any" />
    
    <!-- Permissions runtime -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>

Les patterns associés pour Kotlin Coroutines s'appliquent lors de l'encapsulation des appels ListenableFuture de CameraX dans des fonctions suspend.

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Ce que CameraX 1.6 Change pour les Applications en Production

CameraX 1.6 a apporté trois changements qui affectent les implémentations existantes :

  1. Backend CameraPipe : La pile caméra interne utilise maintenant CameraPipe, la même architecture que la caméra Pixel. Cela améliore les performances sur les appareils Pixel et fournit une fondation plus propre pour les fonctionnalités avancées. Le code existant n'a pas besoin de modifications.

  2. Media3 Muxer par défaut : VideoCapture utilise maintenant Media3 Muxer au lieu de MediaMuxer. Cela améliore la résilience aux crashs : si l'application se termine pendant l'enregistrement, le fichier vidéo reste lisible. La surface d'API reste inchangée.

  3. API SessionConfig stable : Les applications peuvent maintenant configurer des paramètres caméra avancés via SessionConfig sans annotations expérimentales. Cela permet des fonctionnalités comme l'utilisation simultanée des caméras avant/arrière sur les appareils supportés.

Chemin de migration : Mettre à jour la version de dépendance. Tester sur les appareils qui présentaient auparavant des problèmes. Les notes de version CameraX documentent tout changement de comportement entre versions.

  • CameraX utilise la liaison lifecycle-aware : bindToLifecycle() connecte les cas d'usage à la fois au lifecycle de la caméra et de l'Activity
  • Les modes ImageCapture échangent latence contre qualité : choisir en fonction du cas d'usage, pas des capacités de l'appareil
  • VideoCapture nécessite la configuration du Recorder avant binding : préparer les options de sortie avant de démarrer
  • La gestion des permissions doit expliquer le but avant de demander : réduit les taux de refus
  • CameraX 1.6 a migré vers CameraPipe en interne : aucun changement d'API requis, mais tester sur les appareils cibles
  • Les questions d'entretien se concentrent sur la gestion du lifecycle, les combinaisons de cas d'usage et la comparaison avec Camera2
Défi du jour

Tu saurais repérer le bug en Android ?

Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Fondateur de SharpSkill

Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.

Mis à jour le 11 septembre 2026

Partager

Articles similaires