Reagir a integração nativa e C ++ para iOS e Android

Recentemente, fui oferecido para trabalhar em um projeto interessante. Foi necessário desenvolver um aplicativo móvel para a inicialização americana no iOS e Android usando o React Native. O principal recurso e fator técnico que decidiu inequivocamente minha participação no projeto foi a tarefa de integrar uma biblioteca escrita em C ++. Para mim, isso poderia ser uma nova experiência e um novo teste profissional.

Por que foi necessário integrar a biblioteca C ++


Esse aplicativo era necessário para autenticação de dois fatores usando os protocolos FIDO UAF e U2F, usando dados biométricos, como Face ID e Touch ID, e tecnologias semelhantes para a plataforma Android. O cliente para autenticação já estava pronto. Era uma biblioteca escrita em C ++ e usada por alguns outros clientes, além do aplicativo móvel. Portanto, fui obrigado a integrá-lo de maneira semelhante a um aplicativo móvel no React Native.

Como eu fiz isso


Existe uma abordagem para integrar o C ++ ao aplicativo React Native do Facebook. No entanto, o problema é que ele funciona apenas para a plataforma iOS e não está claro o que fazer com o Android nesse caso. Eu queria resolver o problema para duas plataformas ao mesmo tempo.

Uma bifurcação da ferramenta Dropbox Djinni que permite gerar declarações de tipo de plataforma cruzada. Essencialmente, é um aplicativo móvel simples do React Native com uma conexão personalizada ao Djinni. Eu tomei isso como base.

Por conveniência, o código do aplicativo é dividido em dois repositórios git. O primeiro armazena o código-fonte do aplicativo React Native e o segundo contém Djinni e as dependências necessárias.

Passos adicionais


Primeiro, você precisa declarar uma interface para a interação do C ++ e do código React Native. No Djinni, isso é feito usando arquivos .idl. Abra o arquivo react-native-cpp-support / idl / main.Djinni no projeto e familiarize-se com sua estrutura.

Para nossa conveniência, alguns tipos de dados JavaScript e ligações para eles já foram declarados no projeto. Assim, podemos trabalhar com os tipos String, Array, Map, Promise e outros sem nenhuma descrição adicional.

No exemplo, esse arquivo se parece com o seguinte:

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); } 

Depois de fazer alterações no arquivo de interfaces, é necessário regenerar as interfaces Java / Objective-C / C ++. Isso é fácil, executando o script generate_wrappers.sh na pasta react-native-cpp-support / idl / . Este script irá coletar todos os anúncios do nosso arquivo idl e criar as interfaces apropriadas para eles, é muito conveniente.

No exemplo, existem dois arquivos C ++ que nos interessam. O primeiro contém uma descrição e a segunda implementação de métodos C ++ simples:

react-native-cpp / cpp / DemoModuleImpl.hpp
react-native-cpp / cpp / DemoModuleImpl.cpp

Considere o código de um dos métodos como um exemplo:

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

Observe que o resultado é retornado não usando o retorno de palavra-chave , mas usando o objeto JavaScriptPromise passado pelo último parâmetro, conforme descrito no arquivo idl .

Agora ficou claro como descrever o código necessário em C ++. Mas como interagir com isso em um aplicativo React Native? Para entender, basta abrir o arquivo na pasta react-native-cpp / index.js , na qual todas as funções descritas no exemplo são chamadas.

A função do nosso exemplo é chamada em JavaScript da seguinte maneira:

 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)); } 

Agora está claro como as funções de teste funcionam ao lado de C ++ e JavaScript. Da mesma forma, você pode adicionar o código de qualquer outra função. Além disso, considerarei como os projetos Android e iOS funcionam em conjunto com o C ++.

Reagir nativo e C ++ para Android


Para a interação do Android e C ++, você deve instalar o NDK. Instruções detalhadas sobre como fazer isso estão disponíveis em developer.android.com/ndk/guides
Em seguida, dentro do arquivo react-native-cpp / android / app / build.gradle, você precisará adicionar as seguintes configurações:

 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" } } ... } 

Acabamos de configurar o gradle para construir o aplicativo para as arquiteturas usadas e adicionamos os sinalizadores de construção necessários para o cmake, especificamos o arquivo CMAkeLists , que descreveremos no futuro, e também adicionamos as classes java do Djinni que usaremos.
A próxima etapa na configuração de um projeto Android é a descrição do arquivo CMakeLists.txt . No formato final, ele pode ser visualizado no caminho 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} ) 

Aqui, indicamos os caminhos relativos à biblioteca de suporte, adicionamos diretórios com o código C ++ e JNI necessário.

Outro passo importante é adicionar o DjinniModulesPackage ao nosso projeto. Para fazer isso, no arquivo 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() ); } ... } 

O último detalhe importante é a descrição da classe DjinniModulesPackage que acabamos de usar na classe principal do nosso aplicativo. Ele está localizado no caminho react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java e contém o seguinte 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 maior interesse na classe acima é a linha System.loadLibrary ("native-lib"); Graças ao qual carregamos a biblioteca com nosso código nativo e código Djinni no aplicativo Android.

Para entender como isso funciona, aconselho que você se familiarize com o código jni da pasta , que é um wrapper jni para trabalhar com a funcionalidade do nosso módulo, e sua interface é descrita no arquivo idl.

Como resultado, se o ambiente de desenvolvimento Android e o React Native estiverem configurados, você poderá criar e executar o projeto React Native no Android. Para fazer isso, execute dois comandos no terminal:

instalação npm
npm run android

Viva! Nosso projeto está funcionando!

E vemos a seguinte imagem na tela do emulador do Android (clicável):



Agora vamos ver como o iOS e o React Native funcionam com C ++.

Reagir nativo e C ++ para iOS


Abra o projeto react-native-cpp no ​​Xcode.

Primeiro, adicione links ao código usado no projeto Objective-C e C ++ da biblioteca de suporte. Para fazer isso, transfira o conteúdo das pastas react -native-cpp-support / support-lib / objc / e react-native-cpp-support / support-lib / cpp / para o projeto Xcode . Como resultado, as pastas com a biblioteca de suporte ao código serão exibidas na árvore da estrutura do projeto (as imagens são clicáveis):





Assim, adicionamos descrições de tipos JavaScript da biblioteca de suporte ao projeto.

A próxima etapa é adicionar os wrappers objetivo-c gerados para o nosso módulo C ++ de teste. Precisamos transferir o código da pasta react-native-cpp / ios / rncpp / Generated / para o projeto.

Resta adicionar o código C ++ do nosso módulo, para o qual transferimos o código das pastas react-native-cpp / cpp / e react-native-cpp / cpp / gen / para o projeto.

Como resultado, a árvore da estrutura do projeto terá a seguinte aparência (a imagem é clicável):



Você precisa garantir que os arquivos adicionados apareçam na lista Fontes de compilação, na guia Fases de construção.



(a imagem é clicável)

A última etapa é alterar o código do arquivo AppDelegate.m para iniciar a inicialização do módulo Djinni quando o aplicativo for iniciado. E para isso, você precisa alterar as seguintes linhas 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]; ... } 

Agora inicie nosso aplicativo no iOS. (a imagem é clicável)




A aplicação está funcionando!

Adicionando uma biblioteca C ++ ao nosso projeto.


Por exemplo, usamos a popular biblioteca OpenSSL.

E vamos começar com o Android.

Clonamos o repositório com a biblioteca OpenSSL já construída para Android.

Inclua a biblioteca OpenSSL no arquivo 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) 

Em seguida, adicionamos ao nosso módulo C ++ um código de função simples que retorna a versão da biblioteca OpenSSL.

No arquivo react-native-cpp / cpp / DemoModuleImpl.hpp, adicione:

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

No arquivo react-native-cpp / cpp / DemoModuleImpl.cpp, adicione:

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

Resta descrever a interface da nova função no arquivo idl ` react -native-cpp-support / idl / main.djinni` :

  getOpenSSLVersion(promise: JavascriptPromise); 

Chamamos o script `generate_wrappers.sh` da pasta` react-native-cpp-support / idl /` .

Em JavaScript, chamamos a função recém-criada:

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

Tudo está pronto para o Android.
Vamos para o iOS.

Clonamos o repositório com a versão montada da biblioteca OpenSSL para iOS.

Abra o projeto iOS no Xcode e, nas configurações da guia Configurações de compilação, adicione o caminho à biblioteca openssl no campo Outros sinalizadores C (um exemplo do caminho no meu computador está abaixo):
-I / Usuários / andreysaleba / projects / pré-construído-openssl / dist / openssl-1.0.2d-ios / include

No campo Outros sinalizadores de vinculador, adicione as seguintes linhas:

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

Está tudo pronto. Biblioteca OpenSSL adicionada para ambas as plataformas.

Obrigado por assistir!

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


All Articles