适用于iOS和Android的React Native和C ++集成

最近,有人提议我从事一个有趣的项目。 需要使用React Native在iOS和Android上为美国创业公司开发移动应用程序。 明确决定我参与该项目的关键技术特征和因素是集成用C ++编写的库的任务。 对我来说,这可能是一种新的体验和一种新的专业测试。

为什么需要集成C ++库


对于使用FIDO UAF和U2F协议,使用生物识别数据(例如Face ID和Touch ID)以及适用于Android平台的类似技术的两因素身份验证,此应用程序是必需的。 身份验证客户端已准备就绪。 它是一个用C ++编写的库,除移动应用程序外还被其他一些客户端使用。 因此,我被要求以类似的方式将其集成到React Native上的移动应用程序中。

我是怎么做到的


有一种方法可以将C ++集成到Facebook的React Native应用中。 但是,问题在于它仅适用于iOS平台,尚不清楚在这种情况下如何处理Android。 我想一次解决两个平台的问题。

Dropbox Djinni工具的一个分支,它允许您生成跨平台的类型声明。 本质上,它是一个简单的React Native移动应用程序,具有与Djinni的自定义连接。 我以此为基础。

为了方便起见,将应用程序代码分为两个git存储库。 第一个存储React Native应用程序的源代码, 第二个包含Djinni和必要的依赖项。

进一步的步骤


首先,您需要声明一个接口,用于C ++和React Native代码的交互。 在Djinni中,这是使用.idl文件完成的。 在项目中打开文件react-native-cpp-support / idl / main.Djinni并熟悉其结构。

为方便起见,项目中已经声明了一些JavaScript数据类型及其绑定。 因此,我们可以使用String,Array,Map,Promise等类型,而无需任何其他描述。

在示例中,该文件如下所示:

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

在对interfaces文件进行更改之后,您需要重新生成Java / Objective-C / C ++接口。 通过从react-native-cpp-support / idl /文件夹运行generate_wrappers.sh脚本,可以轻松做到这一点。 该脚本将从idl文件中收集所有广告,并为它们创建适当的界面,这非常方便。

在示例中,有两个我们感兴趣的C ++文件。 第一个包含说明,第二个包含简单的C ++方法的实现:

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

以其中一种方法的代码为例:

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

请注意,返回结果不是使用关键字return ,而是使用最后一个参数传递的JavaScriptPromise对象,如idl文件中所述。

现在,很清楚如何用C ++描述必要的代码。 但是如何在React Native应用程序中与此交互? 要理解,只需从react-native-cpp / index.js文件夹中打开文件,该示例中描述的所有功能都将在其中被调用。

我们的示例中的函数在JavaScript中的调用如下:

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

现在很清楚,测试功能如何在C ++和JavaScript方面发挥作用。 同样,您可以添加任何其他功能的代码。 此外,我将考虑Android和iOS项目如何与C ++一起工作。

用于Android的React Native和C ++


为了使Android和C ++交互,您必须安装NDK。 有关如何执行此操作的详细说明,请访问developer.android.com/ndk/guides
然后,在react-native-cpp / android / app / build.gradle文件中,您需要添加以下设置:

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

我们只是配置gradle来为所使用的体系结构构建应用程序,并为cmake添加了必要的构建标志,指定了我们将在以后描述的CMAkeLists文件,还添加了我们将使用的Djinni中的java类。
设置Android项目的下一步是描述CMakeLists.txt文件。 以完成的形式,可以沿着路径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} ) 

在这里,我们指出了支持库的相对路径,并添加了带有必要的C ++和JNI代码的目录。

另一个重要步骤是将DjinniModulesPackage添加到我们的项目中。 为此,请在react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java文件中指定:

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

最后一个重要的细节是我们刚刚在应用程序的主类中使用的DjinniModulesPackage类的描述。 它位于路径react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java中,并包含以下代码:

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

在上述类中,最感兴趣的是System.loadLibrary(“ native-lib”)行; 借助于此,我们将带有本机代码和Djinni代码的库加载到Android应用程序中。

为了理解它的工作原理,我建议您熟悉文件夹中的jni代码,该文件夹是用于使用模块功能的jni包装器,其接口在idl文件中进行了描述。

因此,如果配置了Android开发环境和React Native,则可以在Android上构建和运行React Native项目。 为此,请在终端中执行两个命令:

npm安装
npm运行android

万岁! 我们的项目正在运作!

而且,我们在Android模拟器的屏幕(可点击)上看到以下图片:



现在,让我们看看iOS和React Native如何与C ++一起工作。

用于iOS的React Native和C ++


在Xcode中打开react-native-cpp项目。

首先,从支持库添加指向Objective-C和C ++项目中使用的代码的链接。 为此,请将react-native-cpp-support / support-lib / objc /react-native-cpp-support / support-lib / cpp /文件夹的内容传输到Xcode项目。 结果,带有代码支持库的文件夹将显示在项目结构树中(图片可单击):





因此,我们从支持库向项目添加了JavaScript类型的描述。

下一步是为我们的测试C ++模块添加生成的Objective-C包装器。 我们需要将代码从react-native-cpp / ios / rncpp / Generated /文件夹传输到项目。

仍然需要添加模块的C ++代码,为此,我们将代码从react-native-cpp / cpp /react-native-cpp / cpp / gen /文件夹传输到项目中。

结果,项目结构树将如下所示(图片是可单击的):



您需要确保添加的文件出现在“构建阶段”选项卡内的“编译源”列表中。



(图片可点击)

最后一步是更改AppDelegate.m文件的代码,以在应用程序启动时开始初始化Djinni模块。 为此,您需要更改以下代码行:

 ... #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]; ... } 

现在在iOS上启动我们的应用程序。 (图片可点击)




该应用程序正在运行!

在我们的项目中添加一个C ++库。


例如,我们使用流行的OpenSSL库。

让我们从Android开始。

我们使用适用于Android的OpenSSL库克隆存储库。

在CMakeLists.txt文件中包含OpenSSL库:

 .... 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) 

然后,将简单的函数代码添加到C ++模块,该函数代码返回OpenSSL库的版本。

在文件react-native-cpp / cpp / DemoModuleImpl.hpp中添加:

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

在文件react-native-cpp / cpp / DemoModuleImpl.cpp中添加:

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

仍然需要在idl文件“ react-native-cpp-support / idl / main.djinni”中描述新功能的接口:

  getOpenSSLVersion(promise: JavascriptPromise); 

我们从文件夹react-native-cpp-support / idl /`中调用脚本`generate_wrappers.sh`

然后,在JavaScript中,我们调用新创建的函数:

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

一切就绪,可用于Android。
让我们继续iOS。

我们使用适用于iOS的OpenSSL库的组合版本克隆存储库。

在Xcode中打开iOS项目,然后在Build Settings选项卡的设置中,在Other C Flags字段中添加openssl库的路径(下面是我计算机上路径的示例):
-I /用户/ andreysaleba /项目/prebuilt-openssl/dist/openssl-1.0.2d-ios/include

在“其他链接器标志”字段中,添加以下行:

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

一切准备就绪。 为两个平台添加了OpenSSL库。

感谢收看!

Source: https://habr.com/ru/post/zh-CN439676/


All Articles