前言
之前对ReactNative有过研究,恰好近期公司想在节约成本的前提下,进一步提升App的使用体验,相比起某些页面嵌入H5页面来说,RN的体验更接近原生,加上RN发展这么久生态已经比较庞大,于是决定将RN加入项目实践中。
分析
为了实现React Native与原生App之间的通信,FB实现了自己的一套交互机制,分别是:
- RCTDeviceEventEmitter 事件方式
- Callback 回调方式
- Promise 异步方式
三种方式各具不同优缺点:
1.RCTDeviceEventEmitter
优点:可任意时刻传递,Native主导控制。
2.Callback
优点:JS调用一次,Native返回。
缺点:CallBack为异步操作,返回时机不确定
3.Promise
优点:JS调用一次,Native返回。
缺点:每次使用需要JS调用一次
原生模块
实现三种通信方式前,我们首先去实现原生模块:
第一步:
一个原生模块是一个继承了ReactContextBaseJavaModule
的 Java 类,它可以实现一些 JavaScript 所需的功能。
创建一个新的 Java 类并命名为TransMissonMoudle.java,其代码如下:
public class TransMissonMoudle extends ReactContextBaseJavaModule {
private static final String REACT_CLASS = "TransMissonMoudle";
private ReactContext mReactContext;
public TransMissonMoudle(ReactApplicationContext reactContext) {
super(reactContext);
this.mReactContext = reactContext;
}
@Override
public String getName() {
return REACT_CLASS;
}
}
ReactContextBaseJavaModule
要求派生类实现getName
方法。这个函数用于返回一个字符串名字,这个名字在 JavaScript 端标记这个模块。这里我们把这个模块叫做TransMissonMoudle
,这样就可以在 JavaScript 中通过NativeModules.TransMissonMoudle
访问到这个模块。
第二步:
注册这个模块,我们需要在应用的 Package 类的createNativeModules
方法中添加这个模块。如果模块没有被注册,它也无法在 JavaScript 中被访问到。
创建一个新的 Java 类并命名为TransMissonPackage.java,其代码如下:
public class TransMissonPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new TransMissonMoudle(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
第三步:
这个 package 需要在MainApplication.java
文件的getPackages
方法中提供
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new TransMissonPackage()// <-- 添加这一行,类名替换成你的Package类的名字.
);
}
现在,在别处的 JavaScript 代码中可以这样调用你的方法(本地方法被@ReactMethod才能直接调用),具体的方法我们后面实现:
import { NativeModules } from 'react-native';
NativeModules.TransMissonMoudle.方法
通信交互
1.RCTDeviceEventEmitter
原生模块可以在没有被调用的情况下往 JavaScript 发送事件通知。最简单的办法就是通过RCTDeviceEventEmitter
,这可以通过ReactContext
来获得对应的引用,像这样:
/**
* 1.RCTDeviceEventEmitter方式
* @param reactContext
* @param eventName 事件名
* @param params 传参
*/
public void sendTransMisson(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
@ReactMethod
public void contactWithRTC(String type) {
WritableMap writableMap = new WritableNativeMap();
switch (type){
case "getTime":
writableMap.putString("type",type);
writableMap.putString("age","10");
writableMap.putString("time", getTimeMillis());
break;
default:
break;
}
sendTransMisson(mReactContext, "rnTransMisson", writableMap);
}
JavaScript 模块可以通过使用DeviceEventEmitter
模块来监听事件:
import { DeviceEventEmitter } from 'react-native';
componentWillMount() {
DeviceEventEmitter.addListener('rnTransMisson', (e: Event) => {
this.refs.toast.show("DeviceEventEmitter收到消息:" + "\n" + "年龄:" + msg.age + "时间:" + msg.time, 2000);
});
}
NativeModules.TransMissonMoudle.contactWithRTC("getTime");
2.Callback 回调方式
原生模块还支持一种特殊的参数——回调函数。它提供了一个函数来把返回值传回给 JavaScript。
原生模块通常只应调用回调函数一次。但是,它可以保存 callback 并在将来调用。
请务必注意 callback 并非在对应的原生函数返回后立即被执行——注意跨语言通讯是异步的,这个执行过程会通过消息循环来进行。
/**
* 2.CallBack方式
* @param type
* @param callback
*/
@ReactMethod
public void contactWithCallBack(String type, Callback callback) {
switch (type){
case "getTime":
callback.invoke(getTimeMillis());
break;
default:
break;
}
}
这个函数可以在 JavaScript 里这样使用:
NativeModules.TransMissonMoudle.contactWithCallBack("getTime",
(msg) => {
this.refs.toast.show("CallBack收到消息:" + "\n" + msg, 2000);
}
);
3.Promise
原生模块还可以使用 promise 来简化代码,搭配 ES2016(ES7)标准的async/await
语法则效果更佳。如果桥接原生方法的最后一个参数是一个Promise
,则对应的 JS 方法就会返回一个 Promise 对象。
我们把上面的代码用 promise 来代替回调进行重构:
/**
* 3.Promise方式
* @param type
* @param promise
*/
@ReactMethod
public void contactWithPromise(String type, Promise promise) {
switch (type) {
case "getTime":
WritableMap writableMap=new WritableNativeMap();
writableMap.putString("age","30");
writableMap.putString("time",getTimeMillis());
promise.resolve(writableMap);
break;
default:
break;
}
}
现在 JavaScript 端的方法会返回一个 Promise。这样你就可以在一个声明了async
的异步函数内使用await
关键字来调用,并等待其结果返回。(虽然这样写着看起来像同步操作,但实际仍然是异步的,并不会阻塞执行来等待)。
NativeModules.TransMissonMoudle.contactWithPromise("getTime")
.then(msg=> {
this.refs.toast.show("Promise收到消息:" + "\n" + "年龄:" + msg.age + "时间:" + msg.time, 2000);
}).catch(error=> {
console.log(error);
});
至此,三种通信方式已经介绍完了。
按照惯例,上源码。
https://github.com/tolvgx/RNTransMisson