Android CameraX w 2026: Przechwytywanie Zdjęć i Wideo z Pytaniami Rekrutacyjnymi

Poradnik Android CameraX 1.6 obejmujący przypadki użycia Preview, ImageCapture i VideoCapture. Zawiera kod Kotlin, integrację z Compose oraz pytania rekrutacyjne.

Android CameraX w 2026: Przechwytywanie Zdjęć i Wideo z Pytaniami Rekrutacyjnymi

Android CameraX upraszcza programowanie kamery poprzez abstrakcję złożoności sprzętowej za pomocą API świadomego cyklu życia. W wersji 1.6.2, która jest teraz stabilna, oraz po zakończeniu migracji do CameraPipe, CameraX dostarcza ten sam wydajny stos kamery, który zasila aplikację aparatu Pixel.

Kluczowe Zmiany w CameraX 1.6

CameraX 1.6 przeprowadził migrację do CameraPipe, domyślnie używa Media3 Muxer dla VideoCapture oraz ustabilizował API SessionConfig. Kotlin DSL builders (preview { }, imageCapture { }, videoCapture { }) redukują kod szablonowy o 40%.

Architektura CameraX i Model Przypadków Użycia

CameraX udostępnia cztery główne przypadki użycia, które mogą działać jednocześnie: Preview, ImageCapture, ImageAnalysis oraz VideoCapture. Każdy przypadek użycia wiąże się z LifecycleOwner, więc kamera automatycznie uruchamia się i zatrzymuje wraz z aktywnością lub fragmentem.

CameraProvider działa jako fabryka, która tworzy instancje tych przypadków użycia i wiąże je z kamerą. To rozdzielenie oznacza, że ten sam kod działa na urządzeniach różnych producentów bez martwienia się o specyficzne dziwactwa dostawców.

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 binds use cases to lifecycle
        cameraProvider = ProcessCameraProvider.getInstance(context).await()
    }
    
    fun bindUseCases(previewView: PreviewView) {
        // Unbind existing use cases before rebinding
        cameraProvider.unbindAll()
        
        // Preview use case displays camera feed
        val preview = Preview.Builder()
            .build()
            .apply { setSurfaceProvider(previewView.surfaceProvider) }
        
        // ImageCapture for photos
        imageCapture = ImageCapture.Builder()
            .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
            .build()
        
        // VideoCapture with Recorder
        val recorder = Recorder.Builder()
            .setQualitySelector(QualitySelector.from(Quality.FHD))
            .build()
        videoCapture = VideoCapture.withOutput(recorder)
        
        // Bind all use cases to lifecycle
        cameraProvider.bindToLifecycle(
            lifecycleOwner,
            CameraSelector.DEFAULT_BACK_CAMERA,
            preview,
            imageCapture,
            videoCapture
        )
    }
}

Wywołanie bindToLifecycle łączy przypadki użycia zarówno ze sprzętem kamery, jak i cyklem życia. Gdy LifecycleOwner wchodzi w stan STARTED, kamera się otwiera. Gdy wchodzi w stan STOPPED, kamera automatycznie się zamyka.

Implementacja Przechwytywania Zdjęć z ImageCapture

ImageCapture oferuje dwa tryby przechwytywania: CAPTURE_MODE_MINIMIZE_LATENCY dla szybkiej reakcji migawki oraz CAPTURE_MODE_MAXIMIZE_QUALITY dla zdjęć wymagających intensywnego przetwarzania. Wybór zależy od tego, czy aplikacja priorytetyzuje szybkość (aplikacje społecznościowe) czy jakość (skanery dokumentów).

PhotoCaptureManager.ktkotlin
class PhotoCaptureManager(
    private val context: Context,
    private val imageCapture: ImageCapture
) {
    private val mainExecutor = ContextCompat.getMainExecutor(context)
    
    fun capturePhoto(onResult: (Uri?) -> Unit) {
        // Generate unique filename with timestamp
        val filename = "IMG_${System.currentTimeMillis()}.jpg"
        
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
            // Store in Pictures/CameraX folder on 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 contains the MediaStore URI
                    onResult(results.savedUri)
                }
                
                override fun onError(exception: ImageCaptureException) {
                    Log.e("PhotoCapture", "Capture failed: ${exception.message}")
                    onResult(null)
                }
            }
        )
    }
}

CameraX 1.5 dodał obsługę przechwytywania DNG (RAW) dla aplikacji wymagających nieprzetworzonych danych z sensora. Należy sprawdzić możliwości urządzenia za pomocą ImageCapture.getImageCaptureCapabilities() przed włączeniem wyjścia RAW.

Nagrywanie Wideo z VideoCapture i Recorder

VideoCapture używa obiektu Recorder do obsługi kodowania i miksowania. API Recorder stosuje wzorzec przygotuj-rozpocznij-zatrzymaj, który automatycznie obsługuje synchronizację 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 {
        // Check if already recording
        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()
        
        // Prepare recording with audio if permission granted
        val pendingRecording = videoCapture.output
            .prepareRecording(context, mediaStoreOutput)
        
        if (hasAudioPermission()) {
            pendingRecording.withAudioEnabled()
        }
        
        // Start recording and store reference
        activeRecording = pendingRecording.start(
            ContextCompat.getMainExecutor(context)
        ) { event ->
            onEvent(event)
            
            // Clear reference when recording completes
            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 dodał obsługę nagrywania wideo w zwolnionym tempie. Należy sprawdzić możliwości za pomocą Recorder.getHighSpeedVideoCapabilities() przed włączeniem nagrywania 120fps lub 240fps.

Gotowy na rozmowy o Android?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

CameraX z Jetpack Compose

CameraX 1.5 wprowadził artefakt camera-compose z kompozytorem CameraXViewfinder. Dla aplikacji nadal używających PreviewView opartego na widokach, AndroidView zapewnia interoperacyjność.

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)
            }
        )
        
        // Capture buttons overlay
        Row(
            modifier = Modifier
                .align(Alignment.BottomCenter)
                .padding(32.dp),
            horizontalArrangement = Arrangement.spacedBy(24.dp)
        ) {
            IconButton(
                onClick = { viewModel.capturePhoto() }
            ) {
                Icon(Icons.Default.Camera, "Take photo")
            }
            
            IconButton(
                onClick = { viewModel.toggleRecording() }
            ) {
                Icon(
                    if (viewModel.isRecording) Icons.Default.Stop
                    else Icons.Default.Videocam,
                    "Record video"
                )
            }
        }
    }
}

Dla implementacji opartych na Compose, przewodnik pytań rekrutacyjnych Jetpack Compose obejmuje wzorce integracji ViewModel, które mają bezpośrednie zastosowanie w zarządzaniu stanem kamery.

Obsługa Uprawnień Kamery

Android wymaga uprawnienia CAMERA w czasie wykonywania. RECORD_AUDIO jest potrzebne tylko podczas nagrywania wideo z dźwiękiem. Przepływ uprawnień powinien wyjaśniać, dlaczego dostęp jest potrzebny, przed zażądaniem.

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)
        }
    }
}

Pytania Rekrutacyjne CameraX

CameraX pojawia się na rozmowach kwalifikacyjnych Android podczas omawiania integracji kamery, zarządzania cyklem życia lub abstrakcji sprzętowej. Te pytania testują zrozumienie architektury i praktycznych szczegółów implementacji.

Dlaczego używać CameraX zamiast Camera2?

CameraX abstrahuje specyficzne dla urządzeń dziwactwa, które Camera2 eksponuje bezpośrednio. Implementacja Camera2 wymaga obsługi setek obejść specyficznych dla urządzeń. CameraX wbudowuje te poprawki w bibliotekę, testowaną na CameraX Test Lab, który obejmuje ponad 150 modeli urządzeń.

Wiązanie świadome cyklu życia eliminuje ręczne zarządzanie zasobami. Camera2 wymaga jawnych wywołań open/close, które powodują wycieki, gdy nie są poprawnie obsługiwane w każdym wywołaniu zwrotnym cyklu życia.

Jak CameraProvider wiąże przypadki użycia z cyklem życia?

CameraProvider wewnętrznie używa wzorca LifecycleObserver. Podczas wywoływania bindToLifecycle() rejestruje obserwatora na dostarczonym LifecycleOwner. Ten obserwator otrzymuje zdarzenia ON_START i ON_STOP, aby otwierać i zamykać kamerę.

Kluczowa uwaga: CameraX nie uruchamia kamery natychmiast po wiązaniu. Czeka, aż cykl życia osiągnie stan STARTED, co odpowiada czasowi Activity.onStart().

Co się dzieje, gdy wiążemy niekompatybilne przypadki użycia?

Nie wszystkie kombinacje przypadków użycia działają na wszystkich urządzeniach. Preview + VideoCapture + ImageAnalysis + ImageCapture może nie działać na starszym sprzęcie. CameraX wyrzuca IllegalArgumentException w momencie wiązania, a nie w czasie wykonywania.

Rozwiązaniem jest sprawdzanie CameraProvider.hasCamera() i możliwości urządzenia przed wiązaniem złożonych kombinacji. Dla krytycznych aplikacji należy jawnie testować na urządzeniach z niższej półki.

Wyjaśnij różnicę między CAPTURE_MODE_MINIMIZE_LATENCY a CAPTURE_MODE_MAXIMIZE_QUALITY

MINIMIZE_LATENCY redukuje opóźnienie migawki poprzez użycie szybszych potoków przetwarzania, potencjalnie pomijając niektóre post-przetwarzanie. MAXIMIZE_QUALITY stosuje pełne przetwarzanie HDR+, redukcję szumów i przechwytywanie wieloklatkowe na obsługiwanych urządzeniach.

Kompromis: tryb opóźnienia przechwytuje w 100-200ms, tryb jakości może zająć 500ms-1s w zależności od oświetlenia. Aplikacje społecznościowe zazwyczaj wybierają opóźnienie. Aplikacje do skanowania dokumentów wybierają jakość.

Jak CameraX obsługuje rotację urządzenia?

CameraX automatycznie obsługuje rotację poprzez ustawienie docelowej rotacji. Domyślnie używa rotacji wyświetlacza, ale aplikacje mogą nadpisać za pomocą setTargetRotation(). Dane EXIF obrazu wyjściowego zawierają poprawny tag orientacji.

Dla PreviewView, scaleType obsługuje różnice proporcji. FILL_CENTER przycina, aby wypełnić, FIT_CENTER pokazuje letterboxing. Wybór zależy od tego, czy aplikacja priorytetyzuje podgląd pełnoekranowy czy unikanie przycinania.

Zależności i Konfiguracja dla CameraX 1.6

Biblioteka CameraX jest podzielona na wiele artefaktów. Należy dołączyć tylko to, czego aplikacja używa, aby zminimalizować rozmiar APK.

build.gradle.kts (Module: app)kotlin
dependencies {
    val cameraxVersion = "1.6.2"
    
    // Core library required for all use cases
    implementation("androidx.camera:camera-core:$cameraxVersion")
    
    // Camera2 implementation (required)
    implementation("androidx.camera:camera-camera2:$cameraxVersion")
    
    // Lifecycle integration
    implementation("androidx.camera:camera-lifecycle:$cameraxVersion")
    
    // VideoCapture use case
    implementation("androidx.camera:camera-video:$cameraxVersion")
    
    // PreviewView and camera UI components
    implementation("androidx.camera:camera-view:$cameraxVersion")
    
    // Optional: Extensions (Night mode, HDR, etc.)
    implementation("androidx.camera:camera-extensions:$cameraxVersion")
    
    // Optional: Real-time effects
    implementation("androidx.camera:camera-effects:$cameraxVersion")
}

android {
    // CameraX requires Java 11 bytecode
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
}

Manifest wymaga deklaracji funkcji i uprawnień:

xml
<!-- AndroidManifest.xml -->
<manifest>
    <!-- Camera hardware requirement -->
    <uses-feature android:name="android.hardware.camera.any" />
    
    <!-- Runtime permissions -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>

Powiązane wzorce dla Kotlin Coroutines mają zastosowanie przy opakowywaniu wywołań CameraX ListenableFuture w funkcje suspend.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Co CameraX 1.6 Zmienia dla Aplikacji Produkcyjnych

CameraX 1.6 wprowadził trzy zmiany wpływające na istniejące implementacje:

  1. Backend CameraPipe: Wewnętrzny stos kamery teraz używa CameraPipe, tej samej architektury co kamera Pixel. Poprawia to wydajność na urządzeniach Pixel i zapewnia czystszą podstawę dla zaawansowanych funkcji. Istniejący kod nie wymaga zmian.

  2. Domyślny Media3 Muxer: VideoCapture teraz używa Media3 Muxer zamiast MediaMuxer. Poprawia to odporność na awarie: jeśli aplikacja zostanie zakończona podczas nagrywania, plik wideo pozostaje odtwarzalny. Powierzchnia API pozostaje niezmieniona.

  3. Stabilne API SessionConfig: Aplikacje mogą teraz konfigurować zaawansowane ustawienia kamery przez SessionConfig bez adnotacji eksperymentalnych. Umożliwia to funkcje takie jak jednoczesne użycie kamery przedniej i tylnej na obsługiwanych urządzeniach.

Ścieżka migracji: Zaktualizuj wersję zależności. Przetestuj na urządzeniach, które wcześniej miały problemy. Notatki wydania CameraX dokumentują wszelkie zmiany behawioralne między wersjami.

  • CameraX używa wiązania świadomego cyklu życia: bindToLifecycle() łączy przypadki użycia zarówno z kamerą, jak i cyklem życia Activity
  • Tryby ImageCapture wymieniają opóźnienie na jakość: wybierz na podstawie przypadku użycia, a nie możliwości urządzenia
  • VideoCapture wymaga konfiguracji Recorder przed wiązaniem: przygotuj opcje wyjścia przed rozpoczęciem
  • Obsługa uprawnień powinna wyjaśniać cel przed żądaniem: zmniejsza wskaźnik odrzuceń
  • CameraX 1.6 wewnętrznie przeprowadził migrację do CameraPipe: nie są wymagane zmiany API, ale testuj na docelowych urządzeniach
  • Pytania rekrutacyjne koncentrują się na zarządzaniu cyklem życia, kombinacjach przypadków użycia i porównaniu z Camera2
Wyzwanie dnia

Znajdziesz błąd w Android?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 11 września 2026

Udostępnij

Powiązane artykuły