Розробка 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 дні; модуль з подіями та тестами — від тижня. Вартість розраховується індивідуально. Замовте консультацію — отримайте точну оцінку вашого проекту та безкоштовний аналіз вимог. Зв'яжіться з нами протягом робочого дня.







