rzaripov.kz

Интеграция камеры и 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-приложения.