Integración React Native y C ++ para iOS y Android

Recientemente, me ofrecieron trabajar en un proyecto interesante. Se requería desarrollar una aplicación móvil para una startup estadounidense en plataformas iOS y Android usando React Native. La característica técnica clave y el factor que decidieron inequívocamente mi participación en el proyecto fue la tarea de integrar una biblioteca escrita en C ++. Para mí, esta podría ser una nueva experiencia y una nueva prueba profesional.

¿Por qué fue necesario integrar la biblioteca de C ++?


Esta aplicación era necesaria para la autenticación de dos factores utilizando los protocolos FIDO UAF y U2F, utilizando datos biométricos como Face ID y Touch ID, y tecnologías similares para la plataforma Android. El cliente para la autenticación ya estaba listo. Era una biblioteca escrita en C ++ y utilizada por algunos otros clientes además de la aplicación móvil. Por lo tanto, tuve que integrarlo de manera similar en una aplicación móvil en React Native.

Como lo hice


Existe un enfoque para integrar C ++ en la aplicación React Native de Facebook. Sin embargo, el problema es que solo funciona para la plataforma iOS, y no está claro qué hacer con Android en este caso. Quería resolver el problema para dos plataformas a la vez.

Una bifurcación de la herramienta Djinni de Dropbox que le permite generar declaraciones de tipo multiplataforma. Esencialmente, es una aplicación móvil simple React Native con una conexión personalizada a Djinni. Lo tomé como base.

Por conveniencia, el código de la aplicación se divide en dos repositorios git. El primero almacena el código fuente de la aplicación React Native, y el segundo contiene Djinni y las dependencias necesarias.

Pasos adicionales


Primero debe declarar una interfaz para la interacción de C ++ y React Native code. En Djinni, esto se hace usando archivos .idl. Abra el archivo react-native-cpp-support / idl / main.Djinni en el proyecto y familiarícese con su estructura.

Para nuestra conveniencia, algunos tipos de datos de JavaScript y enlaces para ellos ya se han declarado en el proyecto. Por lo tanto, podemos trabajar con los tipos String, Array, Map, Promise y otros sin ninguna descripción adicional.

En el ejemplo, este archivo se ve así:

DemoModule = interface +r { const EVENT_NAME: string = "DEMO_MODULE_EVENT"; const STRING_CONSTANT: string = "STRING"; const INT_CONSTANT: i32 = 13; const DOUBLE_CONSTANT: f64 = 13.123; const BOOL_CONSTANT: bool = false; testPromise(promise: JavascriptPromise); testCallback(callback: JavascriptCallback); testMap(map: JavascriptMap, promise: JavascriptPromise); testArray(array: JavascriptArray, callback: JavascriptCallback); testBool(value: bool, promise: JavascriptPromise); testPrimitives(i: i32, d: f64, callback: JavascriptCallback); testString(value: string, promise: JavascriptPromise); testEventWithArray(value: JavascriptArray); testEventWithMap(value: JavascriptMap); } 

Después de realizar cambios en el archivo de interfaces, debe regenerar las interfaces Java / Objective-C / C ++. Esto es fácil de hacer ejecutando el script generate_wrappers.sh desde la carpeta react-native-cpp-support / idl / . Este script recopilará todos los anuncios de nuestro archivo idl y creará las interfaces apropiadas para ellos, es muy conveniente.

En el ejemplo, hay dos archivos C ++ que nos interesan. El primero contiene una descripción, y la segunda implementación de métodos simples de C ++:

reaccionar-nativo-cpp / cpp / DemoModuleImpl.hpp
reaccionar-nativo-cpp / cpp / DemoModuleImpl.cpp

Considere el código de uno de los métodos como ejemplo:

 void DemoModuleImpl::testString(const std::string &value, const std::shared_ptr<::JavascriptPromise> &promise) { promise->resolveObject(JavascriptObject::fromString("Success!")); } 

Tenga en cuenta que el resultado se devuelve no usando la palabra clave return , sino usando el objeto JavaScriptPromise pasado por el último parámetro, como se describe en el archivo idl .

Ahora quedó claro cómo describir el código necesario en C ++. Pero, ¿cómo interactuar con esto en una aplicación React Native? Para comprenderlo, simplemente abra el archivo desde la carpeta react-native-cpp / index.js , donde se llaman todas las funciones descritas en el ejemplo.

La función de nuestro ejemplo se llama en JavaScript de la siguiente manera:

 import { NativeAppEventEmitter, NativeModules... } from 'react-native'; const DemoModule = NativeModules.DemoModule; .... async promiseTest() { this.appendLine("testPromise: " + await DemoModule.testPromise()); this.appendLine("testMap: " + JSON.stringify(await DemoModule.testMap({a: DemoModule.INT_CONSTANT, b: 2}))); this.appendLine("testBool: " + await DemoModule.testBool(DemoModule.BOOL_CONSTANT)); // our sample function this.appendLine("testString: " + await DemoModule.testString(DemoModule.STRING_CONSTANT)); } 

Ahora está claro cómo funcionan las funciones de prueba del lado de C ++ y JavaScript. Del mismo modo, puede agregar el código de cualquier otra función. Además, consideraré cómo funcionan los proyectos de Android e iOS junto con C ++.

React Native y C ++ para Android


Para la interacción de Android y C ++, debe instalar el NDK. Las instrucciones detalladas sobre cómo hacerlo están disponibles en developer.android.com/ndk/guides
Luego, dentro del archivo react-native-cpp / android / app / build.gradle, debe agregar la siguiente configuración:

 android { ... defaultConfig { ... ndk { abiFilters "armeabi-v7a", "x86" } externalNativeBuild { cmake { cppFlags "-std=c++14 -frtti -fexceptions" arguments "-DANDROID_TOOLCHAIN=clang", "-DANDROID_STL=c++_static" } } } externalNativeBuild { cmake { path "CMakeLists.txt" } } sourceSets { main { java.srcDirs 'src/main/java', '../../../react-native-cpp-support/support-lib/java' } } splits { abi { reset() enable enableSeparateBuildPerCPUArchitecture universalApk false // If true, also generate a universal APK include "armeabi-v7a", "x86" } } ... } 

Simplemente configuramos gradle para compilar la aplicación para las arquitecturas utilizadas y agregamos los indicadores de compilación necesarios para cmake, especificamos el archivo CMAkeLists , que describiremos en el futuro, y también agregamos las clases java de Djinni que usaremos.
El siguiente paso para configurar un proyecto de Android es describir el archivo CMakeLists.txt . En forma terminada, se puede ver a lo largo de la ruta react-native-cpp / android / app / CMakeLists.txt .

 cmake_minimum_required(VERSION 3.4.1) set( PROJECT_ROOT "${CMAKE_SOURCE_DIR}/../.." ) set( SUPPORT_LIB_ROOT "${PROJECT_ROOT}/../react-native-cpp-support/support-lib" ) file( GLOB JNI_CODE "src/main/cpp/*.cpp" "src/main/cpp/gen/*.cpp" ) file( GLOB PROJECT_CODE "${PROJECT_ROOT}/cpp/*.cpp" "${PROJECT_ROOT}/cpp/gen/*.cpp" ) file( GLOB PROJECT_HEADERS "${PROJECT_ROOT}/cpp/*.hpp" "${PROJECT_ROOT}/cpp/gen/*.hpp" ) file( GLOB DJINNI_CODE "${SUPPORT_LIB_ROOT}/cpp/*.cpp" "${SUPPORT_LIB_ROOT}/jni/*.cpp" ) file( GLOB DJINNI_HEADERS "${SUPPORT_LIB_ROOT}/cpp/*.hpp" "${SUPPORT_LIB_ROOT}/jni/*.hpp" ) include_directories( "${SUPPORT_LIB_ROOT}/cpp" "${SUPPORT_LIB_ROOT}/jni" "${PROJECT_ROOT}/cpp" "${PROJECT_ROOT}/cpp/gen" ) add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED ${JNI_CODE} ${DJINNI_CODE} ${DJINNI_HEADERS} ${PROJECT_CODE} ${PROJECT_HEADERS} ) 

Aquí indicamos las rutas relativas a la biblioteca de soporte, agregamos directorios con el código C ++ y JNI necesario.

Otro paso importante es agregar DjinniModulesPackage a nuestro proyecto. Para hacer esto, en el archivo react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java especificamos:

 ... import com.rncpp.jni.DjinniModulesPackage; ... public class MainApplication extends Application implements ReactApplication { ... @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new DjinniModulesPackage() ); } ... } 

El último detalle importante es la descripción de la clase DjinniModulesPackage que acabamos de utilizar en la clase principal de nuestra aplicación. Se encuentra en la ruta react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java y contiene el siguiente código:

 package com.rncpp.jni; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class DjinniModulesPackage implements ReactPackage { static { System.loadLibrary("native-lib"); } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new DemoModule(reactContext)); return modules; } } 

De mayor interés en la clase anterior es la línea System.loadLibrary ("native-lib"); Gracias a lo cual cargamos la biblioteca con nuestro código nativo y el código Djinni en la aplicación de Android.

Para entender cómo funciona esto, le aconsejo que se familiarice con el código jni de la carpeta , que es un contenedor jni para trabajar con la funcionalidad de nuestro módulo, y su interfaz se describe en el archivo idl.

Como resultado, si el entorno de desarrollo de Android y React Native están configurados, puede compilar y ejecutar el proyecto React Native en Android. Para hacer esto, ejecute dos comandos en la terminal:

npm install
npm ejecutar android

¡Hurra! Nuestro proyecto esta funcionando!

Y vemos la siguiente imagen en la pantalla del emulador de Android (se puede hacer clic):



Ahora veamos cómo funcionan iOS y React Native con C ++.

React Native y C ++ para iOS


Abra el proyecto react-native-cpp en Xcode.

Primero, agregue enlaces al código utilizado en el proyecto Objective-C y C ++ desde la biblioteca de soporte. Para hacer esto, transfiera el contenido de las carpetas react-native-cpp-support / support-lib / objc / y react-native-cpp-support / support-lib / cpp / al proyecto Xcode . Como resultado, las carpetas con la biblioteca de soporte de código se mostrarán en el árbol de estructura del proyecto (se puede hacer clic en las imágenes):





Por lo tanto, agregamos descripciones de tipos de JavaScript de la biblioteca de soporte al proyecto.

El siguiente paso es agregar los contenedores del objetivo-c generados para nuestro módulo de prueba C ++. Necesitamos transferir el código de la carpeta react-native-cpp / ios / rncpp / Generated / al proyecto.

Queda por agregar el código C ++ de nuestro módulo, para lo cual transferimos el código de las carpetas react-native-cpp / cpp / y react-native-cpp / cpp / gen / al proyecto.

Como resultado, el árbol de estructura del proyecto tendrá el siguiente aspecto (se puede hacer clic en la imagen):



Debe asegurarse de que los archivos agregados aparezcan en la lista de fuentes de compilación dentro de la pestaña Fases de compilación.



(se puede hacer clic en la imagen)

El último paso es cambiar el código del archivo AppDelegate.m para comenzar a inicializar el módulo Djinni cuando se inicia la aplicación. Y para esto necesita cambiar las siguientes líneas de código:

 ... #import "RCDjinniModulesInitializer.h" ... @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... id<RCTBridgeDelegate> moduleInitialiser = [[RCDjinniModulesInitializer alloc] initWithURL:jsCodeLocation]; RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:nil]; RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"rncpp" initialProperties: nil]; ... } 

Ahora inicie nuestra aplicación en iOS. (se puede hacer clic en la imagen)




¡La aplicación está funcionando!

Agregar una biblioteca C ++ a nuestro proyecto.


Por ejemplo, usamos la popular biblioteca OpenSSL.

Y comencemos con Android.

Clonamos el repositorio con la biblioteca OpenSSL ya construida para Android.

Incluya la biblioteca OpenSSL en el archivo CMakeLists.txt:

 .... SET(OPENSSL_ROOT_DIR /Users/andreysaleba/projects/OpenSSL-for-Android-Prebuilt/openssl-1.0.2) SET(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/${ANDROID_ABI}/lib") SET(OPENSSL_INCLUDE_DIR ${OPENSSL_ROOT_DIR}/include) SET(OPENSSL_LIBRARIES "ssl" "crypto") ... LINK_DIRECTORIES(${OPENSSL_LIBRARIES_DIR} ${ZLIB_LIBRARIES_DIR}) include_directories( "${SUPPORT_LIB_ROOT}/cpp" "${SUPPORT_LIB_ROOT}/jni" "${PROJECT_ROOT}/cpp" "${PROJECT_ROOT}/cpp/gen" "${OPENSSL_INCLUDE_DIR}" ) add_library(libssl STATIC IMPORTED) add_library(libcrypto STATIC IMPORTED) ... set_target_properties( libssl PROPERTIES IMPORTED_LOCATION ${OPENSSL_LIBRARIES_DIR}/libssl.a ) set_target_properties( libcrypto PROPERTIES IMPORTED_LOCATION ${OPENSSL_LIBRARIES_DIR}/libcrypto.a ) target_link_libraries(native-lib PRIVATE libssl libcrypto) 

Luego agregamos a nuestro módulo C ++ un código de función simple que devuelve la versión de la biblioteca OpenSSL.

En el archivo react-native-cpp / cpp / DemoModuleImpl.hpp agregue:

 void getOpenSSLVersion(const std::shared_ptr<::JavascriptPromise> & promise) override; 

En el archivo react-native-cpp / cpp / DemoModuleImpl.cpp agregue:

 #include <openssl/crypto.h> ... void DemoModuleImpl::getOpenSSLVersion(const std::shared_ptr<::JavascriptPromise> &promise) { promise->resolveString(SSLeay_version(1)); } 

Queda por describir la interfaz de la nueva función en el archivo idl `react-native-cpp-support / idl / main.djinni` :

  getOpenSSLVersion(promise: JavascriptPromise); 

Llamamos al script `generate_wrappers.sh` desde la carpeta` react-native-cpp-support / idl /` .

Luego, en JavaScript, llamamos a la función recién creada:

 async promiseTest() { ... this.appendLine("openSSL version: " + await DemoModule.getOpenSSLVersion()); } 

Todo está listo para Android.
Pasemos a iOS.

Clonamos el repositorio con la versión ensamblada de la biblioteca OpenSSL para iOS.

Abra el proyecto de iOS en Xcode y en la configuración de la pestaña Configuración de compilación agregue la ruta a la biblioteca openssl en el campo Otras banderas de C (a continuación se muestra un ejemplo de la ruta en mi computadora):
-I / Users / andreysaleba / projects / prebuilt-openssl / dist / openssl-1.0.2d-ios / include

En el campo Otros indicadores de vinculador, agregue las siguientes líneas:

 -L/Users/andreysaleba/projects/prebuilt-openssl/dist/openssl-1.0.2d-ios/lib -lcrypto -lssl 

Todo esta listo. Biblioteca OpenSSL agregada para ambas plataformas.

Gracias por mirar!

Source: https://habr.com/ru/post/439676/


All Articles