Telegram Web Link
Старая архитектура vs новая архитектура

Когда React Native только появился, то команда Facebook предложила следующую концепцию. JavaScript-инструкция отправлялась через bridge выполняя код из нативного модуля, затем результат выполнения отправляется обратно в JavaScript-код через bridge. Эта операция является асинхронной, обозначая, что JavaScript-код может выполняться не дожидаясь возвращения результата от нативного модуля. Данный подход нужен для того, чтобы приложение осталось отзывчивым, особенно для задач, которые занимают много времени, например, обработка изображений или доступ к камере, к Bluetooth, NFC, работа с OpenGL.


Проблемы старой архитектуры
1. Большое количество событий, требующих сериализации / десериализации (передающихся через Bridge) становится бутылочным горлышком и замедляет UI.
2. Асинхронность: коммуникация через bridge всегда асинхронна, что делает невозможным выполнение синхронных вызовов между JavaScript и нативными модулями.

Разберем на примере игры с обновлениями в реальном времени, которую мы захотим написать на React Native, где пользовательские вводы отправляются в нативный код для обработки — например, для определения столкновений в физическом движке. Мост (bridge) создает задержки из-за времени, необходимого на:
• Сериализацию входных данных в формат JSON;
• Отправку данных в нативный модуль;
• Обработку данных на нативной стороне;
• Сериализацию и возврат ответа обратно в JavaScript.

Эта задержка делает приложение менее отзывчивым и может вызывать зависания и лаги в приложениях, работающих в реальном времени. Пользователи могу столкнуться с заметными задержками.
6👍1
В новой архитектуре нативные модули стали называться TurboModules. Главным нововведением стало удаление bridge и замена его на JSI (JavaScript Interface). В старой архитектуре много времени уходило на отправку и получение больших сериализованных сообщений через bridge, а TurboModules позволяют JavaScript напрямую дергать нативный код без использования бриджа.

Основные изменения:

1. Удаление bridge и добавление JSI. JSI предоставляет JavaScript прямой доступ к нативным объектам, что позволяет выполнять операции быстрее и с меньшими накладными расходами.
2. Lazy Click Me Load More, в старой архитектуре все нативные модули подгружались при загрузке приложения, а в новой только по необходимости. Соответственно есть выигрыш в памяти.
3. Бридж сериализовывал сообщения и десериализовывал, что может также замедлять работу приложения.
4. TurboModules получают и отправляют данные напрямую через JSI.
5. В TurboModules улучшено управление потоками.
6. Fabric (новый UI рендеринг) — обеспечивает более быстрый рендеринг пользовательского интерфейса. Поддерживает асинхронное обновление UI, что предотвращает блокировки основного потока. Позволяет обновлять только измененные части UI вместо перерисовки всего экрана.


Как итог TurboModules смогли улучшить производительность, оптимизировать использование ресурсов, и улучшили работу анимаций, время загрузки и отзывчивость приложения.
6❤‍🔥4👍1
Далее хочу рассказать об одном нативном модуле, который я написал, чтобы решить довольно непростую проблему в проекте.

У нас есть проект для риэлторов, в котором необходимо подключаться через приложение к 360 камере и делать панорамные снимки. На камере есть специальный баркод, такой маленький в виде data matrix, в котором зашит ssid нашей камеры. Нам необходимо было отсканировать его, считать ssid и подключиться к камере через Wi-Fi. Сперва мы искали существующие решения, но библиотеки просто не могли отсканировать такой маленький баркод, а задать область было невозможно. Перебрали немало библиотек, какие-то из них могли распарсить маленький баркод на iOS, например, библиотека react-native-vision-camera позволяла это сделать, но на Android не получалось ничего отсканировать, так как баркод был и инвертированный к тому же. Поэтому сперва его нужно было инвертировать и только потом отсканировать. Было принято решение написать кастомный нативный модуль, про который я сейчас немного расскажу. На прикрепленной картинке изображена нижняя часть камеры с кодом YN14010033, который вшит в дата матрицу справа от этого кода.
5👍2🌭2
Проблемы готовых решений
Отсутствие одинакового поведения на нескольких платформах.Так на iOS могло работать на ультра широкой камере, на Android не хотелось заводиться совсем.
1. Частые ошибки при сканировании из-за недостаточного освещения.
2. Отсутствие сканирования по области.
3. Нам необходимо было сделать процесс сканирования удобным, дабы избежать ситуации, когда пользователь пытается безуспешно несколько минут отсканировать код, но с готовыми библиотеками об этом сразу можно было забыть.
3👍2🌭2
Проблема многих библиотек под React Native в том, что они работают для общих случаев, тот же QR код платежный сканировался практически везде. Но если случай не самый обычный, то частенько не остается никаких альтернатив, кроме как взяться за написание нативного модуля, чем я в итоге и занялся, бросив попытки найти ту самую библиотеку. Самое интересное, что мозг пытается верить, что библиотечку удастся настроить под свои нужды, и довольно сложно заставить себя открывать тот же XCode и Android Studio и пытаться по мануалам написать свой первый нативный модуль, который будет работать так как нужно тебе. И здесь хочу отметить, что в этом месте я был немного разочарован в React Native, что все также возникает необходимость писать нативный код, иногда даже много нативного кода. Хотя фреймворк обещает Learn Once. Write Everywhere, мне довольно часто приходилось лезть внутрь библиотек и разбираться, почему ничего не работает и патчить на свой страх и риск. Поэтому, на мой взгляд, разрабатывать для React Native без знания натива достаточно сложно. Для простых экранов и относительно стандартного UI можно найти много библиотек, которые просто будут работать из коробки, но если требуется что-то кастомное, то же подключение к сторонним устройствам, сканирование баркодов и многое другое может отнять кучу нервов и заставить погрузиться в мир нативной разработки хоть чуть-чуть.
👍114❤‍🔥2
После того как мы обозначили важность нативных модулей в коде, предлагаю немного погрузиться в имплементацию данного модуля. Напомню, что основная цель это сканирование маленького баркода камеры для автоматического подключения к камере по Wi-Fi. Данный модуль состоит из трех частей.
1. BarcodeView — это обычная вьюха, которая показывает превью с камеры и по сути является нашим React-компонентом. Данная вьюха показывает поток с камеры, позволяя пользователю видеть картинку в реальном времени.
2. BarcodeCameraManager — это класс, который настраивает камеру и превью с ней. Для настрйоки камеры требуется создать camera input, camera output и настроить capture session. По факту данный класс полностью несет ответственность за работу с камерой, позволяя выбрать нужную камеру, настроить фокус. А также данный класс принимает колбэки, которые перенаправляют видеопоток в функцию, которая сканирует баркод.
3. BarcodeScanner — класс, где случается вся магия. Класс, который проверяет каждый видеофрейм на наличие баркода, накладывая фильтры на изображение для улучшения видимости этого баркода (накладываем grayscale для улучшения контраста и инвертируем картинку, чтобы алгоритмы Vision Framework смогли распознать код).

Приложу ссылку на исходный код, если хотите взглянуть https://github.com/kidasov/qrparser
4👍4❤‍🔥1
Далее расскажу вкратце об исходном коде. BarcodeView — это UIView, аналог View в React Native, в котором мы инициализируем инстансы BarcodeCameraManager и BarcodeDetector, в качестве параметров данный компонент принимает функцию onBarcodeRead, которая вызывается в момент успешного сканирования баркода.
При добавлении в родительскую вьюху запускается setupCameraLiveView(), чтобы включить камеру. При удалении из родительской вьюхи вызывается stopCameraLiveView() для освобождения ресурсов. Также компонент реализует протокол BackgroundListenerDelegate, при переходе в background срабатывает функция onBackgroundMove, которая останавливает работу камеры при переходе приложения в фон. При переходе в foreground срабатывает onForegroundMove, которая перезапускает камеру при возвращении приложения в foreground. Также, для дебага мы отображаем картинку, в которой мы ищем баркод и отрисовываем ее в функции
_DebugSetPhotoImagePreview. Код можно посмотреть здесь https://github.com/kidasov/qrparser/blob/main/ios/BarcodeView.swift
4❤‍🔥1
Следующим у нас идет код класса BarcodeCameraManager, который выполняет настройку камеры, захват видео и обработку кадров. Перечислим свойства данного класса.

backCamera — устройство захвата видеопотока (задняя камера в нашем случае). captureVideoOutput — выходной поток для получения видеокадров.
captureSession — сессия захвата, которая связывает входы (камера) и выходы (видео).
cameraPreviewLayer — слой для отображения видео с камеры в пользовательском интерфейсе.

В функции setupCamera мы настраиваем камеру, устанавливаем разрешение для видеопотока hd1920x1080. Затем в configureBackCamera мы находим заднюю камеру телефона, далее настраиваем input и output для нашей сессии, и в функции startCaptureSession запускаем сессию. Класс реализует протокол AVCaptureVideoDataOutputSampleBufferDelegate, что позволяет получать каждый кадр из видеопотока: метод captureOutput(_:didOutput:from:): вызывается при получении нового кадра и передает кадр в onCaptureOutput для дальнейшей обработки (для сканирования баркода).
https://github.com/kidasov/qrparser/blob/main/ios/BarcodeCameraManager.swift
❤‍🔥32
Расскажу еще об одном челлендже при попытке отсканировать наш баркод. Многие библиотеки сканировали баркод только с помощью широкогоугольной камеры (builtInUltraWideCamera), но этой камеры у нас не было на iPhone X, поэтому это был еще один аргумент в пользу написания кастомного модуля для того, чтобы баркод сканировался и на устаревших моделях iPhone. В коде мы используем builtInWideAngleCamera — это системное обозначение основной камеры на устройствах iOS. Эта камера является стандартной для всех iPhone, начиная с самых ранних моделей.
❤‍🔥32
И наконец, у нас остается класс детектора баркода BarcodeDetector, который отвечает за обнаружение штрихкодов на изображениях, используя Vision Framework от Apple. Он инкапсулирует весь процесс обработки изображения от его предварительной подготовки до распознавания баркода. Первая проблема, с которой мы столкнулись, была в том, что баркод не сканировался средствами Vision Framework, поскольку он был слишком маленький. Поэтому сперва пришлось обрезать изображение — мы использовали imageOffsetX и imageOffsetY для определения области интереса на изображении (ROI, Region of Interest).
❤‍🔥4
На вход компонент принимает три колбэка:
onPreview — показывает предварительный просмотр обработанного изображения;
onBarcodeRead — возвращает результат распознавания;
onShowError — уведомляет об ошибках.


Распознавание штрихкода просиходит в методе (detectBarcodeFromImage) Далее о том, что выполняет этот метод.
❤‍🔥4
1. Мы хотим вырезать область с баркодом из изображения полученного с камеры. Изображение с камеры отличается по размеру от превью. Для того чтобы рассчитать коэффициент, на которое оно отличается, мы используем метод calculateImageScale. Метод учитывает то, что исходное изображение повернуты на 90 градусов.Вычисляет масштаб изображения. Метод сравнивает соотношение сторон изображения и кадра, чтобы определить коэффициент масштабирования для корректной обрезки.
❤‍🔥4
2. Обрезка изображения, вычисляем область интереса (ROI) с учетом смещения (imageOffsetX, imageOffsetY) и размера (imageSize). Маленький синий квадратик на экране
❤‍🔥4
Процесс сканирования выглядит следующим образом
❤‍🔥4
3. Обработка изображения — преобразуем изображение в монохромное (monochrome) и инвертирует его (invert). На скриншоте снизу мы применяем фильтры, в левом верхнем углу можно увидеть изображение, на которое были применены фильтры.
❤‍🔥4
4. Показываем получившееся изображение на экране в левом углу для дебага: вызывая колбэк onPreview с обработанным изображением.
❤‍🔥4
5. Пытаемся распознать баркод с использованием Vision Framework для разных ориентаций изображения (.up, .right, .left, .down). Разных типов изображений (монохромное, инвертированное). Если штрихкод найден, вызываем onBarcodeRead и прекращает дальнейший анализ. На картинках показано обнаружение баркода в разных ориентациях
❤‍🔥2
При обработке изображений мы используем следующие методы для обработки

1. invert — инвертирует цвета изображения. Использует фильтр CIColorMatrix для инверсии цветовых каналов. Про ColorMatrix можно почитать здесь: https://docs.rainmeter.net/tips/colormatrix-guide/

2. monochrome — преобразует изображение в черно-белое. Регулирует яркость, контраст и насыщенность через фильтр CIColorControls, увеличиваем экспозицию с помощью CIExposureAdjust.

Эти преобразования повышают вероятность успешного распознавания баркода в различных условиях при недостаточном освещении.
❤‍🔥3
До и После

До
1. Не могли отсканировать баркод средствами библиотек (react-native-vision).
2. Не могли кастомизировать библиотеки таким образом, чтобы кропнуть зону поиска.
3. Невозможно было наложить фильтры, на том же Android без инвертирования изображения, сканирование баркода было всегда безуспешным.
4. Не могли выбрать нужную камеру,

После
1. Получили полностью кастомный модуль, который легко можно настроить под свои нужды.
2. Сканируем баркод на всевозможных iOS и Android устройствах.
3. Не думаем искать готовое решение, перебирая кучу библиотек.
❤‍🔥3👍1
2025/07/09 05:12:17
Back to Top
HTML Embed Code: