Нативные модули Android¶
Native Module и Native Components — это наши стабильные технологии, используемые в унаследованной архитектуре. Они будут устаревшими в будущем, когда новая архитектура станет стабильной. Новая архитектура использует Turbo Native Module и Fabric Native Components для достижения аналогичных результатов.
Добро пожаловать в раздел "Нативные модули для Android". Пожалуйста, начните с чтения Native Modules Intro, чтобы узнать, что такое нативные модули.
Создание нативного модуля календаря¶
В следующем руководстве вы создадите нативный модуль CalendarModule
, который позволит вам получить доступ к API календаря Android из JavaScript. В конце вы сможете вызывать CalendarModule.createCalendarEvent('Dinner Party', 'My House');
из JavaScript, вызывая метод Java/Kotlin, который создает событие календаря.
Установка¶
Чтобы начать работу, откройте проект Android внутри вашего приложения React Native в Android Studio. Вы можете найти свой проект Android здесь в приложении React Native:
Мы рекомендуем использовать Android Studio для написания нативного кода. Android Studio — это IDE, созданная для разработки Android, и ее использование поможет вам быстро решить мелкие проблемы, такие как синтаксические ошибки кода.
Мы также рекомендуем включить Gradle Daemon, чтобы ускорить сборку по мере итерации кода на Java/Kotlin.
Создание файла пользовательского нативного модуля¶
Первым шагом будет создание Java/Kotlin файла (CalendarModule.java
или CalendarModule.kt
) в папке android/app/src/main/java/com/your-app-name/
(папка одинакова для Kotlin и Java). Этот Java/Kotlin файл будет содержать ваш нативный модуль Java/Kotlin класса.
Затем добавьте следующее содержание:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
1 2 3 4 5 6 7 8 |
|
Как вы видите, ваш класс CalendarModule
расширяет класс ReactContextBaseJavaModule
. Для Android нативные модули Java/Kotlin пишутся как классы, которые расширяют ReactContextBaseJavaModule
и реализуют функциональность, требуемую JavaScript.
Стоит отметить, что технически классы Java/Kotlin должны расширять класс BaseJavaModule
или реализовывать интерфейс NativeModule
, чтобы считаться нативным модулем в React Native.
Однако мы рекомендуем использовать ReactContextBaseJavaModule
, как показано выше. ReactContextBaseJavaModule
предоставляет доступ к ReactApplicationContext
(RAC), что полезно для нативных модулей, которым необходимо подключаться к методам жизненного цикла активности. Использование ReactContextBaseJavaModule
также упростит задачу обеспечения типобезопасности вашего нативного модуля в будущем. Для обеспечения безопасности типов нативных модулей, которая появится в будущих релизах, React Native просматривает спецификацию JavaScript каждого нативного модуля и генерирует абстрактный базовый класс, который расширяет ReactContextBaseJavaModule
.
Имя модуля¶
Все нативные модули Java/Kotlin в Android должны реализовывать метод getName()
. Этот метод возвращает строку, которая представляет собой имя нативного модуля. Затем к нативному модулю можно обратиться в JavaScript, используя его имя. Например, в приведенном ниже фрагменте кода getName()
возвращает "CalendarModule"
.
1 2 3 4 5 |
|
1 2 |
|
Затем к нативному модулю можно получить доступ в JS следующим образом:
1 |
|
Экспорт нативного метода в JavaScript¶
Далее вам нужно будет добавить метод в ваш нативный модуль, который будет создавать события календаря и может быть вызван в JavaScript. Все методы нативного модуля, предназначенные для вызова из JavaScript, должны быть аннотированы @ReactMethod
.
Создайте метод createCalendarEvent()
для CalendarModule
, который может быть вызван в JS через CalendarModule.createCalendarEvent()
. На данный момент метод принимает имя и местоположение в виде строк. Варианты типов аргументов будут рассмотрены в ближайшее время.
1 2 3 |
|
1 |
|
Добавьте отладочный журнал в метод, чтобы подтвердить, что он был вызван, когда вы вызываете его из своего приложения. Ниже приведен пример того, как можно импортировать и использовать класс Log из пакета Android util:
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 |
|
Как только вы завершите реализацию нативного модуля и подключите его в JavaScript, вы можете выполнить эти шаги для просмотра журналов вашего приложения.
Синхронные методы¶
Вы можете передать isBlockingSynchronousMethod = true
нативному методу, чтобы пометить его как синхронный метод.
1 |
|
1 |
|
В настоящее время мы не рекомендуем этого делать, поскольку синхронный вызов методов может привести к значительным потерям производительности и внести ошибки, связанные с потоками, в ваши родные модули. Кроме того, обратите внимание, что если вы решите включить isBlockingSynchronousMethod
, ваше приложение больше не сможет использовать отладчик Google Chrome. Это связано с тем, что синхронные методы требуют, чтобы JS VM делила память с приложением. Для отладчика Google Chrome, React Native работает внутри JS VM в Google Chrome и асинхронно взаимодействует с мобильными устройствами через WebSockets.
Регистрация модуля (специфика Android)¶
После того как нативный модуль написан, его необходимо зарегистрировать в React Native. Для этого необходимо добавить нативный модуль в ReactPackage
и зарегистрировать ReactPackage
в React Native. Во время инициализации React Native перебирает все пакеты и для каждого ReactPackage
регистрирует каждый нативный модуль.
React Native вызывает метод createNativeModules()
на ReactPackage
, чтобы получить список нативных модулей для регистрации. Для Android, если модуль не инстанцирован и не возвращен в createNativeModules, он не будет доступен из JavaScript.
Чтобы добавить ваш нативный модуль в ReactPackage
, сначала создайте новый Java/Kotlin класс с именем (MyAppPackage.java
или MyAppPackage.kt
), который реализует ReactPackage
в папке android/app/src/main/java/com/your-app-name/
:
Затем добавьте следующее содержимое:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Этот файл импортирует созданный вами нативный модуль CalendarModule
. Затем он инстанцирует CalendarModule
в функции createNativeModules()
и возвращает его в виде списка NativeModules
для регистрации. Если в дальнейшем вы будете добавлять дополнительные нативные модули, вы также можете инстанцировать их и добавить в возвращаемый список.
Стоит отметить, что такой способ регистрации нативных модулей инициализирует все нативные модули при запуске приложения, что увеличивает время запуска приложения. В качестве альтернативы можно использовать TurboReactPackage. Вместо
createNativeModules
, который возвращает список инстанцированных объектов нативных модулей, TurboReactPackage реализует методgetModule(String name, ReactApplicationContext rac)
, который создает объект нативного модуля, когда это необходимо. TurboReactPackage на данный момент реализован немного сложнее. Помимо реализации методаgetModule()
, необходимо реализовать методgetReactModuleInfoProvider()
, который возвращает список всех нативных модулей, которые пакет может инстанцировать, вместе с функцией, которая их инстанцирует, пример здесь. Опять же, использование TurboReactPackage позволит вашему приложению иметь более быстрое время запуска, но в настоящее время он немного громоздок в написании. Поэтому будьте осторожны, если решите использовать TurboReactPackages.
Чтобы зарегистрировать пакет CalendarModule
, необходимо добавить MyAppPackage
в список пакетов, возвращаемых методом getPackages()
от ReactNativeHost. Откройте файл MainApplication.java
или MainApplication.kt
, который можно найти по следующему пути: android/app/src/main/java/com/your-app-name/
.
Найдите метод ReactNativeHost getPackages()
и добавьте свой пакет в список пакетов, который возвращает getPackages()
:
1 2 3 4 5 6 7 8 |
|
1 2 3 4 5 6 |
|
Теперь вы успешно зарегистрировали свой нативный модуль для Android!
Протестируйте то, что вы создали¶
На данном этапе вы создали базовые подмостки для вашего нативного модуля в Android. Проверьте это, обратившись к родному модулю и вызвав его экспортированный метод в JavaScript.
Найдите место в вашем приложении, где вы хотите добавить вызов метода createCalendarEvent()
родного модуля. Ниже приведен пример компонента NewModuleButton
, который вы можете добавить в свое приложение. Вы можете вызвать нативный модуль внутри функции NewModuleButton
onPress()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Чтобы получить доступ к нативному модулю из JavaScript, сначала нужно импортировать NativeModules
из React Native:
1 |
|
Затем вы можете получить доступ к нативному модулю CalendarModule
из NativeModules
.
1 |
|
Теперь, когда у вас есть нативный модуль CalendarModule, вы можете вызвать нативный метод createCalendarEvent()
. Ниже он добавлен в метод onPress()
в NewModuleButton
:
1 2 3 4 5 6 |
|
Последний шаг — пересобрать приложение React Native, чтобы у вас был доступен самый свежий нативный код (с вашим новым нативным модулем!). В командной строке, где находится приложение react native, выполните следующее:
1 |
|
Создание по мере итерации¶
По мере того, как вы будете работать по этим руководствам и итерационно дорабатывать свой родной модуль, вам нужно будет перестроить ваше приложение, чтобы получить доступ к последним изменениям из JavaScript. Это связано с тем, что код, который вы пишете, находится в нативной части вашего приложения. В то время как metro bundler в React Native может следить за изменениями в JavaScript и перестраивать его на лету, он не будет делать этого для нативного кода. Поэтому, если вы хотите протестировать последние изменения в нативном коде, вам нужно перестроиться с помощью команды npx react-native run-android
.
Recap✨¶
Теперь вы должны иметь возможность вызвать метод createCalendarEvent()
на вашем нативном модуле в приложении. В нашем примере это происходит при нажатии кнопки NewModuleButton
. Вы можете подтвердить это, просмотрев журнал, который вы установили в методе createCalendarEvent()
. Вы можете выполнить эти шаги для просмотра журналов ADB в вашем приложении. Вы сможете найти сообщение Log.d
(в нашем примере "Create event called with name: testName and location: testLocation") и увидеть, что ваше сообщение записывается в журнал каждый раз, когда вы вызываете метод родного модуля.
На данном этапе вы создали нативный модуль Android и вызвали его нативный метод из JavaScript в своем приложении React Native. Вы можете прочитать дальше, чтобы узнать больше о таких вещах, как типы аргументов, доступных для метода нативного модуля, и как настроить обратные вызовы и обещания.
За пределами нативного модуля календаря¶
Улучшенный экспорт нативного модуля¶
Импорт нативного модуля путем извлечения его из NativeModules
, как описано выше, немного неудобен.
Чтобы избавить потребителей вашего нативного модуля от необходимости делать это каждый раз, когда они хотят получить доступ к вашему нативному модулю, вы можете создать JavaScript-обертку для модуля. Создайте новый файл JavaScript с именем CalendarModule.js
со следующим содержимым:
1 2 3 4 5 6 7 8 9 10 |
|
Этот файл JavaScript также становится хорошим местом для добавления любой функциональности на стороне JavaScript. Например, если вы используете систему типов, такую как TypeScript, вы можете добавить сюда аннотации типов для вашего нативного модуля. Хотя React Native пока не поддерживает безопасность типов Native to JS, весь ваш JS-код будет безопасен для типов. Это также облегчит вам переход на безопасные для типов нативные модули в будущем. Ниже приведен пример добавления безопасности типов в модуль CalendarModule:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
В других файлах JavaScript вы можете обратиться к родному модулю и вызвать его метод следующим образом:
1 2 |
|
Это предполагает, что место, куда вы импортируете CalendarModule
, находится в той же иерархии, что и CalendarModule.js
. Пожалуйста, обновите относительный импорт при необходимости.
Типы аргументов¶
Когда метод нативного модуля вызывается на JavaScript, React Native преобразует аргументы из JS-объектов в их аналоги из Java/Kotlin. Так, например, если ваш метод нативного модуля Java принимает двойку, в JS вам нужно вызвать метод с числом. React Native выполнит преобразование за вас. Ниже приведен список типов аргументов, поддерживаемых методами нативного модуля, и их JavaScript-эквивалентов.
Java | Kotlin | JavaScript |
---|---|---|
Boolean | Boolean | ?boolean |
boolean | boolean | |
Double | Double | ?number |
double | number | |
String | String | string |
Callback | Callback | Function |
ReadableMap | ReadableMap | Object |
ReadableArray | ReadableArray | Array |
Следующие типы поддерживаются в настоящее время, но не будут поддерживаться в TurboModules. Пожалуйста, избегайте их использования:
- Integer Java/Kotlin → ?number
- Float Java/Kotlin → ?число
- int Java → число
- float Java → число
Для типов аргументов, не перечисленных выше, вам придется самостоятельно выполнять преобразование. Например, в Android преобразование Date
не поддерживается из коробки. Вы можете самостоятельно выполнить преобразование к типу Date
в родном методе следующим образом:
1 2 3 4 5 6 |
|
1 2 3 4 5 6 7 8 |
|
Экспорт констант¶
Родной модуль может экспортировать константы, реализуя родной метод getConstants()
, который доступен в JS. Ниже вы реализуете getConstants()
и вернете Map, содержащий константу DEFAULT_EVENT_NAME
, к которой можно получить доступ в JavaScript:
1 2 3 4 5 6 |
|
1 2 |
|
Затем к константе можно получить доступ, вызвав getConstants
на родном модуле в JS:
1 2 3 4 |
|
Технически можно получить доступ к константам, экспортируемым в getConstants()
непосредственно из объекта родного модуля. Это больше не будет поддерживаться в TurboModules, поэтому мы призываем сообщество перейти на описанный выше подход, чтобы избежать необходимой миграции в будущем.
В настоящее время константы экспортируются только во время инициализации, поэтому если вы измените значения getConstants во время выполнения, это не повлияет на среду JavaScript. Это изменится с появлением Turbomodules. С Turbomodules, getConstants()
станет обычным методом нативного модуля, и каждый вызов будет бить по нативной стороне.
Обратные вызовы¶
Нативные модули также поддерживают уникальный вид аргумента: обратный вызов. Обратные вызовы используются для передачи данных из Java/Kotlin в JavaScript для асинхронных методов. Они также могут быть использованы для асинхронного выполнения JavaScript с нативной стороны.
Чтобы создать метод нативного модуля с обратным вызовом, сначала импортируйте интерфейс Callback
, а затем добавьте новый параметр в метод нативного модуля типа Callback
. Есть несколько нюансов с аргументами обратного вызова, которые вскоре будут устранены в TurboModules. Во-первых, вы можете иметь только два обратных вызова в аргументах функции — successCallback и failureCallback. Кроме того, последний аргумент вызова метода нативного модуля, если это функция, рассматривается как successCallback, а предпоследний аргумент вызова метода нативного модуля, если это функция, рассматривается как failure Callback.
1 2 3 4 5 |
|
1 2 3 |
|
Вы можете вызвать обратный вызов в своем методе Java/Kotlin, предоставляя любые данные, которые вы хотите передать JavaScript. Обратите внимание, что вы можете передавать только сериализуемые данные из нативного кода в JavaScript. Если вам нужно передать обратно нативный объект, вы можете использовать WriteableMaps
, если вам нужно использовать коллекцию, используйте WritableArrays
. Также важно отметить, что обратный вызов не вызывается сразу после завершения нативной функции. Ниже в обратный вызов передается ID события, созданного в предыдущем вызове.
1 2 3 4 5 |
|
1 2 3 4 5 |
|
Затем к этому методу можно получить доступ в JavaScript, используя:
1 2 3 4 5 6 7 8 9 10 11 |
|
Еще одна важная деталь, которую следует отметить, заключается в том, что метод родного модуля может вызывать только один обратный вызов, один раз. Это означает, что вы можете вызвать либо обратный вызов успеха, либо обратный вызов неудачи, но не оба, и каждый обратный вызов может быть вызван не более одного раза. Родной модуль может, однако, сохранить обратный вызов и вызвать его позже.
Существует два подхода к обработке ошибок с помощью обратных вызовов. Первый — следовать соглашению Node и рассматривать первый аргумент, переданный обратному вызову, как объект ошибки.
1 2 3 4 5 |
|
1 2 3 4 5 |
|
В JavaScript вы можете проверить первый аргумент, чтобы узнать, была ли передана ошибка:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Другой вариант — использовать обратный вызов onSuccess
и onFailure
:
1 2 3 4 5 6 7 8 |
|
1 2 3 4 5 6 7 |
|
Затем в JavaScript вы можете добавить отдельный обратный вызов для ответов об ошибке и успехе:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Промисы¶
Нативные модули также могут выполнять Promise, что может упростить ваш JavaScript, особенно при использовании синтаксиса async/await ES2016. Когда последним параметром метода нативного модуля Java/Kotlin является Promise, его соответствующий JS-метод вернет JS-объект Promise.
Рефакторинг приведенного выше кода для использования обещания вместо обратных вызовов выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 |
|
Подобно обратным вызовам, метод родного модуля может либо отклонить, либо разрешить обещание (но не оба) и может сделать это не более одного раза. Это означает, что вы можете вызвать либо обратный вызов успеха, либо обратный вызов отказа, но не оба, и каждый обратный вызов может быть вызван не более одного раза. Родной модуль может, однако, сохранить обратный вызов и вызвать его позже.
JavaScript-аналог этого метода возвращает Promise
. Это означает, что вы можете использовать ключевое слово await
в асинхронной функции для вызова этого метода и ожидания его результата:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Метод reject принимает различные комбинации следующих аргументов:
1 |
|
1 |
|
Более подробную информацию вы можете найти в интерфейсе Promise.java
здесь. Если userInfo
не предоставлен, ReactNative установит его в null. Для остальных параметров React Native будет использовать значение по умолчанию. Аргумент message
предоставляет сообщение об ошибке
, отображаемое в верхней части стека вызовов ошибок. Ниже приведен пример сообщения об ошибке, показанного в JavaScript после следующего вызова reject на Java/Kotlin.
Вызов отказа на Java/Kotlin:
1 |
|
1 |
|
Сообщение об ошибке в React Native App при отклонении обещания:
Отправка событий на JavaScript¶
Нативные модули могут сигнализировать JavaScript о событиях, не вызывая их напрямую. Например, вы можете захотеть передать в JavaScript напоминание о том, что скоро произойдет событие календаря из родного календарного приложения Android. Самый простой способ сделать это — использовать RCTDeviceEventEmitter
, который можно получить из ReactContext
, как в приведенном ниже фрагменте кода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
Затем модули JavaScript могут зарегистрироваться для получения событий путем addListener
на классе NativeEventEmitter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Получение результатов деятельности от startActivityForResult¶
Вам нужно прослушать onActivityResult
, если вы хотите получить результаты деятельности, которую вы начали с помощью startActivityForResult
. Для этого вы должны расширить BaseActivityEventListener
или реализовать ActivityEventListener
. Первый вариант предпочтительнее, так как он более устойчив к изменениям API. Затем необходимо зарегистрировать слушателя в конструкторе модуля следующим образом:
1 |
|
1 |
|
Теперь вы можете прослушивать onActivityResult
, реализовав следующий метод:
1 2 3 4 5 6 7 8 |
|
1 2 3 4 5 6 7 8 |
|
Чтобы продемонстрировать это, давайте реализуем базовый подборщик изображений. Программа выбора изображений будет предоставлять JavaScript метод pickImage
, который при вызове будет возвращать путь к изображению.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
|
Прослушивание событий жизненного цикла¶
Прослушивание событий жизненного цикла активности, таких как onResume
, onPause
и т.д., очень похоже на то, как был реализован ActivityEventListener
. Модуль должен реализовать LifecycleEventListener
. Затем необходимо зарегистрировать слушателя в конструкторе модуля следующим образом:
1 |
|
1 |
|
Теперь вы можете прослушивать события LifeCycle активности, реализуя следующие методы:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
Threading¶
На сегодняшний день в Android все асинхронные методы нативных модулей выполняются в одном потоке. Нативные модули не должны иметь никаких предположений о том, в каком потоке они вызываются, поскольку текущее назначение может измениться в будущем. Если требуется блокирующий вызов, то тяжелая работа должна быть передана внутреннему управляемому рабочему потоку, а все обратные вызовы распределяются оттуда.