首先,由RN中文网关于原生模块(Android)的介绍可以看到,RN前端与原生模块之
间通信,主要有三种方法:
(1)使用回调函数Callback,它提供了一个函数来把返回值传回给JavaScript。
(2)使用Promise来实现。
(3)原生模块向JavaScript发送事件。
其中,在我的博客React-Native开发之原生模块封装(Android)升级版较为详细的阐述了如何使用回调函数Callback
来将数据传向JavaScript 端。
但是有一个比较难以解决的问题是:
callback并非在对应的原生函数返回后立即被执行,因为跨语言通讯是异步的,这个执行过程会通过消息循环来进行。
也就是说,当我们在前端调用原生函数后,原生函数的返回值可能还没有得出,然而已经在执行Callback了,所以我
们并不能得到准确的返回值。因为回调函数Callback 对原生模块来说是被动的,由前端来主动调用回调函数,然后
得到返回值,实现原生模块和前端的数据交互。
原生模块和前端数据交互的第三种方法:原生模块向JavaScript发送事件则是一种主动机制。当原生函数执行到任
何一步时都可以主动向前端发送事件。前端需要监视该事件,当前端收到某确定事件时,则可准确获知原生函数目前
执行的状态以及得到原生函数的返回值等。这样前端可以进行下一步的操作,如更新UI等。
接下来我们看一下如何由原生模块向JavaScript前端发送事件。
(1)首先,你需要定义一个发送事件的方法。如下所示:
/*原生模块可以在没有被调用的情况下往JavaScript发送事件通知。 最简单的办法就是通过RCTDeviceEventEmitter, 这可以通过ReactContext来获得对应的引用,像这样:*/ public static void sendEvent(ReactContext reactContext,String eventName,@Nullable WritableMap paramss) { System.out.println("reactContext="+reactContext); reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(eventName,paramss); }
其中方法名可以任意,但是参数不可改变。该方法可以放在你要复用的原生类中(即为原生类1)。
需要注意的是,由于版本问题,该函数中的参数reactContext有可能为null,此时会报NullPointException的错误。所以我们需要手动给reactContext赋值,见步骤2.
(2)我们在原生类1中,定义变量public static ReactContext MyContext;
然后在我们自定义的继承至ReactContextBaseJavaModule的类中给reactContext赋值。
如下所示:
public class MyModule extends ReactContextBaseJavaModule { private BluetoothAdapter mBluetoothAdapter = null; public MyModule(ReactApplicationContext reactContext) { super(reactContext); 原生类1.MyContext=reactContext; } .......以下写被@ReactNative所标注的方法 ............................ ................... }
此时,reactContext将不会是null。也就不会报错。
(3)在某个原生函数中向JavaScript发送事件。如下所示:
WritableMap event = Arguments.createMap(); sendEvent(MyContext,"EventName",event);
然后使用componentWillMount建立监听。
代码如下:
componentWillMount(){ DeviceEventEmitter.addListener('EventName',function() { alert("send success"); }); }
注意:该监听必须放在class里边,和render、const对齐。
下边展示一个完整Demo,Demo功能如下:
(1)JavaScript端在监听一个事件。
(3)在原生方法中,延迟3s后向前端发送对应事件。
(4)前端接收到事件后,给出alert提示。
代码如下:
ManiActivity.java
package com.ywq; import com.facebook.react.ReactActivity; public class MainActivity extends ReactActivity { /** * Returns the name of the main component registered from JavaScript. * This is used to schedule rendering of the component. */ @Override protected String getMainComponentName() { return "ywq"; } }
ManiApplication.java
package com.ywq; import android.app.Application; import android.util.Log; import com.facebook.react.ReactApplication; import com.facebook.react.ReactInstanceManager; 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 protected boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(),new MyPackage() ); } }; @Override public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } }
MyModule.java
package com.ywq; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; /** * Created by Administrator on 2016/10/30. */ public class MyModule extends ReactContextBaseJavaModule { public MyModule(ReactApplicationContext reactContext) { super(reactContext); //给上下文对象赋值 Test.myContext=reactContext; } @Override public String getName() { return "MyModule"; } @ReactMethod public void NativeMethod() { //调用Test类中的原生方法。 new Test().fun(); } }
MyPackage.java
package com.ywq; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.JavaScriptModule; 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; /** * Created by Administrator on 2016/10/30. */ public class MyPackage implements ReactPackage { @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules=new ArrayList<>(); modules.add(new MyModule(reactContext)); return modules; } @Override public List<Class<? extends JavaScriptModule>> createJSModules() { return Collections.emptyList(); } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } }
Test.java
package com.ywq; import android.provider.Settings; import android.support.annotation.Nullable; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; /** * Created by Administrator on 2016/10/30. */ public class Test { //定义上下文对象 public static ReactContext myContext; //定义发送事件的函数 public void sendEvent(ReactContext reactContext,@Nullable WritableMap params) { System.out.println("reactContext="+reactContext); reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(eventName,params); } public void fun() { //在该方法中开启线程,并且延迟3秒,然后向JavaScript端发送事件。 new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //发送事件,事件名为EventName WritableMap et= Arguments.createMap(); sendEvent(myContext,et); } }).start(); } }
前端index.android.js代码如下:
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React,{ Component } from 'react'; import { AppRegistry,StyleSheet,Text,DeviceEventEmitter,NativeModules,View } from 'react-native'; export default class ywq extends Component { componentWillMount(){ //监听事件名为EventName的事件 DeviceEventEmitter.addListener('EventName',function() { alert("send success"); }); } constructor(props) { super(props); this.state = { content: '这个是预定的接受信息',} } render() { return ( <View style={styles.container}> <Text style={styles.welcome} onPress={this.callNative.bind(this)} > 当你点我的时候会调用原生方法,原生方法延迟3s后会向前端发送事件。 前端一直在监听该事件,如果收到,则给出alert提示! </Text> <Text style={styles.welcome} > {this.state.content} </Text> </View> ); } callNative() { NativeModules.MyModule.NativeMethod(); } } 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('ywq',() => ywq);
运行结果如下所示:
点击之前:
再说一个值得注意的地方,一般我们在接收到原生模块主动发来的事件时,都会进行一些操作,如更新UI,而不仅仅是弹出alert 。
例如我们需要更新UI,代码如下:
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React,View } from 'react-native'; export default class ywq extends Component { componentWillMount(){ //监听事件名为EventName的事件 DeviceEventEmitter.addListener('EventName',function() { this.showState(); alert("send success"); }); } constructor(props) { super(props); this.state = { content: '这个是预定的接受信息',} } render() { return ( <View style={styles.container}> <Text style={styles.welcome} onPress={this.callNative.bind(this)} > 当你点我的时候会调用原生方法,原生方法延迟3s后会向前端发送事件。 前端一直在监听该事件,如果收到,则给出alert提示! </Text> <Text style={styles.welcome} > {this.state.content} </Text> </View> ); } callNative() { NativeModules.MyModule.NativeMethod(); } showState() { this.setState({content:'已经收到了原生模块发送来的事件'}) } } const styles = StyleSheet.create({ container: { flex: 1,() => ywq);
很明显,我们的本意是:当收到事件时,改变一个文本框的内容,即更新UI。
运行结果如下,说明在此function中不能使用this,也就是我们并不能更新UI。
那我们能做到在接收到事件后更新UI等后续操作吗?
能!!!
如何做?
答:使用胖箭头函数(Fat arrow functions)。
胖箭头函数,又称箭头函数,是一个来自ECMAScript 2015(又称ES6)的全新特性。有传闻说,箭头函数的语法=>,是受到了CoffeeScript 的影响,并且它与CoffeeScript中的=>语法一样,共享this上下文。
箭头函数的产生,主要由两个目的:更简洁的语法和与父作用域共享关键字this。
具体给参考 JavaScript ES6箭头函数指南
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React,View } from 'react-native'; export default class ywq extends Component { componentWillMount(){ //监听事件名为EventName的事件 DeviceEventEmitter.addListener('EventName',()=> { this.showState(); alert("send success"); }); } constructor(props) { super(props); this.state = { content: '这个是预定的接受信息',() => ywq);
代码的不同之处就是使用 ()=>来替代function()
运行结果如下,由图可以看出,我们文本框中的内容已经发生了改变,成功更新了UI界面。
至此,实现了原生模块主动向JavaScript发送事件,并且实现了接收事件之后的一些更新UI等操作。
如果不懂React-Native如何复用原生函数,请查看本博客这篇文章。
React-Native开发之原生模块封装(Android)升级版
本博客源码详见github:https://github.com/chaohuangtianjie994/React-Native-Send-Event-from-Native-Module