Android CameraX у 2026: Захоплення Фото та Відео з Питаннями для Співбесід

Посібник з Android CameraX 1.6, що охоплює випадки використання Preview, ImageCapture та VideoCapture. Містить код Kotlin, інтеграцію з Compose та питання для співбесід.

Android CameraX у 2026: Захоплення Фото та Відео з Питаннями для Співбесід

Android CameraX спрощує розробку камери шляхом абстрагування апаратної складності за допомогою API, що враховує життєвий цикл. З версією 1.6.2, яка тепер стабільна, та завершеною міграцією на CameraPipe, CameraX надає той самий високопродуктивний стек камери, що живить додаток камери Pixel.

Ключові Зміни в CameraX 1.6

CameraX 1.6 мігрував на CameraPipe, за замовчуванням використовує Media3 Muxer для VideoCapture та стабілізував API SessionConfig. Kotlin DSL builders (preview { }, imageCapture { }, videoCapture { }) зменшують шаблонний код на 40%.

Архітектура CameraX та Модель Випадків Використання

CameraX надає чотири основні випадки використання, які можуть працювати одночасно: Preview, ImageCapture, ImageAnalysis та VideoCapture. Кожен випадок використання прив'язується до LifecycleOwner, тому камера автоматично запускається та зупиняється разом з activity або fragment.

CameraProvider діє як фабрика, що створює екземпляри цих випадків використання та прив'язує їх до камери. Це розділення означає, що той самий код працює на пристроях різних виробників без турботи про специфічні особливості постачальників.

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

Виклик bindToLifecycle з'єднує випадки використання як з апаратним забезпеченням камери, так і з життєвим циклом. Коли LifecycleOwner входить у стан STARTED, камера відкривається. Коли входить у стан STOPPED, камера автоматично закривається.

Реалізація Захоплення Фото з ImageCapture

ImageCapture пропонує два режими захоплення: CAPTURE_MODE_MINIMIZE_LATENCY для швидкої реакції затвора та CAPTURE_MODE_MAXIMIZE_QUALITY для знімків з інтенсивною обробкою. Вибір залежить від того, чи додаток пріоритезує швидкість (соціальні додатки) чи якість (сканери документів).

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 додав підтримку захоплення DNG (RAW) для додатків, що потребують необроблених даних сенсора. Перевірте можливості пристрою за допомогою ImageCapture.getImageCaptureCapabilities() перед увімкненням виводу RAW.

Запис Відео з VideoCapture та Recorder

VideoCapture використовує об'єкт Recorder для обробки кодування та мікшування. API Recorder дотримується шаблону підготувати-почати-зупинити, який автоматично обробляє синхронізацію аудіо.

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 додав підтримку уповільненого відео. Запитуйте можливості за допомогою Recorder.getHighSpeedVideoCapabilities() перед увімкненням запису 120fps або 240fps.

Готовий до співбесід з Android?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

CameraX з Jetpack Compose

CameraX 1.5 представив артефакт camera-compose з composable CameraXViewfinder. Для додатків, що досі використовують PreviewView на основі View, AndroidView забезпечує сумісність.

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

Для реалізацій на основі Compose, посібник питань для співбесід Jetpack Compose охоплює шаблони інтеграції ViewModel, що безпосередньо застосовуються до керування станом камери.

Обробка Дозволів Камери

Android вимагає дозволу CAMERA під час виконання. RECORD_AUDIO потрібен лише при записі відео зі звуком. Потік дозволів повинен пояснювати, чому потрібен доступ, перед запитом.

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

Питання для Співбесід з CameraX

CameraX з'являється на співбесідах Android при обговоренні інтеграції камери, керування життєвим циклом або апаратної абстракції. Ці питання тестують розуміння архітектури та практичних деталей реалізації.

Чому використовувати CameraX замість Camera2?

CameraX абстрагує специфічні для пристроїв особливості, які Camera2 експонує безпосередньо. Реалізація Camera2 вимагає обробки сотень специфічних для пристроїв обхідних рішень. CameraX вбудовує ці виправлення в бібліотеку, тестовану на CameraX Test Lab, що охоплює понад 150 моделей пристроїв.

Прив'язка, що враховує життєвий цикл, усуває ручне керування ресурсами. Camera2 вимагає явних викликів open/close, які спричиняють витоки, якщо не обробляються правильно в кожному зворотному виклику життєвого циклу.

Як CameraProvider прив'язує випадки використання до життєвого циклу?

CameraProvider внутрішньо використовує шаблон LifecycleObserver. При виклику bindToLifecycle() реєструє спостерігача на наданому LifecycleOwner. Цей спостерігач отримує події ON_START та ON_STOP для відкриття та закриття камери.

Ключовий момент: CameraX не запускає камеру негайно при прив'язці. Він чекає, поки життєвий цикл досягне стану STARTED, що відповідає часу Activity.onStart().

Що відбувається, коли прив'язуються несумісні випадки використання?

Не всі комбінації випадків використання працюють на всіх пристроях. Preview + VideoCapture + ImageAnalysis + ImageCapture може не працювати на старішому обладнанні. CameraX викидає IllegalArgumentException під час прив'язки, а не під час виконання.

Рішення — перевірка CameraProvider.hasCamera() та можливостей пристрою перед прив'язкою складних комбінацій. Для критичних додатків явно тестуйте на пристроях низького класу.

Поясніть різницю між CAPTURE_MODE_MINIMIZE_LATENCY та CAPTURE_MODE_MAXIMIZE_QUALITY

MINIMIZE_LATENCY зменшує затримку затвора шляхом використання швидших конвеєрів обробки, можливо пропускаючи деяку пост-обробку. MAXIMIZE_QUALITY застосовує повну обробку HDR+, зменшення шуму та багатокадрове захоплення на підтримуваних пристроях.

Компроміс: режим затримки захоплює за 100-200мс, режим якості може займати 500мс-1с залежно від освітлення. Соціальні додатки зазвичай обирають затримку. Додатки для сканування документів обирають якість.

Як CameraX обробляє обертання пристрою?

CameraX автоматично обробляє обертання через налаштування цільового обертання. За замовчуванням використовує обертання дисплея, але додатки можуть перевизначити за допомогою setTargetRotation(). Дані EXIF вихідного зображення містять правильний тег орієнтації.

Для PreviewView, scaleType обробляє відмінності співвідношення сторін. FILL_CENTER обрізає для заповнення, FIT_CENTER показує letterboxing. Вибір залежить від того, чи додаток пріоритезує повноекранний попередній перегляд чи уникнення обрізки.

Залежності та Налаштування для CameraX 1.6

Бібліотека CameraX розділена на кілька артефактів. Додавайте лише те, що використовує додаток, щоб мінімізувати розмір 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
    }
}

Маніфест потребує оголошень функцій та дозволів:

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>

Пов'язані шаблони для Kotlin Coroutines застосовуються при обгортанні викликів CameraX ListenableFuture у suspend функції.

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Що CameraX 1.6 Змінює для Продакшн Додатків

CameraX 1.6 приніс три зміни, що впливають на існуючі реалізації:

  1. Бекенд CameraPipe: Внутрішній стек камери тепер використовує CameraPipe, ту саму архітектуру, що й камера Pixel. Це покращує продуктивність на пристроях Pixel та забезпечує чистішу основу для розширених функцій. Існуючий код не потребує змін.

  2. Media3 Muxer за замовчуванням: VideoCapture тепер використовує Media3 Muxer замість MediaMuxer. Це покращує стійкість до збоїв: якщо додаток завершується під час запису, відеофайл залишається відтворюваним. Поверхня API не змінилася.

  3. Стабільний API SessionConfig: Додатки тепер можуть налаштовувати розширені параметри камери через SessionConfig без експериментальних анотацій. Це дозволяє такі функції, як одночасне використання передньої та задньої камери на підтримуваних пристроях.

Шлях міграції: Оновіть версію залежності. Протестуйте на пристроях, що раніше мали проблеми. Примітки до випуску CameraX документують будь-які зміни поведінки між версіями.

  • CameraX використовує прив'язку, що враховує життєвий цикл: bindToLifecycle() з'єднує випадки використання як з камерою, так і з життєвим циклом Activity
  • Режими ImageCapture обмінюють затримку на якість: обирайте на основі випадку використання, а не можливостей пристрою
  • VideoCapture вимагає налаштування Recorder перед прив'язкою: підготуйте параметри виводу перед початком
  • Обробка дозволів повинна пояснювати мету перед запитом: зменшує рівень відмов
  • CameraX 1.6 внутрішньо мігрував на CameraPipe: зміни API не потрібні, але тестуйте на цільових пристроях
  • Питання для співбесід зосереджуються на керуванні життєвим циклом, комбінаціях випадків використання та порівнянні з Camera2
Щоденний виклик

Чи знайдеш ти помилку в Android?

Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Anthony Fillion-Maillet

Автор:

Anthony Fillion-Maillet

Засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 11 вересня 2026 р.

Поділитися

Пов'язані статті