Skip to content

Latest commit

 

History

History
1614 lines (1231 loc) · 63.8 KB

File metadata and controls

1614 lines (1231 loc) · 63.8 KB

十三、与本机应用的集成

在本章中,我们将介绍以下配方:

  • 组合 React 本机应用和本机 iOS 应用
  • 从 iOS 应用进行通信以响应本机
  • 从 React Native 到 iOS 应用容器进行通信
  • 由外部 iOS 应用调用的句柄
  • 结合 React 本机应用和本机 Android 应用
  • 从 Android 应用进行通信以响应本机
  • 从 React Native 通信到 Android 应用容器
  • 由外部 Android 应用调用的句柄

介绍

React Native 是作为一种使用 JavaScript 构建本机应用的解决方案引入的,目的是让更多的开发人员能够为多个平台构建真正的本机应用。作为与团队一起构建 React 本机应用的结果,JavaScript 开发人员和本机开发人员密切合作是很常见的

React-Native 呈现本机 UI 视图的能力的优点之一是,它们可以轻松嵌入现有的本机应用中。公司已经拥有对其业务线至关重要的复杂本机应用的情况并不少见。如果应用没有损坏,可能不需要立即在 React Native 中重写他们的整个代码库。在这种情况下,JavaScript 和本机开发人员都可以利用 React-Native 编写可集成到现有应用中的 React-Native 代码。

本章将专门关注在现有的本机 iOS 和 Android 应用中使用 React Native。我们将介绍如何在本机应用中呈现 React 本机应用,如何在 React 本机应用与其本机父应用之间通信,以及如何使用用户设备上的其他应用调用 React 本机应用。

When working on the Android recipes, it is recommended that you enable the auto-import settings in Android Studio or use Alt+Enter to perform a quick fix code completion for the class import.

组合 React 本机应用和本机 iOS 应用

如果你为一家公司工作,或者你的客户机上有一个活跃的 iOS 应用,那么从头开始重写它可能并不有利,尤其是如果它构建良好、使用频繁并受到用户的赞扬。如果您只想使用 React-Native 构建新功能,React-Native 应用可以嵌入并呈现在现有的本机 iOS 应用中。

本食谱将介绍如何创建一个空白的 iOS 应用,并将其添加到 React 本机应用中,以便两个层可以相互通信。我们将介绍两种呈现 React 本机应用的方法:作为嵌套视图嵌入到应用中,另一种作为全屏实现。本配方中讨论的步骤可作为呈现 React 本机应用以及本机 iOS 应用的基准。

准备

此配方将引用名为EmbeddedApp的本机 iOS 应用。在本节中,我们将介绍如何创建示例 iOS 应用。如果您已经有了打算与 React Native 集成的 iOS 应用,您可以跳到配方说明。但是,您需要确保已安装cocoapods。此库是 Xcode 项目的包管理器。可使用以下命令通过自制软件进行安装:

brew install cocoapods

安装了cocoapods后,下一步是在 Xcode 中创建一个新的本机 iOS 项目。这可以通过打开 Xcode 并选择 File | New | Project 来完成。在下面的窗口中,选择默认的单视图应用 iOS 模板开始,然后点击下一步。

在新项目的选项屏幕中,确保将产品名称字段设置为EmbeddedApp

怎么做。。。

  1. 我们将首先创建一个新的本地应用,作为我们项目的根。让我们把新项目命名为EmbedApp。您可以使用以下命令使用 CLI 创建新的 React 本机应用:
react-native init EmbedApp
  1. 通过使用 CLI 创建新应用,iosandroid子文件夹将自动为我们创建,保存每个平台的本机代码。让我们将我们在准备就绪部分创建的本机应用移动到ios文件夹中,以便它位于/EmbedApp/ios/EmbeddedApp
  2. 现在我们已经有了应用所需的基本结构,我们需要添加一个 Podfile。这是一个类似于 web 开发中的package.json的文件,用于跟踪项目中使用的所有 cocoapod 依赖项(称为 POD)。该 POD 文件应始终位于 vanilla iOS 项目的根目录中,在我们的示例中为/EmbedApp/ios/EmbeddedApp。在终端中,cd进入该目录并运行pod init命令。这将为您生成一个基本 Podfile。
  3. 接下来,在您喜爱的 IDE 中打开 pod 文件。我们将把应用所需的播客添加到此文件中。以下是最终 Podfile 的内容,新添加的 React 原生依赖项以粗体显示:
target 'EmbeddedApp' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
  # use_frameworks!

  # Pods for EmbeddedApp

  target 'EmbeddedAppTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'EmbeddedAppUITests' do
    inherit! :search_paths
    # Pods for testing
  end

 # Pods that will be used in the app
 pod 'React', :path => '../../node_modules/react-native', :subspecs => [
 'Core',
 'CxxBridge', # Include this for RN >= 0.47
 'DevSupport', # Include this to enable In-App Devmenu if RN >= 0.43
 'RCTText',
 'RCTNetwork',
 'RCTWebSocket', # Needed for debugging
 'RCTAnimation', # Needed for FlatList and animations running on native UI thread
 # Add any other subspecs you want to use in your project
 ]

 # Explicitly include Yoga if you are using RN >= 0.42.0
 pod 'yoga', :path => '../../node_modules/react-native/ReactCommon/yoga'

 # Third party deps podspec link
 pod 'DoubleConversion', :podspec => '../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
 pod 'glog', :podspec => '../../node_modules/react-native/third-party-podspecs/glog.podspec'
 pod 'Folly', :podspec => '../../node_modules/react-native/third-party-podspecs/Folly.podspec'

end

Notice how each of the paths listed in the React Native dependencies that we're adding point to the /node_modules folder of the React Native project. If your native project (in our case, EmbeddedApp) was at a different location, these references to /node_modules would have to be updated accordingly.

  1. 有了 pod 文件,安装 pod 本身就很容易了,只需在我们创建 pod 文件的同一目录下从终端运行pod install命令即可。
  2. 接下来,让我们回到项目根目录/EmbedApp中的 React 本机应用。首先,我们将删除index.js中生成的代码,并将其替换为我们自己的简单 React 本机应用。在文件底部,我们将使用AppRegistry组件上的registerComponent方法将EmbedApp注册为 React 本机应用的根组件。这将是一个非常简单的应用,只呈现文本Hello in React Native以便在后续步骤中将其与本机层区分开来:
import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  View,
  Text
} from 'react-native';

class EmbedApp extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>Hello in React Native</Text>
      </View>
    );
  }
}

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

AppRegistry.registerComponent('EmbedApp', () => EmbedApp);
  1. 现在我们有了 React 本机应用,我们可以转到本机代码。当我们在步骤 3中初始化 cocoapods 时,它也生成了一个新的.xcworkspace文件。请确保在 Xcode 中关闭EmbeddedApp项目,然后使用EmbeddedApp.xcworkspace文件在 Xcode 中重新打开它。
  2. 在 Xcode 中,我们打开Main.storyboard

  1. 在情节提要中,我们需要添加两个按钮:一个标记为 Open React Native App,另一个标记为 Open React Native App(嵌入式)。我们还需要在两个按钮下面有一个新的容器视图。生成的情节提要应如下所示:

  1. 接下来,我们需要一个新的 Cocoa Touch 类。这可以通过选择 File | New | File 从菜单中创建。我们将该类命名为EmbeddedViewController并为其分配一个子类UIViewController

  1. 让我们回到Main.storyboard。在通过在上一步中添加类创建的新场景(第二个视图控制器场景)中,选择视图控制器子场景。确保右侧面板中的 Identity inspector 处于打开状态:

选择视图控制器后,将类值更改为我们新创建的类EmbeddedViewController

  1. 接下来,在俯视控制器场景中,选择嵌入的 segue 对象:

  1. 选择 segue 后,从右侧面板中选择 Attributes inspector,并将标识符字段更新为嵌入值。我们将使用此标识符在本机应用中嵌入 React 本机层:

  1. 我们已经准备好构建ViewController实现。打开ViewController.m文件。我们将从导入开始:
#import "ViewController.h"
#import "EmbeddedViewController.h"
#import <React/RCTRootView.h>
  1. 在导入下方,我们可以添加一个接口定义,指向我们在步骤 10中创建的EmbeddedViewController
@interface ViewController () {
 EmbeddedViewController *embeddedViewController;
}

@end
  1. 以下是@interface,我们将在@implementation中添加我们需要的方法。第一个方法openRNAppButtonPressed将连接到我们在故事板中创建的第一个按钮,标记为 Open React Native App。类似地,openRNAppEmbeddedButtonPressed方法将连接到第二个按钮,打开反应本机应用(嵌入式)。 您可能会注意到这些方法几乎相同,第二个方法引用了embeddedViewController,与我们在步骤 10 中创建的EmbeddedViewController类相同([embeddedViewController setView:rootView];。这两种方法都使用值http://localhost:8081/index.bundle?platform=ios定义jsCodeLocation,这是 React 本机应用将从中提供服务的 URL。另外,请注意,两种方法中的moduleName属性都设置为EmbedApp,这是 React 本机应用导出时的名称,我们在步骤 6中定义:
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)openRNAppButtonPressed:(id)sender {
 NSURL *jsCodeLocation = [NSURL
 URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
 RCTRootView *rootView =
 [[RCTRootView alloc] initWithBundleURL : jsCodeLocation
 moduleName : @"EmbedApp"
 initialProperties : nil
 launchOptions : nil];

 UIViewController *vc = [[UIViewController alloc] init];
 vc.view = rootView;
 [self presentViewController:vc animated:YES completion:nil];
}
- (IBAction)openRNAppEmbeddedButtonPressed:(id)sender {
 NSURL *jsCodeLocation = [NSURL
 URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
 RCTRootView *rootView =
 [[RCTRootView alloc] initWithBundleURL : jsCodeLocation
 moduleName : @"EmbedApp"
 initialProperties : nil
 launchOptions : nil];

 [embeddedViewController setView:rootView];
}

// Defined in next step

@end
  1. 我们还需要定义prepareForSegue方法。在这里,您可以看到segue.identifier isEqualToString:@"embed",它是指我们在步骤 13中给 segue 的嵌入标识符:
// Defined in previous steps - (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
 if([segue.identifier isEqualToString:@"embed"]) {
 embeddedViewController = segue.destinationViewController;
 }
}

@end
  1. 随着ViewController的实施到位,我们现在需要将按钮动作与按钮本身联系起来。让我们回到Main.storyboardCtrl+点击第一个按钮,获得可分配给该按钮的动作菜单,通过点击并从“触动内部”拖回到故事板来选择“触动内部”动作,并将该按钮映射到步骤 15中定义的openRNAppButtonPressed方法。对第二个按钮重复这些步骤,将其链接到openRNAppEmbeddedButtonPressed方法:

  1. 为了使 React 本机层能够与本机层通信,我们还需要添加一个安全异常,这将允许我们的代码与localhost通信。右键点击Info.plist文件,选择“作为源代码打开”。在 base<dict>标记中,添加以下条目:
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
    <key>localhost</key>
    <dict>
      <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
      <true/>
    </dict>
  </dict>
</dict>
  1. 我们的应用已完成!在/EmbedApp根目录中,使用 CLI 使用以下命令启动 React 本机应用:
react-native start
  1. 运行 React 本机应用后,让我们也从 Xcode 运行本机应用EmbeddedApp。现在,按下 Open React Native App 按钮应全屏打开我们在步骤 6中创建的 React Native App,按下 Open React Native App(嵌入式)按钮时,相同的 React Native App 应在我们在步骤 9中创建的容器视图中打开。

它是如何工作的。。。

在本配方中,我们介绍了通过两种不同的方法在本机 iOS 应用中呈现 React 本机应用。第一个方法将应用的主UIViewController实例替换为 React 本机应用,在本机代码中称为RCTRootView。这是通过openRNAppButtonPressed方法实现的。第二种方法是将 React 本机应用与本机应用内联呈现。这是通过创建一个链接到不同UIViewController实例的容器视图来实现的。在本例中,我们用RCTRootView实例替换了embedViewController的内容。这就是openRNAppEmbeddedButtonPressed方法被触发时发生的情况

另见

为了更好地理解 cocoapods 在 Xcode/React 原生开发中所扮演的角色,我推荐谷歌的Route 85 Show一集在 YouTube 上讲述这个主题。视频可在找到 https://www.youtube.com/watch?v=iEAjvNRdZa0

从 iOS 应用进行通信以响应本机

在前面的配方中,我们学习了如何将 React 本机应用作为更大的本机 iOS 应用的一部分呈现。除非您正在构建美化的应用容器或门户,否则您可能需要在本机层和 React 本机层之间进行通信。这将是接下来两个食谱的主题,每个交流方向一个食谱。

在本配方中,我们将介绍从本机层到 React 本机层的通信,通过使用 iOS 应用中的UITextField将数据发送到 React 本机应用,将数据从父 iOS 应用发送到嵌入式 React 本机应用。

准备

由于此配方需要一个内置 React 本机应用的本机应用,因此我们将从上一配方的末尾开始,有效地从我们结束的地方开始。这将帮助您了解基本的跨层通信是如何工作的,以便您可以在自己的本机应用中使用相同的原则,本机应用可能已经存在并具有复杂的功能。因此,遵循此配方的最简单方法是使用前一配方的端点作为起点。

怎么做。。。

  1. 让我们从更新本机层中的ViewController.m实现文件开始。请确保通过EmbeddedApp中的.xcworkspace文件在 Xcode 中打开项目,我们在前面的配方中将该文件放在项目的/ios/EmbeddApp目录中。我们将从导入开始:
#import "ViewController.h"
#import "EmbeddedViewController.h"
#import <React/RCTRootView.h>
#import <React/RCTBridge.h>
#import <React/RCTEventDispatcher.h>
  1. 下一步是通过ViewController接口添加对 React 本机网桥的引用,有效地将本机控制器与 React 本机代码链接起来:
@interface ViewController () <RCTBridgeDelegate> {
    EmbeddedViewController *embeddedViewController;
    RCTBridge *_bridge;
    BOOL isRNRunning;
}
  1. 我们还需要一个userNameField@property参考,我们将在后面的步骤中使用它连接到UITextField
@property (weak, nonatomic) IBOutlet UITextField *userNameField;

@end
  1. 在这个引用的正下方,我们将开始定义类方法。我们将从sourceURLForBridge方法开始,该方法定义 React 本机应用将从何处提供服务。在我们的例子中,应用 URL 应该是http://localhost:8081/index.bundle?platform=ios,一旦使用react-native start命令运行,它将指向 React 本机应用的index.js文件:
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
    NSURL *jsCodeLocation = [NSURL
                             URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
    return jsCodeLocation;
}
  1. 我们将保留viewDidLoaddidReveiveMemoryWarning方法的原样:
- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
  1. 接下来,我们需要更新openRNAppEmbeddedButtonPressed方法。注意moduleName属性是如何设置为FromNativeToRN的。这是对我们在导出 React 本机应用时提供的名称的引用,我们将在后面的步骤中定义该名称。这一次,我们还定义了一个属性userName,用于将数据传递到 React 本机层:
- (IBAction)openRNAppEmbeddedButtonPressed:(id)sender {
    NSString *userName = _userNameField.text;
    NSDictionary *props = @{@"userName" : userName};

    if(_bridge == nil) {
        _bridge = [[RCTBridge alloc] initWithDelegate:self 
        launchOptions:nil];
    }

    RCTRootView *rootView =
    [[RCTRootView alloc] initWithBridge :_bridge
                             moduleName : @"FromNativeToRN"
                      initialProperties : props];

    isRNRunning = true;
    [embeddedViewController setView:rootView];
}
  1. 我们还需要一个onUserNameChanged方法。这是将通过网桥向 React 本机层实际发送数据的方法。我们在这里定义的事件名称是UserNameChanged,我们将在后面的步骤中在 React 原生层中引用它。这也将传递当前在文本输入中的文本,该文本将被命名为userNameField
- (IBAction)onUserNameChanged:(id)sender {
    if(isRNRunning == YES && _userNameField.text.length > 3) {
        [_bridge.eventDispatcher sendAppEventWithName:@"UserNameChanged" body:@{@"userName" : _userNameField.text}];
    }
}
  1. 在显示之前,我们还需要prepareForSegue来配置embeddedViewController
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if([segue.identifier isEqualToString:@"embed"]) {
        embeddedViewController = segue.destinationViewController;
    }
}
@end
  1. 回到Main.storyboard中,让我们添加该文本字段,以及定义输入内容的标签。还可以命名“输入用户名”字段,以便在“视图控制器”场景中更容易识别所有内容:

  1. 接下来,我们需要为用户名字段文本输入中的文本更改连接一个事件和一个引用出口,以便视图控制器知道如何引用它。这两项都可以通过连接检查器完成,通过右侧面板顶部的最后一个按钮(图标是一个圆圈中的右箭头)可以访问该检查器。选中文本输入后,单击并从编辑中拖动到视图控制器(通过主故事板表示),然后选择onUserNameChange我们在步骤 7中定义的方法。然后,通过将项目拖动到ViewController来创建以下接线。类似地,通过单击并从新的引用出口拖回视图控制器,添加一个新的引用出口,这次选择我们在步骤 7中针对的 userNameField 值。您的连接检查器设置现在应如下所示:

  1. 我们现在已经完成了本机应用所需的步骤。让我们转到 React 原生层。回到index.js文件中,我们将从导入开始。注意我们现在是如何包含NativeAppEventEmitter的。
  2. 将以下函数放入类定义中:
import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  View,
  Text,
  NativeAppEventEmitter
} from 'react-native';
  1. 我们将应用命名为FromNativeToRN以匹配我们在步骤 6中在本机层中定义的模块名称,使用AppRegistry.registerComponent以相同的名称注册应用。我们还将保留基本样式:
class FromNativeToRN extends Component {
 // Defined in following steps
}

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

AppRegistry.registerComponent('FromNativeToRN', () => FromNativeToRN);
  1. 我们将使用userName字符串属性设置初始state对象,用于存储和显示从本机层接收的文本:
class FromNativeToRN extends Component {
 state = {
 userName: ''
 }

 // Defined in following steps
}
  1. 传递到 React 本机层的userName值将作为属性接收。当组件装载时,我们要做两件事:设置userName状态属性(如果本机层已经定义了该属性),并在更新本机层中的文本字段时连接事件侦听器更新userName。回想一下在步骤 7中,我们将事件的名称定义为UserNameChanged,因此我们将监听该事件。收到事件后,我们将state.userName更新为随事件一起传递的文本:
  componentWillMount() {
    this.setState({
      userName : this.props.userName
    });

    NativeAppEventEmitter.addListener('UserNameChanged', (body) => {
        this.setState({userName : body.userName});
    });
  }
  1. 最后,我们可以添加render函数,它简单地呈现state.userName中存储的值:
  render() {
    return (
      <View style={styles.container}>
        <Text>Hello {this.state.userName}</Text>
      </View>
    );
  }
  1. 是时候运行我们的应用了!首先,在项目的根目录中,我们可以使用以下命令使用 React-Native CLI 启动 React-Native 应用:
react-native start

我们通过 Xcode 在模拟器中运行本机应用:

从 React Native 到 iOS 应用容器进行通信

最后一个配方涵盖了各层之间的通信,以本机方式进行反应。在本食谱中,我们将介绍相反方向的交流:从 React Native 到 Native。这次,我们将在 React 本机应用中呈现一个用户输入元素,并设置从 React 本机到本机应用中呈现的 UI 组件的单向绑定。

准备

就像上一个配方一样,这个配方取决于本章中第一个应用的最终产品,在中结合了 React 本机应用和本机 iOS 应用配方。接下来,确保你已经完成了那份食谱。

怎么做。。。

  1. 让我们从本机层开始。通过.xcworkspace文件在 Xcode 中打开EmbeddedApp本机应用。我们将首先向ViewController.m添加导入:
#import "ViewController.h"
#import "EmbeddedViewController.h"
#import <React/RCTRootView.h>
#import <React/RCTBridge.h>
#import <React/RCTEventDispatcher.h>
  1. 正如我们在上一个配方中所做的,我们需要通过ViewController接口添加对 React 本机网桥的引用,在本机控制器和 React 本机代码之间提供一个网桥:
@interface ViewController () <RCTBridgeDelegate> {
    EmbeddedViewController *embeddedViewController;
    RCTBridge *_bridge;
    BOOL isRNRunning;
}
  1. 我们还需要一个userNameField@property参考,我们将在后面的步骤中使用它连接到UITextField
@property (weak, nonatomic) IBOutlet UITextField *userNameField;

@end
  1. 让我们继续定义@implementation。同样,我们必须提供 React 本机应用的来源,该应用将从localhost提供:
@implementation ViewController

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
    NSURL *jsCodeLocation = [NSURL
                             URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
    return jsCodeLocation;
}
  1. 使用viewDidLoad方法,我们还可以将控制器连接到在容器视图(openRNAppEmbeddedButtonPressed中)中打开 React 本机应用的方法。我们将didReveiveMemoryWarning方法保持原样:
- (void)viewDidLoad {
    [super viewDidLoad];
 [self openRNAppEmbeddedButtonPressed:nil];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
  1. 与上一个配方一样,我们需要更新openRNAppEmbeddedButtonPressed方法。这一次,moduleName属性被设置为FromRNToNative,以反映我们将在导出 React 本机应用时为其提供的名称,如后面步骤中所定义。我们还定义了一个属性userName,用于将数据传递到 React 本机层:
- (IBAction)openRNAppEmbeddedButtonPressed:(id)sender {
    if(_bridge == nil) {
        _bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil];
    }

    RCTRootView *rootView =
    [[RCTRootView alloc] initWithBridge :_bridge
                      moduleName : @"FromRNToNative"
                      initialProperties : nil];

    isRNRunning = true;
    [embeddedViewController setView:rootView];
}
  1. 在本文件中,我们需要的最后两种方法是prepareForSegue用于在显示之前配置embeddedViewController,以及updateUserNameField方法,当我们在本机层中输入的文本使用来自用户的新文本更新时,将触发该方法:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if([segue.identifier isEqualToString:@"embed"]) {
        embeddedViewController = segue.destinationViewController;
    }
}

-(void) updateUserNameField:(NSString *)userName {
    [_userNameField setText:userName];
}
@end
  1. 与前面的配方不同,我们还需要更新ViewController头文件(ViewController.h。此处引用的方法updateUserNameField将在我们定义ViewController实现时使用:
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
- (void) updateUserNameField:(NSString *)userName;

@end
  1. 接下来,我们需要创建一个新的UserNameManager本机模块。首先,创建一个名为UserNameManager的 Cocoa Touch 类。创建完成后,让我们打开实现文件(UserNameManger.m并添加我们的导入:
#import "UserNameManager.h"
#import "AppDelegate.h"
#import "ViewController.h"
#import <React/RCTBridgeModule.h>

For a more in-depth look at creating native modules, refer to the Exposing Custom iOS Modules recipe in Chapter 11Adding Native Functionality.

  1. 接下来,我们将定义类实现。这里主要介绍的是setUserName方法,这是我们从本机层导出的用于 React 本机应用的方法。我们将在 React Native 应用中使用此方法来更新 Native 文本字段中的值。但是,由于我们正在更新本机 UI 组件,因此必须在主线程上执行该操作。这是methodQueue函数的目的,它指示模块在主线程上执行:
@implementation UserNameManager
RCT_EXPORT_MODULE();

- (dispatch_queue_t)methodQueue
{
    return dispatch_get_main_queue();
}

RCT_EXPORT_METHOD(setUserName: (NSString *)userName) {
    AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    ViewController *controller = (ViewController *)delegate.window.rootViewController;

    [controller updateUserNameField:userName];
}
@end
  1. 我们还需要更新UserNameMangager.h头文件以使用 React 本机网桥模块:
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

@interface UserNameManager : NSObject <RCTBridgeModule>

@end
  1. 与上一个配方一样,我们需要为用户名输入添加文本字段和标签:

  1. 我们还需要将上一个集合中创建的文本字段中的引用出口添加到我们的userNameField属性中:

If you need more information on how to create a Referencing Outlet, view step 10 of the previous recipe.

  1. 我们已经完成了这个项目的本机部分,所以让我们转向 React 本机代码。让我们打开项目根目录下的index.js文件。我们将从我们的进口开始:
import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  View,
  Text,
  TextInput,
  NativeModules
} from 'react-native';
  1. 让我们定义名为FromRNToNative的应用,与步骤 6中本机代码中声明的moduleName对齐,并用相同的名称注册组件。state对象只需要一个userName字符串属性来保存保存到TextInput组件的值,我们将在组件的render函数中添加该值:
class FromRNToNative extends Component {
  state = {
    userName: ''
  }

  // Defined on next step
}

AppRegistry.registerComponent('FromRNToNative', () => FromRNToNative);
  1. 应用的render功能使用TextInput组件从用户获取输入,然后通过 React native bridge 发送到本机应用。当TextInput的值发生变化时,调用onUserNameChange方法:
  render() {
    return (
      <View style={styles.container}>
        <Text>Enter User Name</Text>
        <TextInput
          style={styles.userNameField}
          onChangeText={this.onUserNameChange}
          value={this.state.userName}
        />
      </View>
    );
  }
  1. 我们需要做的最后一件事是定义我们在上一步中定义的TextInput组件的onChangeText属性所使用的onUserNameChange方法。此方法将state.userName更新为文本输入中的值,并使用 React native 中的NativeModules组件将该值发送到本机代码。NativeModules具有UserNameManager类,我们在步骤 9中定义为本机层中的 Cocoa Touch 类。我们调用步骤 10中在类上定义的setUserName方法,将值传递到本机层,并在步骤 12中创建的文本字段中显示:
  onUserNameChange = (userName) => {
    this.setState({userName});
    NativeModules.UserNameManager.setUserName(userName);
  }
  1. 应用完成了!返回项目的根目录,使用以下命令启动 React 本机应用:
react-native start

然后,启动 React 本机应用,从 Xcode 运行本机EmbeddedApp项目。现在,React 本机应用中的输入应将其值传达给父级本机应用中的输入:

它是如何工作的。。。

为了从我们的 React 本机应用与父级本机应用进行通信,我们创建了一个名为UserNameManager的本机模块,使用setUserName方法,该方法从本机层导出,并在 React 本机应用的onUserNameChange方法中使用。这是推荐的从 React Native 到 Native 的通信方式。

由外部 iOS 应用调用的句柄

本机应用之间通过链接进行通信也是一种常见行为,通常会向用户提示短语 Open in…,以及能够更好地处理操作的应用的名称。这是通过使用特定于应用的协议来实现的。就像任何网站链接都有一个http://https://协议一样,我们也可以创建一个自定义协议,允许任何其他应用打开并向我们的应用发送数据。

在这个配方中,我们将创建一个名为invoked://的定制协议。通过使用invoked://协议,任何其他应用都可以使用它来运行我们的应用并将数据传递给它。

准备

对于这个食谱,我们将从一个新的本地应用开始。让我们把它命名为InvokeFromNative

怎么做。。。

  1. 让我们首先在 Xcode 中打开新项目的本机层。我们需要做的第一件事是调整项目的构建设置。这可以通过在左侧面板中选择根项目,然后沿中间面板顶部选择“生成设置”选项卡来完成:

  1. 我们需要在标题搜索路径字段中添加一个新条目:

为了让项目知道 React 原生 JavaScript 的位置,它需要$(SRCROOT)/../node_modules/react-native/Libraries值。让我们将其添加为递归条目:

  1. 我们还需要注册我们的自定义协议,这将被其他应用使用。将Info.plist文件作为源代码打开(右键单击,然后作为|源代码打开)。让我们在文件中添加一个条目,该条目将根据invoked://协议注册我们的应用:
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleTypeRole</key>
    <string>Editor</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>invoked</string>
    </array>
  </dict>
</array>
  1. 接下来,我们需要将RCTLinkingManager添加到AppDelegate实现中,该实现位于AppDelegate.m中,并将其连接到我们的应用:
#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTLinkingManager.h>

@implementation AppDelegate

// The rest of the AppDelegate implementation

- (BOOL)application:(UIApplication *)application
 openURL:(NSURL *)url
 options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
 return [RCTLinkingManager application:application openURL:url options:options];
}

@end
  1. 现在,让我们转到 React 原生层。在index.js中,我们将添加我们的进口,其中包括Linking组件:
import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Linking
} from 'react-native';
  1. 接下来,我们将创建类定义并将组件注册为InvokeFromNative。我们还将定义一个初始的state对象,其status字符串属性设置为值'App Running'
class InvokeFromNative extends Component {
 state = {
 status: 'App Running'
 }

 // Defined on following steps
}

AppRegistry.registerComponent('InvokeFromNative', () => InvokeFromNative);
  1. 现在,我们将使用add/remove事件监听器的invoked://协议的装载和卸载生命周期挂钩。听到事件后,将触发下一步定义的onAppInvoked方法:
  componentWillMount() {
    Linking.addEventListener('url', this.onAppInvoked);
  }

  componentWillUnmount() {
    Linking.removeEventListener('url', this.onAppInvoked);
  }
  1. onAppInvoked函数只需从事件侦听器获取事件并更新state.status以反映调用已发生,通过event.url显示协议:
  onAppInvoked = (event) => {
    this.setState({
      status: `App Invoked by ${ event.url }`
    });
  }
  1. 此配方中的render方法的唯一真正用途是在状态上呈现status属性:
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.instructions}>
          App Status:
        </Text>
        <Text style={styles.welcome}>
          {this.state.status}
        </Text>
      </View>
    );
  }
  1. 我们还将添加一些基本样式以使文本居中并调整其大小:
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});
  1. 我们的应用完成了。一旦开始运行应用,您应该会看到如下内容:

  1. 在应用运行时,我们可以模拟另一个应用使用invoked://协议打开我们的 React 本机应用的动作。这可以通过以下终端命令完成:
 xcrun simctl openurl booted invoked://

一旦调用,应用应更新以反映调用:

它是如何工作的。。。

在本食谱中,我们介绍了如何注册自定义协议(或 URL 模式),以允许其他应用调用我们的应用。这个方法的目的是使我们的示例尽可能简单,因此我们没有构建通过链接机制传递给应用的处理数据。但是,如果应用的需要,完全可以这样做。对于Linking组件的深入研究,请查看上的官方文件 https://facebook.github.io/react-native/docs/linking

结合 React 本机应用和本机 Android 应用

由于安卓平台仍然持有智能手机市场的大部分股份,因此您可能希望为安卓和 iOS 构建应用。React Native 开发的一大优势是使此过程更容易。但是,当你想为一个已经发布的 Android 应用编写一个新功能时,会发生什么呢?幸运的是,React Native 也使这成为可能。

本食谱将介绍通过在容器视图中显示 React 本机应用,将 React 本机应用嵌入现有 Android 应用的过程。此处的步骤用作后续配方的基线,其中涉及与 React 本机应用的通信。

准备

在本节中,我们将使用名为EmbedApp的 Android Studio 创建一个示例 Android 应用。如果您有一个想要使用的基础 Android 应用,您可以跳过这些步骤,继续实际的实现:

  1. 打开 Android Studio 并创建一个新项目(文件|新项目)
  2. 将应用名称设置为EmbeddedApp并填写您的公司域。按下一步
  3. 将空活动保留为默认活动,然后按“下一步”
  4. 将“活动”属性保留为默认状态,然后按 Finish

怎么做。。。

  1. 此时,我们的应用没有响应本机的引用,因此我们将从安装它开始。在应用的根文件夹中,在终端中,使用yarn从命令行安装 React Native:
yarn add react-native

或者,您可以使用npm

 npm install react-native --save
  1. 我们还需要一个 Node.js 脚本来启动 React 本机应用。让我们打开package.json并添加以下属性作为脚本对象的成员:
 "start": "node node_modules/react-native/local-cli/cli.js start"
  1. 我们只需要一个非常简单的反应本机应用这个食谱。让我们使用以下样板应用创建一个index.android.js文件:
import React, { Component } from 'react';
import { AppRegistry, StyleSheet, View, Text } from 'react-native';

export default class EmbedApp extends Component {
  render() {
    return (<View style={styles.container}>
      <Text>Hello in React Native</Text>
    </View>);
  }
}

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

AppRegistry.registerComponent('EmbedApp', () => EmbedApp);

将此文件命名为index.android.js表示此代码仅适用于此应用的 Android 版本。当特定于平台的代码更复杂时,官方文档建议这样做。您可以在上了解更多信息 https://facebook.github.io/react-native/docs/platform-specific-code#platform-特定扩展名

  1. 让我们回到 Android Studio,打开build.gradle文件(来自应用模块),并将以下内容添加到依赖项中:
dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"]) 
    implementation "com.android.support:appcompat-v7:27.1.1"
    implementation "com.facebook.react:react-native:+" // From node_modules
}   
  1. 我们还需要对本地 React Native maven 目录的引用。打开另一个build.gradle并向allprojects.repositories对象添加以下行:
allprojects {
  repositories {
    mavenLocal()
      maven {
        url "$rootDir/../node_modules/react-native/android"
      }
    google()
    jcenter()
  }
}
  1. 接下来,让我们更新应用使用互联网的权限,以及系统警报窗口。我们将打开AndroidManifest.xml并向<manifest>节点添加以下权限:
<?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.warlyware.embeddedapp">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

    <application
        android:name=".EmbedApp" 
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

 </manifest>
  1. 我们已经准备好更新MainApplicationJava 类。这里的getUseDeveloperSupport方法将启用开发菜单。getPackages方法是应用使用的软件包列表,因为我们只使用主 React 软件包,所以只包括MainReactPackage()getJSMainModuleName方法返回index.android字符串,表示 React 原生层中的index.android.js文件:
import android.app.Application;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;

import java.util.Arrays;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {
  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    @Override
    public boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

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

  @Override
  public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
  }
  @Override
  protected String getJSMainModuleName() {
    return "index.android";
  }
}
  1. 接下来,让我们创建另一个名为ReactFragment的新 Java 类。此类需要三种方法:OnAttach在片段附加到主活动时调用,OnCreateView实例化片段的视图,在创建活动时调用OnActivityCreated
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;

import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;

public abstract class ReactFragment extends Fragment {
  private ReactRootView mReactRootView;
  private ReactInstanceManager mReactInstanceManager;

  // This method returns the name of our top-level component to show
  public abstract String getMainComponentName();

  @Override
  public void onAttach(Context context) {
    super.onAttach(context);
    mReactRootView = new ReactRootView(context);
    mReactInstanceManager =
      ((EmbedApp) getActivity().getApplication())
        .getReactNativeHost()
        .getReactInstanceManager();
  }

  @Override
  public ReactRootView onCreateView(LayoutInflater inflater, ViewGroup group, Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    return mReactRootView;
  }

  @Override
  public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mReactRootView.startReactApplication(
      mReactInstanceManager,
      getMainComponentName(),
      getArguments()
    );
  }
}
  1. 最后,创建一个名为EmbedFragment的 Java 类,该类将扩展ReactFragment
import android.os.Bundle;

public class EmbedFragment extends ReactFragment {
  @Override
  public String getMainComponentName() {
    return "EmbedApp";
  }
}
  1. 让我们打开MainActivity.java并将implements DefaultHardwareBackBtnHandler添加到类定义中,以处理硬件后退按钮事件。您可以在此处查看此 React 本机类的注释源代码:https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/modules/core/DefaultHardwareBackBtnHandler.java

  2. 我们还将向类中添加一些方法。onCreate方法将内容视图设置为主活动,并添加一个 FAB 按钮,单击该按钮时,将实例化我们在步骤 10中定义的EmbedFragment的新实例。片段管理器使用EmbedFragment的实例将 React 本机应用添加到视图中。其余方法处理当按下设备的系统按钮(如后退、暂停和恢复按钮)时发生的事件:

import android.app.Fragment;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.KeyEvent;
import android.view.View;

import com.facebook.react.ReactInstanceManager;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;

public class MainActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
  private ReactInstanceManager mReactInstanceManager;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {
        Fragment viewFragment = new EmbedFragment();
        getFragmentManager().beginTransaction().add(R.id.reactnativeembed, viewFragment).commit(); }
    });

    mReactInstanceManager = ((EmbedApp) getApplication()).getReactNativeHost().getReactInstanceManager();
  }

  @Override
  public void invokeDefaultOnBackPressed() {
    super.onBackPressed();
  }

  @Override
  protected void onPause() {
    super.onPause();

    if (mReactInstanceManager != null) {
      mReactInstanceManager.onHostPause(this);
    }
  }

  @Override
  protected void onResume() {
    super.onResume();

    if (mReactInstanceManager != null) {
      mReactInstanceManager.onHostResume(this, this);
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();

    if (mReactInstanceManager != null) {
      mReactInstanceManager.onHostDestroy(this);
    }
  }

  @Override
  public void onBackPressed() {
    if (mReactInstanceManager != null) {
      mReactInstanceManager.onBackPressed();
    } else {
      super.onBackPressed();
    }
  }

  @Override
  public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
      mReactInstanceManager.showDevOptionsDialog();
      return true;
    }
    return super.onKeyUp(keyCode, event);
  }
}
  1. 最后一步是在加载片段时为布局添加一些设置。我们需要编辑content_main.xml文件,它位于/res文件夹中。这是视图的主要内容。它包含我们将附加片段的容器视图(FrameLayout,其他本机元素应显示:
 <FrameLayout
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:layout_centerVertical="true"
    android:layout_alignParentStart="true"
    android:id="@+id/reactnativeembed"
    android:background="#FFF">
</FrameLayout>
  1. 在终端中,运行以下命令:
 react-native start 

这将构建并托管 React 本机应用。现在,我们可以在 Android emulator 中打开该应用。按下 FAB 按钮后,您将看到以下内容:

它是如何工作的。。。

为了在我们的 Android 应用中实现本地渲染,我们必须执行几个步骤。首先,我们必须定义一个实现ReactApplication接口的Application类。然后,我们必须创建一个Fragment,负责实例化和呈现ReactRootView。使用片段,我们可以在MainActivity中呈现 React 本机视图。在这个配方中,我们将片段添加到片段容器视图中。这实质上用 React 本机应用替换了所有应用内容。

我们在这个配方中介绍了很多集成代码。要更深入地了解每一部分的工作原理,您可以阅读上的官方文档 https://facebook.github.io/react-native/docs/integration-with-existing-apps.html

从 Android 应用进行通信以响应本机

既然我们已经在中介绍了如何在 Android 应用中呈现 React 本机应用,结合 React 本机应用和本机 Android 应用配方,那么我们就准备好进行下一步了。我们的 React 本机应用应该不仅仅是一个虚拟 UI。它应该能够对其父应用中正在进行的操作作出反应。

在此配方中,我们将完成从 Android 应用向嵌入式 React 本机应用发送数据。React 本机应用可以在第一次实例化数据时接受数据,然后在运行时接受数据。我们将介绍如何实现这两种方法。此配方将在 Android 应用中使用EditText,并设置与 React 本机应用的单向绑定。

准备

对于此配方,请确保您的 Android 应用中嵌入了 React 本机应用。如果您需要指导来完成此操作,请完成结合 React 原生应用和原生 Android 应用的配方。

怎么做。。。

  1. 在 Android Studio 中,打开 React 本机应用的 Android 部分。首先,我们需要编辑content_main.xml
  2. 我们只需要一个非常简单的布局这个应用。通过按底部的“文本”选项卡打开源代码编辑器并添加/替换以下节点,可以编辑文件:
<TextView android: layout_width = "wrap_content"
android: layout_height = "wrap_content"
android: text = "Press the Mail Icon to start the React Native application"
android: id = "@+id/textView" />
<FrameLayout android: layout_width = "match_parent"
android: layout_height = "300dp"
android: layout_centerVertical = "true"
android: layout_alignParentStart = "true"
android: id = "@+id/reactnativeembed"
android: background = "#FFF" >
</FrameLayout>
<LinearLayout android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="75dp"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true">
  <TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="User Name:"
  android:id="@ + id / textView2"
  android:layout_weight="0.14 " />
  <EditText android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:id="@ + id / userName"
  android:layout_weight="0.78"
  android:inputType="text"
  android:singleLine="true"
  android:imeOptions="actionDone"/>
</LinearLayout>
  1. 打开MainActivity.java并添加以下类字段:
private ReactInstanceManager mReactInstanceManager;
private EditText userNameField;
private Boolean isRNRunning = false;
  1. onCreatemethod内,用以下代码设置userNameField属性:
  userNameField = (EditText) findViewById(R.id.userName);
  1. 我们将使用 FAB 按钮将 Android 应用的内容更新为我们的 React 本机应用。我们需要将FloatingActionButtononClickListener替换为以下内容:
fab.setOnClickListener(new View.OnClickListener() {
  @Override public void onClick(View view) {
    Fragment viewFragment = new EmbedFragment();
    if (userNameField.getText().length() > 0) {
      Bundle launchOptions = new Bundle();
      launchOptions.putString("userName", 
      userNameField.getText().toString());
      viewFragment.setArguments(launchOptions);
    }
    getFragmentManager().beginTransaction().add(R.id.reactnativeembed, viewFragment).commit();
    isRNRunning = true;
  }
});
  1. 接下来,我们需要在onCreate方法中的userNameField中添加一个TextChangedListener
userNameField.addTextChangedListener(new TextWatcher() {
  @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
  @Override public void onTextChanged(CharSequence s, int start, int before, int count) {}
  @Override public void afterTextChanged(Editable s) {
    if (isRNRunning) {
      sendUserNameChange(s.toString());
    }
  }
});
  1. 我们需要对Activity进行的最后一个更改是添加将通过 React 本机网桥发送事件的方法:
private void sendUserNameChange(String userName) {
  WritableMap params = Arguments.createMap();
  params.putString("userName", userName);
  sendReactEvent("UserNameChanged", params);
}

private void sendReactEvent(String eventName, WritableMap params) {
  mReactInstanceManager.getCurrentReactContext()
    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
    .emit(eventName, params);
}
  1. 让我们回到 JavaScript 层。我们将使用NativeAppEventEmitter组件的addListener方法监听本机 Android 代码发送的UserNameChanged事件,并使用事件中的数据更新state.userName
import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  View,
  Text,
  NativeAppEventEmitter
} from 'react-native';

export default class EmbedApp extends Component<{}> {
  componentWillMount() {
    this.setState({
      userName : this.props.userName
    });

    NativeAppEventEmitter.addListener('UserNameChanged', (body) => {
        this.setState({userName : body.userName});
    });
  }
  render() {
    return (
      <View style={styles.container}>
        <Text>Hello {this.state.userName}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('EmbedApp', () => EmbedApp);
  1. 现在,如果运行应用,可以在用户名字段中输入文本并启动 React 本机应用:

它是如何工作的。。。

在这个配方中,我们将片段呈现为一个内联视图。在步骤 2中,我们添加了一个空的FrameLayout,我们在步骤 5中针对它来渲染片段。绑定功能是通过RCTDeviceEventEmitter使用 React 本机网桥实现的。这最初设计用于本机模块,但只要您有权访问ReactContext实例,就可以将其用于与 React 本机 JavaScript 层的任何通信。

从 React Native 通信到 Android 应用容器

正如我们在前面的配方中所讨论的,我们的嵌入式应用能够了解周围发生的事情是非常有益的。我们还应该努力让 Android 父应用了解 React 本机应用内部的情况。应用不仅应该能够执行业务逻辑,还应该能够更新其 UI 以反映嵌入式应用中的更改。

这个方法向我们展示了如何利用本机模块来更新 Android 应用内部创建的本机 UI。我们将在 React 原生应用中有一个文本字段,用于更新在宿主 Android 应用中呈现的文本字段。

准备

对于此配方,请确保您的 Android 应用中嵌入了 React 本机应用。如果您需要指导来完成此操作,请完成结合 React 原生应用和原生 Android 应用的配方。

怎么做。。。

  1. 打开 Android Studio到您的项目并打开content_main.xml *** 按下底部文本**选项卡,打开源代码编辑器,添加/替换以下节点:****
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:paddingBottom="@dimen/activity_vertical_margin"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  android:paddingTop="@dimen/activity_vertical_margin"
  app:layout_behavior="@string/appbar_scrolling_view_behavior"
  tools:context="com.embedapp.MainActivity"
  tools:showIn="@layout/activity_main">

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Press the Mail Icon to start the React Native application"
    android:id="@+id/textView" />

  <FrameLayout
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:layout_centerVertical="true"
    android:layout_alignParentStart="true"
    android:id="@+id/reactnativeembed"
    android:background="#FFF"></FrameLayout>

  <LinearLayout
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="75dp"
    android:layout_below="@+id/textView"
    android:layout_centerHorizontal="true">

    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="User Name:"
      android:id="@+id/textView2"
      android:layout_weight="0.14" />

      <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/userName"
        android:layout_weight="0.78"
        android:inputType="text"
        android:singleLine="true"
        android:imeOptions="actionDone"/>
  </LinearLayout>
</RelativeLayout>
  1. 创建一个名为UserNameManager的 Java 类。这将是一个本机模块,用于更新我们添加到布局中的EditTextfield

If you are not familiar with creating a native module for React Native, please refer to the Exposing custom Android modules recipe in Chapter 11Adding Native Functionality

  1. UserNameManager.java中的大部分工作都是通过setUserName方法完成的。在这里,Android 层根据从 React 原生层发送的内容更新视图的文本内容。React 方法不一定会在主 UI 线程上运行,所以我们使用mainActivity.runOnUiThread在主 UI 线程就绪时更新视图:
public class UserNameManager extends ReactContextBaseJavaModule {
  public UserNameManager(ReactApplicationContext reactApplicationContext) {
    super(reactApplicationContext);
  }
  @Override public String getName() {
    return "UserNameManager";
  }
  @ReactMethod public void setUserName(final String userName) {
    Activity mainActivity = getReactApplicationContext().getCurrentActivity();
    final EditText userNameField = (EditText) mainActivity.findViewById(R.id.userName);
    mainActivity.runOnUiThread(new Runnable() {
      @Override public void run() {
        userNameField.setText(userName);
      }
    });
  }
}
  1. 要导出UserNameManager模块,我们需要编辑UserNamePackageJava 类。我们可以通过调用modules.add将其导出到 React 原生层,传入一个新的UserNameManager,该UserNameManagerreactContext为参数:
public class UserNamePackage implements ReactPackage {
  @Override public List < Class << ? extends JavaScriptModule >> createJSModules() {
      return Collections.emptyList();
  }
  @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 UserNameManager(reactContext));
      return modules;
  }
}
  1. MainApplicationgetPackages方法中增加UserNamePackage
 @Override
 protected List<ReactPackage> getPackages() {
  return Arrays.<ReactPackage>asList(
   new MainReactPackage(),
   new UserNamePackage()
  );
 }
  1. 现在,我们需要让 React 本机 UI 呈现一个TextField并调用UserNameManager本机模块。打开index.android.js并从'react-native'导入TextInputNativeModules模块。
  2. UserNameManager创建变量引用:
       const UserNameManager = NativeModules.UserNameManager;
  1. React 本机应用只需要一个TextInput就可以操作state对象上的userName属性:
let state = {
  userName: ''
}

onUserNameChange = (userName) => {
  this.setState({
    userName
  });

  UserNameManager.setUserName(userName);
}

render() {
  return (
    <View style={styles.container}>
      <Text>Embedded RN App</Text>
      <Text>Enter User Name</Text>
      <TextInput style={styles.userNameField}
        onChangeText={this.onUserNameChange}
        value={this.state.userName}
      />
    </View>
  );
}
  1. 运行应用、启动 React Native embedded 应用并将文本添加到文本字段后,您应该会看到类似于以下屏幕截图所示的内容:

它是如何工作的。。。

为了让我们的 React 本机应用更新本机应用容器,我们创建了一个本机模块。这是从 JavaScript 到本机层通信的推荐方式。但是,由于我们必须更新一个本机 UI 组件,因此操作必须在主线程上执行。这是通过获取对MainActivity的引用并调用runOnUiThread方法来实现的。这是在步骤 4setUserName方法中完成的。

由外部 Android 应用调用的句柄

在本章前面,我们在外部 Android 应用调用的句柄中介绍了如何在 iOS 中处理外部应用的调用。在本食谱中,我们将介绍 Android 中深度链接的相同概念。

怎么做。。。

  1. 让我们首先在 Android Studio 中打开 React 原生 Android 项目并导航到AndroidManifest.xml
  2. 例如,我们将在invoked://scheme下注册我们的申请。我们将<activity>节点更新为以下内容:
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTask">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
</activity>

For more information on how this intent-filter works, refer to the official Android documentation at https://developer.android.com/training/app-links/deep-linking.

  1. 接下来,我们需要创建一个简单的 React 本机应用,其 UI 会对被调用做出反应。让我们打开index.android.js文件。我们首先从'react-native'导入import块中的Linking模块:
import React from 'react';
import { Platform, Text, Linking } from 'react-native';
  1. 让我们为 React 本机应用构建App类。当组件挂载时,我们将注册一个Linking事件侦听器,并将其命名为url。当此事件发生时,onAppInvoked将被触发,更新 state 的status属性以及传递给回调的事件:
export default class App extends React.Component {
  state = {
    status: 'App Running'
  }

  componentWillMount() {
    Linking.addEventListener('url', this.onAppInvoked);
  }

  componentWillUnmount() {
    Linking.removeEventListener('url', this.onAppInvoked);
  }

  onAppInvoked = (event) => {
    this.setState({ status: `App Invoked by ${event.url}` });
  }

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.instructions}>
          App Status:
        </Text>
        <Text style={styles.welcome}>
          {this.state.status}
        </Text>
      </View>
    );
  } 
}
  1. 运行应用并从另一个应用调用该应用将如下所示:

它是如何工作的。。。

在这个配方中,我们通过编辑步骤 2中的AndroidManifest.xml文件注册了链接的 URL 模式。需要注意的一件重要事情是将launchMode更改为singleTask。这可以防止操作系统创建 React 活动的多个实例。如果您希望能够正确捕获随意图传递的数据,这一点很重要。****