Разработка Native Module для React Native-приложения (Android)
Представьте: вы подключаете к React Native приложению специфичное Bluetooth-устройство, но стандартные библиотеки не дают нужного уровня контроля. JavaScript не может напрямую вызвать Android API — нужен Native Module. Ошибка в его реализации приведёт к крашу приложения или утечке памяти. За 5 лет работы мы выполнили более 30 проектов с Native Modules и знаем типичные ловушки. Разберём на примере Bluetooth LE.
Native Module — это мост между JS и нативным кодом Android. React Native предлагает два подхода: классический Bridge и новый TurboModule на основе JSI. Выбор архитектуры напрямую влияет на скорость работы: TurboModule обеспечивает latency 1–2 мс против 10–20 мс у Bridge, что даёт выигрыш до 10 раз для высокочастотных вызовов. Эта разница заметна при непрерывном потоке данных (аудио, датчики).
Реализация Bridge-модуля
Для проектов, использующих старую архитектуру (без поддержки JSI), применяйте классический подход.
// BluetoothModule.kt class BluetoothModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { override fun getName(): String = "BluetoothModule" @ReactMethod fun isBluetoothEnabled(promise: Promise) { val bluetoothManager = reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager promise.resolve(bluetoothManager.adapter?.isEnabled ?: false) } @ReactMethod fun startScan(promise: Promise) { val scanner = (reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager) .adapter?.bluetoothLeScanner if (scanner == null) { promise.reject("BT_ERROR", "Bluetooth LE not supported") return } promise.resolve(null) } private fun sendEvent(eventName: String, params: WritableMap?) { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(eventName, params) } } Зарегистрируйте модуль через ReactPackage и добавьте его в MainApplication.kt в метод getPackages().
Как выбрать между Bridge и TurboModule?
| Критерий | Bridge (старая архитектура) | TurboModule (новая архитектура) |
|---|---|---|
| Производительность | Вызов через JSON-сериализацию, latency ~10-20 мс | JSI без сериализации, latency ~1-2 мс |
| Совместимость | Проекты без поддержки JSI | Проекты с новой архитектурой (JSI) |
| Поддержка | Устаревает, но ещё широко используется | Будущее React Native, активная разработка |
| Сложность | Проще, не требует Codegen | Требует TypeScript спецификацию и генерацию кода |
Для высокочастотных вызовов (аудио, датчики) TurboModule даёт выигрыш в производительности до 10 раз. Затраты на разработку такого модуля окупаются за счёт снижения времени доработок на 30%.
Почему TurboModule быстрее Bridge?
TurboModule использует JSI (JavaScript Interface) для прямого вызова нативных функций, минуя JSON-сериализацию. Bridge же каждый вызов упаковывает в JSON, что добавляет накладные расходы. Для одиночных вызовов разница незаметна, но при 1000 вызовов в секунду TurboModule экономит до 20 мс — критично для анимаций или обработки датчиков.
Как передавать события из нативного кода в JS?
Паттерн един для обеих архитектур — используйте RCTDeviceEventEmitter.
fun emitScanResult(deviceAddress: String, rssi: Int) { val params = Arguments.createMap().apply { putString("address", deviceAddress) putInt("rssi", rssi) } reactApplicationContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit("onBluetoothDeviceFound", params) } На JS подписывайтесь через NativeEventEmitter:
import { NativeEventEmitter, NativeModules } from 'react-native'; const { BluetoothModule } = NativeModules; const emitter = new NativeEventEmitter(BluetoothModule); useEffect(() => { const subscription = emitter.addListener('onBluetoothDeviceFound', (event) => { console.log('Found device:', event.address, 'RSSI:', event.rssi); }); return () => subscription.remove(); }, []); Обязательно вызывайте subscription.remove() в cleanup — утечка подписки приведёт к вызову обработчика после размонтирования компонента.
Запрос разрешений: ответственность JS-слоя
Нативный модуль не должен самостоятельно запрашивать runtime-разрешения — это ответственность JS-слоя через react-native-permissions или PermissionsAndroid. Модуль только проверяет наличие и возвращает ошибку. Например, если разрешение BLUETOOTH_SCAN не выдано, модуль вернёт ошибку PERMISSION_DENIED.
Использование Codegen для TurboModule
При использовании TurboModule требуется создать TypeScript-спецификацию и запустить Codegen. Это автоматически генерирует Kotlin-интерфейсы и JNI-код. Пример:
export interface Spec extends TurboModule { readonly getConstants: () => { [key: string]: any }; isBluetoothEnabled(): Promise<boolean>; startScan(): Promise<void>; } Codegen сокращает время реализации на 30% и исключает ошибки ручного связывания.
Тестирование модуля
Нативный модуль тестируется на уровне Kotlin (JUnit + Mockk) и на уровне интеграции через Detox. Например, проверка возврата false при отсутствии Bluetooth:
@Test fun `isBluetoothEnabled returns false when adapter is null`() { val context = mockk<ReactApplicationContext>() every { context.getSystemService(Context.BLUETOOTH_SERVICE) } returns mockk<BluetoothManager> { every { adapter } returns null } val module = BluetoothModule(context) val promise = mockk<Promise>(relaxed = true) module.isBluetoothEnabled(promise) verify { promise.resolve(false) } } Также важно тестировать обработку ошибок и жизненный цикл. Благодаря модульным тестам клиенты экономят до 30% бюджета на отладке.
Типичные ошибки при разработке
- Утечка контекста: не храните ссылку на
ReactApplicationContextв статическом поле — используйтеWeakReference. - Игнорирование ProGuard: добавьте правила
-keepдля вашего модуля, иначе продакшн-сборка упадёт сClassNotFoundException. - Смешивание архитектур: не используйте Bridge-методы в TurboModule-проекте — нарушится подпись.
Что входит в разработку нативного модуля
| Компонент | Описание |
|---|---|
| Спецификация API | TypeScript интерфейс для Codegen (если TurboModule) или объявление методов для Bridge |
| Нативная реализация | Класс модуля на Kotlin с обработкой всех кейсов |
| Юнит-тесты | Тестирование модуля на Kotlin с mock зависимостей |
| Интеграция в проект | Настройка Package, разрешений и Gradle |
| Документация | API, примеры, обработка ошибок |
| Поддержка после сдачи | 30 дней на исправление багов |
Процесс работы
- Анализ требований — изучаем Android API, согласуем сигнатуры методов.
- Проектирование спецификации — создаём TypeScript интерфейс или JS-прокси.
- Реализация нативного кода — пишем модуль на Kotlin, обрабатываем edge cases.
- Тестирование — юнит-тесты + интеграция в проект-пример.
- Интеграция и документирование — подключаем к вашему приложению, обновляем документацию.
- Передача — исходники, демо-проект, инструкция по сборке.
Сроки: базовый модуль с 3-5 методами — 2-4 дня; модуль с событиями и тестами — от недели. Стоимость рассчитывается индивидуально. Закажите консультацию — получите точную оценку вашего проекта и бесплатный анализ требований. Свяжитесь с нами в течение рабочего дня.







