Интеграция камеры и QR-сканера в приложении на Kotlin
Сканирование QR-кодов стало привычной функцией для мобильных приложений: оно используется при оплате, авторизации, учёте товаров, выдаче электронных билетов и обмене контактами. В Android-проекте на Kotlin такую возможность можно реализовать средствами CameraX и Google ML Kit, получив современный интерфейс с быстрым распознаванием изображения.
CameraX отвечает за подключение камеры, предварительный просмотр и анализ кадров, а ML Kit определяет штрихкоды и QR-коды. Такое разделение упрощает архитектуру приложения, повышает совместимость с устройствами и позволяет постепенно добавлять дополнительные сценарии обработки.
| Подход | Преимущества | Ограничения | Подходящий сценарий |
|---|---|---|---|
| CameraX + ML Kit | Современный API, хорошая совместимость, готовое распознавание | Требуется настройка зависимостей и анализ кадров | Большинство Android-приложений |
| Camera2 + собственный анализ | Полный контроль над камерой и параметрами съёмки | Сложная реализация и большое количество кода | Специализированные камеры |
| ZXing Embedded | Простое подключение готового сканера | Меньше контроля над интерфейсом и жизненным циклом | Быстрый прототип |
| WebView или внешний сканер | Минимум собственной логики | Зависимость от сторонних приложений и сети | Редкие второстепенные сценарии |
Выбор архитектуры и библиотек
Для нового проекта разумно использовать CameraX из Android Jetpack. Библиотека скрывает различия между моделями смартфонов и предоставляет понятные сценарии Preview, ImageAnalysis, ImageCapture и VideoCapture. Для QR-сканера обычно достаточно предварительного просмотра и анализа изображения.
ML Kit Barcode Scanning распознаёт QR-коды, Data Matrix, PDF417, Aztec, EAN, Code 128 и другие форматы. Если приложение работает только с QR, список поддерживаемых типов можно ограничить. Это сокращает объём анализа и помогает быстрее получать результат в условиях слабого освещения или небольшого размера кода.
Для чистого разделения ответственности удобно вынести работу с камерой в отдельный класс или компонент. Activity либо Fragment управляет разрешениями и состоянием интерфейса, анализатор кадров передаёт изображение в ML Kit, а ViewModel обрабатывает найденное значение и запускает бизнес-логику.
Подготовка проекта Kotlin
В Gradle-модуле Android-приложения потребуются зависимости CameraX и ML Kit. Версии библиотек следует выбирать совместимыми с используемой версией Android Gradle Plugin. В актуальных проектах зависимости обычно подключаются через каталог версий, однако принцип остаётся одинаковым.
dependencies {
val cameraX = "1.3.4"
implementation("androidx.camera:camera-camera2:$cameraX")
implementation("androidx.camera:camera-lifecycle:$cameraX")
implementation("androidx.camera:camera-view:$cameraX")
implementation("com.google.mlkit:barcode-scanning:17.3.0")
}
В манифесте необходимо объявить доступ к камере:
<uses-permission android:name="android.permission.CAMERA" />
Разрешение считается опасным, поэтому одного объявления недостаточно. На Android 6.0 и выше приложение должно запросить его во время работы. До запуска ProcessCameraProvider интерфейс должен проверить текущий статус разрешения и показать пользователю понятное объяснение назначения камеры.
Отображение изображения с камеры
В разметке можно использовать PreviewView. Он автоматически отображает поток с камеры и поддерживает корректное масштабирование изображения под экран:
<androidx.camera.view.PreviewView
android:id="@+id/cameraPreview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
После получения разрешения приложение создаёт экземпляр ProcessCameraProvider, выбирает заднюю камеру и связывает сценарий предварительного просмотра с жизненным циклом Fragment или Activity:
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(binding.cameraPreview.surfaceProvider)
}
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this,
cameraSelector,
preview,
imageAnalysis
)
Метод bindToLifecycle важен для стабильности приложения. CameraX автоматически останавливает поток при переходе экрана в фон и возобновляет работу после возвращения. Если камера уже занята другим сценарием, перед повторным подключением следует вызвать unbindAll, чтобы избежать конфликтов ресурсов.
Анализ кадров через ML Kit
Для передачи изображений в распознаватель используется ImageAnalysis. Настройка STRATEGY_KEEP_ONLY_LATEST позволяет пропускать устаревшие кадры, когда распознавание занимает больше времени, чем поступление новых изображений:
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(
ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
)
.build()
val scanner = BarcodeScanning.getClient(
BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
)
Анализатор получает ImageProxy, извлекает из него mediaImage и создаёт объект InputImage с правильным углом поворота. После завершения обработки кадр обязательно закрывается. Если забыть вызвать imageProxy.close(), поток постепенно остановится, а приложение начнёт расходовать память и процессорное время.
imageAnalysis.setAnalyzer(executor) { imageProxy ->
val mediaImage = imageProxy.image
if (mediaImage == null) {
imageProxy.close()
return@setAnalyzer
}
val image = InputImage.fromMediaImage(
mediaImage,
imageProxy.imageInfo.rotationDegrees
)
scanner.process(image)
.addOnSuccessListener { barcodes ->
val value = barcodes.firstOrNull()?.rawValue
if (!value.isNullOrBlank()) {
onCodeDetected(value)
}
}
.addOnCompleteListener {
imageProxy.close()
}
}
Обработчик может срабатывать много раз для одного и того же QR-кода. Поэтому найденное значение стоит временно блокировать после первого успешного результата. Обычно интерфейс показывает короткое подтверждение, останавливает анализ или устанавливает задержку перед повторным сканированием.
Обработка содержимого QR-кода
Значение QR-кода нельзя автоматически считать безопасной командой. Оно может содержать обычный текст, URL, номер телефона, данные Wi-Fi, JSON или произвольную строку. Приложение должно определить формат результата и передать его в соответствующий обработчик.
Для ссылок полезно проверять схему и хост. Разрешать следует https, а запуск неизвестных схем вроде intent, file или произвольных deep link нужно ограничивать. Если QR-код используется для входа в аккаунт, сервер обязан дополнительно проверить срок действия токена и его подпись.
Пример маршрутизации результата:
fun handleQrValue(value: String) {
val uri = runCatching { Uri.parse(value) }.getOrNull()
when {
uri?.scheme == "https" -> openTrustedLink(uri)
value.startsWith("WIFI:") -> parseWifiData(value)
value.startsWith("{") -> parseApplicationJson(value)
else -> showTextResult(value)
}
}
Для пользовательского опыта важно отображать распознанное значение перед выполнением действия. Кнопка копирования, повторное сканирование и сообщение об ошибке помогают избежать случайного перехода по поддельной ссылке или неверной активации промокода.
Разрешения и защита данных
Запрос доступа к камере следует выполнять в момент, когда пользователь понимает назначение функции. На экране можно кратко объяснить, что камера используется для поиска QR-кода и изображения не сохраняются. Если разрешение отклонено, приложение должно предложить повторить запрос или открыть системные настройки.
Обработка изображения происходит в памяти, поэтому кадры не нужно записывать на диск без явной необходимости. Не следует отправлять фотографии на сервер, если задача решается локальным ML Kit. Такой подход уменьшает задержку, расход трафика и риски утечки персональных данных.
Для корпоративных QR-кодов желательно использовать криптографическую проверку. Простое наличие JSON-поля role=admin не подтверждает подлинность сообщения. Надёжнее применять подписанные сервером данные, одноразовые токены и проверку времени действия на backend.
Производительность и качество распознавания
Сканирование быстрее работает при достаточном освещении, резком изображении и правильном расстоянии до кода. В интерфейсе полезно разместить рамку, подсказку о положении QR-кода и индикатор состояния камеры. Наличие визуальной зоны помогает пользователю удерживать код в центре кадра.
Для анализа не требуется максимальное разрешение камеры. Слишком большие кадры увеличивают нагрузку на CPU, поэтому параметры ImageAnalysis следует подбирать с учётом целевых устройств. Обработку нужно выполнять на отдельном executor, а тяжёлую бизнес-логику после распознавания передавать в корутины или ViewModel.
Тестировать сканер нужно на разных версиях Android, при повороте экрана, после возврата из фона и при отсутствии камеры. Отдельно проверяются маленькие, повреждённые, наклонённые и частично закрытые коды. Полезно также убедиться, что повторный запуск экрана не создаёт несколько анализаторов одновременно.
Жизненный цикл и расширение функциональности
Камеру следует освобождать при уничтожении экрана, особенно если используется собственный executor или отдельный объект сканера. В Fragment важно учитывать состояние viewLifecycleOwner, чтобы обработчик не обращался к уже уничтоженной разметке. Это предотвращает утечки памяти и ошибки при быстрой навигации.
Поверх базового QR-сканера можно добавить фонарик, переключение камер, ручной ввод значения, историю последних результатов и распознавание нескольких кодов в одном кадре. CameraX предоставляет управление фонариком через объект Camera, если конкретное устройство поддерживает эту функцию.
Такой модуль легко встроить в архитектуру с MVVM: камера отвечает за получение изображения, ML Kit — за обнаружение, ViewModel — за состояние и бизнес-правила, а UI — за отображение результата. Благодаря этому сканирование QR-кодов остаётся независимым компонентом, который можно повторно использовать в разных экранах Android-приложения.