[译] React Native 与 iOS 和 Android 通信

avatar
@掘金

React Native 流行的最大原因之一是我们可以在 Native 语言和 JavaScript 代码之间建立桥梁。这意味着我们可以复用在 iOS 和 Android 中创建的所有可重用库。

要创建一个商业级的应用程序,您需要使用 Native Bridge。React Native 可以同时在 iOS 和 Android 上运行,但关于它如何跨平台的文章教程非常少。在本文中,我们将创建一个 Native Bridge,以便从 JavaScript 访问 Swift 和 Java 类。

文本是此系列的第一部分,第二部分可以在 Native Bridge of UI component 中找到 这里

代码可以在这里找到 -> github.com/nalwayaabhi…

创建一个 LightApp(手电筒)

为了更好地理解 Native Module,我们将使用 react-native CLI 创建一个简单的 LightApp 示例。

$ react-native init LightApp
$ cd LightApp

接下来,我们将在 Swift 和 Java 中创建一个 Bulb 类,稍后将在 React 组件中使用它。这是一个跨平台的示例,相同的 React 代码将同时适用于iOS和Android。

现在我们已经创建了项目的基本框架,接下来我们将本文分为两部分:

第一节 — 与原生 iOS 通信

第二节 — 与原生 Android 通信

第一节 — 与原生 iOS 通信

在本节中,我们将重点关注 iOS,了解如何在 Swift/Objective C 和 React 组件间建立桥梁。有以下三个步骤:

步骤 1) 创建一个 Bulb 类 并且完整初步通信

步骤 2) 理解 GCD Queue 并且解决出现的警告

步骤 3) 从 Swift 和 Callbacks 访问 JavaScript 中的变量

步骤 1) 创建一个 Bulb 类 并且完成初步通信

首先,我们将在 swift 中创建一个 Bulb 类,它将具有一个静态类变量 isOn 和一些其他函数。然后我们将从 Javascript 访问这个 swift 类。让我们首先在 ios 文件夹中打开 LightApp.xcodeproj 文件。此时 Xcode 应该会被打开。

在 Xcode 中打开项目后,创建一个新的 Swift文件 Bulb.swift,如下所示:

我们还要点击 Create Bridging Header,创建一个文件 LightApp-Bridging-Header.h 它将有助于 Swift 和 Objective C 代码之间的通信。请记住,在项目中,我们只有一个 Bridge Header 文件。因此,如果我们添加新文件,我们可以重用此文件。

将以下代码加入 -Bridging-Header.h 文件:

#import "React/RCTBridgeModule.h"

RCTBridgeModul 将提供一个接口,用于注册 Bridge 模块。

接下来将以下代码输入 Bulb.swift

import Foundation

@objc(Bulb)
class Bulb: NSObject {

    @objc
    static var isOn = false

    @objc
    func turnOn() {
        Bulb.isOn = true
        print("Bulb is now ON")
    }
}

我们创建了 Bulb 类,它继承自 NSObject。大多数 Objective-C 类的根类是 NSObject,子类从该类继承运行时系统的基本接口,因此它们有与 Objective-C 对象相同的能力。我们在函数和类之前使用了 @objc,这将使那个类,方法或对象可用于 Objective C。

@objc 注解使您的 Swift API 在 Objective-C 和 Objective-C 运行时可用。

现在选择 File -> New -> File 创建一个新文件,然后选择 Objective-C 文件,然后将该文件命名为 Bulb.m 并添加以下代码:

#import "React/RCTBridgeModule.h"
@interface RCT_EXTERN_MODULE(Bulb, NSObject)
RCT_EXTERN_METHOD(turnOn)
@end

除非显式地指定,否则 React Native 不会将任何 Bulb 中的函数暴露给 React JavaScript。为此,我们使用了 RCT_EXPORT_METHOD() 宏。所以我们已经暴露了 Bulb 类和 turnOn 函数给了我们的 Javascript 代码。由于 Swift 对象被转换为了 Javascript 对象,因此其中一定存在一种对应关系。RCT_EXPORT_METHOD 支持所有标准 JSON 对象类型:

  • NSString 对应 string
  • NSInteger、float、double、CGFloat、NSNumber 对应 number
  • BOOL 对应 boolean
  • NSArray 对应 array
  • NSDictionary 对应 包含此列表中的字符串键和任何类型的值的对象
  • RCTResponseSenderBlock 对应 function

现在让我们更新 JavaScript 代码并从我们的 React 组件访问这个 Bulb 类。为此,请打开 App.js 并更新为以下代码:

import React, {Component} from 'react';
import {StyleSheet, Text, View, NativeModules, Button} from 'react-native';

export default class App extends Component{
  turnOn = () => {
    NativeModules.Bulb.turnOn();
  }
  render() {
  return (
    <View style={styles.container}>
    <Text style={styles.welcome}>Welcome to Light App!!</Text>
    <Button
        onPress={this.turnOn}
    title="Turn ON "
    color="#FF6347" />
    </View>
  );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
});

现在运行iOS模拟器:

现在打开 Xcode 控制台查看日志,我们可以看到从 JavaScript 代码调用 Swift turnOn 方法。(因为我们已经看到了方法中执行的日志)

步骤 2) 理解 GCD Queue并且解决出现的警告

现在让我们修复模拟器底部和浏览器控制台中显示的警告:

Bulb 模块需要主队列设置,因为它覆盖 init但 没有实现 requiresMainQueueSetup。在以后的版本中,React Native 将默认初始化后台线程上的所有原生模块,除非明确选择不需要。

为了更好地理解,让我们了解 React Native 运行的所有线程:

  • Main thread:UI 渲染执行的线程
  • Shadow queue:布局发生的地方
  • JavaScript thread:JS 代码实际执行的地方

除非另有说明,否则每个原生模块都有自己的 GCD 队列。 现在,由于这个原生模块将在不同的线程上运行,并且我们的主线程依赖于它,它会显示此警告。 要使此代码在 MainQueue 上运行,请打开 Bulb.swift 并添加此函数。

@objc
static func requiresMainQueueSetup() -> Bool {
  return true
}

您可以明确提及 return false 以让它在单独的线程中运行。

步骤 3) 从Swift和Callbacks访问JavaScript中的变量

现在让我们将 Bulb 的开关(ON 或 OFF)值添加到 React 屏幕。为此,我们将 getStatus 函数添加到 Bulb.swift 并从 JavaScript 代码调用该方法。 我们将创建此方法作为回调。

React Native 桥是异步的,因此将结果传递给 JavaScript 的唯一方法是使用回调或触发事件

让我们用粗体更新 Bulb.swift 中的代码:

@objc(Bulb)
class Bulb: NSObject {
    @objc
    static var isOn = false

    @objc
    func turnOn() {
        Bulb.isOn = true
        print("Bulb is now ON")
    }

    @objc
    func turnOff() {
        Bulb.isOn = false
        print("Bulb is now OFF")
    }

    @objc
    func getStatus(_ callback: RCTResponseSenderBlock) {
        callback([NSNull(), Bulb.isOn])
    }

    @objc
        static func requiresMainQueueSetup() -> Bool {
        return true
    }
}

getStatus() 方法接收一个我们将从您的 JavaScript 代码传递的回调参数。我们用值数组调用了回调函数,这些函数将暴露在 JavaScript 中。我们已经将 NSNull() 作为第一个元素传递,我们将其视为回调中的错误。

我们需要将这个 Swift 方法暴露给 JavaScript,所以添加下方的粗体行代码到 Bulb.m 中:

@interface RCT_EXTERN_MODULE(Bulb, NSObject)
RCT_EXTERN_METHOD(turnOn)
RCT_EXTERN_METHOD(turnOff)
RCT_EXTERN_METHOD(getStatus: (RCTResponseSenderBlock)callback)
@end

我们已将 (RCTResponseSenderBlock)callback 暴露为函数 getStatus 的参数

然后最后更新React代码:

import React, {Component} from 'react';
import {StyleSheet, Text, View, NativeModules, Button} from 'react-native';

export default class App extends Component{
    constructor(props) {
      super(props);
      this.state = { isOn: false };
      this.updateStatus();
    }

    turnOn = () => {
      NativeModules.Bulb.turnOn();
      this.updateStatus()
    }

    turnOff = () => {
      NativeModules.Bulb.turnOff();
      this.updateStatus()
    }

    updateStatus = () => {
      NativeModules.Bulb.getStatus( (error, isOn)=>{
      this.setState({ isOn: isOn});
    })
}

render() {
    return (
    <View style={styles.container}>
    <Text style={styles.welcome}>Welcome to Light App!!</Text>
    <Text> Bulb is {this.state.isOn ? "ON": "OFF"}</Text>
    {!this.state.isOn ? <Button
    onPress={this.turnOn}
    title="Turn ON "
    color="#FF6347"
    /> :
    <Button
    onPress={this.turnOff}
    title="Turn OFF "
    color="#FF6347"
    /> }
    </View>
    );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
    },
});

重新变异代码并运行应用程序,您可以看到 Bulb Status 的值,当您单击 Turn ON 时,它将显示 Bulb 为 ON

请记住重新编译代码而不是刷新,因为我们更改了原生代码。

Section 2 — Native Bridge in Android

在本节中,我们将使用与 iOS 相同的 Javascript 代码,它同样可以应用在 Android 中。这次我们将在 Java 中创建 Bulb 类并将相同的函数 turnOn, TurnOffgetStatus 暴露给 Javascript。

打开 Android Studio 并单击 打开现有的 Android Studio 项目,然后在 LightApp 中选择 android 文件夹。下载所有 gradle 依赖项后,创建一个 Java 类 Bulb.java,如下所示:

并将 Bulb.java 中代码更新为:

package com.lightapp;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;

public class Bulb extends ReactContextBaseJavaModule  {
    private static Boolean isOn = false;
    public Bulb(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @ReactMethod
    public void getStatus(
        Callback successCallback) {
        successCallback.invoke(null, isOn);

    }

    @ReactMethod
    public void turnOn() {
        isOn = true;
        System.out.println("Bulb is turn ON");
    }
    @ReactMethod
    public void turnOff() {
        isOn = false;
        System.out.println("Bulb is turn OFF");
    }

    @Override
    public String getName() {
        return "Bulb";
    }

}

我们创建了一个 Bulb Java 类,它继承自 ReactContextBaseJavaModuleReactContextBaseJavaModule 要求一定要实现名一个为 getName 的函数。此方法的作用是返回在 JavaScript 中表示此类的 NativeModule 的字符串名称。所以在这里我们将调用 Bulb ,以便我们可以通过 JavaScript 中的 React.NativeModules.Bulb 来访问它。我们可以使用其它不同的名称代替 Bulb。

并非所有函数都显式地暴露给 Javascript,要向 JavaScript 公开函数,必须使用 @ReactMethod 注解 Java 方法。桥接方法的返回类型始终为 void。

我们还创建了一个 getStatu 函数,它具有参数作为回调,它返回一个 callback 并传递静态变量 isOn 的值。

下一步是注册模块,如果模块未注册,则无法从 JavaScript 获得。通过单击菜单"文件” ->“新建” -> “Java 类”并将文件名设置为 BulbPackage 来创建文件,然后单击“确定”。然后将以下代码添加到 BulbPackage.java

package com.lightapp;
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 BulbPackage implements ReactPackage  {
    @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 Bulb(reactContext));

        return modules;
    }

}

我们需要覆盖 createNativeModules 函数并将 Bulb 对象添加到 modules 数组中。如果这里没有添加,那么它将无法在 JavaScript 中使用。

需要在 MainApplication.java 文件的 getPackages 方法中提供 BulbPackage 包。此文件存在于 react-native 应用程序目录中的 android 文件夹下。在 android/app/src/main/java/com/LightApp/MainApplication.java 中更新以下代码

public class MainApplication extends Application implements ReactApplication {
...

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
          new BulbPackage()
      );
    }

....
}

我们不需要更改在 iOS 中编写的任何 JavaScript 代码,因为我们已经暴露了相同的类名和函数。如果您已跳过 iOS 部分,则需要从 App.js 复制 React Javascript 代码。

现在通过 Android Studio 或 react-native run-android 运行 App:

哇唔!我们可以在屏幕上看到 Bulb 状态,并可以从按钮切换 ON 或 OFF。最棒的是我们创建了一个跨平台的应用。

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏