前面React Native编译常见问题解决和官方demo分析一文介绍了FaceBook的RN官方demo,初步介绍了官方demo的一些重要的模块及其作用。这一节就来讲解下如何在js端调用安卓原生模块,以及js端和安卓原生模块之间的互相通信的几种方式和他们的特点。因为对于任何一个跨语言的开发框架来讲,在开发过程中2端之间的相互调用和消息传递是非常基础和也是非常重要的。就像NDK开发(java和c/c++互相调用)和WebView使用一样(java和js互相调用)。
在JS端调用安卓原生模块
这里以改造官方demo为例,因为自己接触RN是因为我们的验证码SDK有个客户希望能给一个RN的demo,所以这里以在js代码中点击登陆按钮调用易盾验证码SDK为例进行讲解。期望达到的效果是:
- 点击js代码中的登陆按钮,显示验证码界面(js端调用安卓原生模块)
- 滑动验证码验证通过,js端弹出一个“验证成功”提示框,反之弹出“验证失败”提示框(安卓端与js端通信)
这个demo能够很好的把js端调用安卓原生模块(验证码SDK)和安卓端向js端传递消息(告知验证是否通过)包含进来。打个广告,欢迎大家使用网易易盾验证码SDK服务进行行为式验证,告别繁琐的输入字符验证:http://dun.163.com/trial/jigsaw
首先修改demo的App.js。添加一个按钮以及监听事件。
export default class App extends Component<Props> {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit App.js
</Text>
<Text style={styles.instructions}>
{instructions}
</Text>
//增加一个登陆按钮
<Button
onPress={onButtonPress}
title="登录"
color="#841584"
/>
</View>
);
}
}
//定义按钮的响应事件,在该函数调用安卓端的验证码SDK
const onButtonPress = () => {
//Alert.alert('Button has been pressed!');
};
在登陆按钮的事件响应函数中调用安卓端的SDK,所以接下来就是在安卓端创建要被js调用的原生模块。
- 创建一个原生模块类继承自ReactContextBaseJavaModule抽象类,实现getName方法,返回该原生模块的模块名。(该方法的返回值即为需要在JS代码中引入的模块名)
- 在需要被JS代码调用的方法处加上@ReactMethod注解
public class CaptchaModule extends ReactContextBaseJavaModule {
private CaptchaHelper captchaHelper = null;
public CaptchaModule(ReactApplicationContext reactContext) {
super(reactContext);
captchaHelper = new CaptchaHelper();
}
@Override
public String getName() {
return "CaptchaHelper"; //返回需要在js端被引入的模块名
}
@ReactMethod //加上该注解,表面该方法时一个RN方法,可以被js端调用
public void showCaptcha() {
captchaHelper.init(getCurrentActivity());
captchaHelper.show();
}
}
- 创建一个包层次的模块管理类实现ReactPackage接口,实现该接口的2个方法。在createNativeModules方法中将前面创建的模块类添加到包模块管理List中
public class CaptchaPackageManager implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new CaptchaModule(reactContext));//添加前面创建的验证码模块类
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
- 在demo的MainApplication的相应位置将前面创建的包管理模块添加进去
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(), new CaptchaPackageManager()//添加包管理模块
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}
之间的层次关系可以这么理解:
- 继承自ReactContextBaseJavaModule的原生模块相当于java端的需要被js端调用的类模块,在这个模块中返回该模块希望被js端调用的类模块名(实现getName方法)和希望被js端调用的函数(在函数上面加上@ReactMethod注解)
- 实现了ReactPackage的原生模块相当于java端的包层次模块,在这个模块中添加前面创建的类模块
- 在MainApplication内部类ReactNativeHost的getPackages方法中添加前面创建的包管理模块
- 引入原生模块NativeModules
import {
Platform,
StyleSheet,
Text,
View,
Alert,
Button,
NativeModules,//引入RN的原生模块
} from 'react-native';
- 调用安卓端相应模块的RN方法
const onButtonPress = () => {
NativeModules.CaptchaHelper.showCaptcha(); //CaptchaHelper是在安卓端注册的模块,showCaptcha是安卓端添加注解的RN方法
}
2端之间的相互通信
[wp_table id=826/]
2端之间的消息传递方式可以概括为以上3种方式,我们一个一个来看。
- 在安卓端需要被JS调用的ReactMethod中增加Callback这个对象参数,在需要发送消息的地方使用Callback的invoke方法传递消息。该函数定义如下:
public void invoke(Object... args);
也就是该方法的参数是一个对象数组。因此如果我们有多个消息要传递,直接把多个消息对象作为参数传入调用即可。
@ReactMethod
public void showCaptcha(Callback callback) {//在RN方法中增加Callback参数
captchaHelper.init(getCurrentActivity(),callback);
captchaHelper.show();
}
- 在需要发送消息的地方调用invoke方法
public void onValidate(String result, String validate, String message) {
//验证结果,valiadte,可以根据返回的三个值进行用户自定义二次验证
if (validate.length() > 0) {
mCallback.invoke("success");
} else {
mCallback.invoke("error");
}
- 在JS端调用时传入回调函数接收回调消息.
const onButtonPress = () => {
NativeModules.CaptchaHelper.showCaptcha(msg=>{ //调用安卓原生模块时传入回调函数
if(msg== "success"){
Alert.alert("提示","验证通过");
}else{
Alert.alert("提示","验证失败");
}
});
};
回调函数方式,JS端是主动调用方,安卓端是被动消息发送方。对于验证码验证而言,安卓端可能会多次触发验证结果,即多次调用invoke方法。但是因为JS端只调用了一次原生模块函数。所以只会回调一次。因此对于验证码而言,不大合适使用回调方式从安卓端向js端传递消息。
在JS端建立事件监听函数,在安卓端调用RN提供的事件发送API发送事件。
安卓端代码
private void sendMsgToJs(String msg) { ((MainActivity)mContext).getReactContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit("onValidate", msg); //对onValidate事件发送msg消息 }js端代码
componentWillMount(){ //监听事件名为onValidate的事件 DeviceEventEmitter.addListener('onValidate', function(msg) { if(msg== "success"){ Alert.alert("提示","验证通过"); }else{ Alert.alert("提示","验证失败"); } }); }
事件发送方式安卓端是主动方,js端是被动监听方,所以可以进行多次消息传递。而且这种方式无需作为RN函数的参数,所以代码耦合性低。
将安卓原生模块声明的RN方法的最后一个参数定义为Promise对象,则在JS端调用该RN方法时会返回一个Promise对象。在JS端调用该方法的时候一般会配合ES7标准的async/await语法。安卓端在需要发送消息的地方使用Promise的resolve方法传递消息。关于Promise的介绍可以参看:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises
安卓端代码
在RN方法的最后一个参数处添加Promise参数@ReactMethod public void showCaptcha(Promise promise) {//最后一个参数需要为Promise对象 captchaHelper.init(getCurrentActivity(),promise); captchaHelper.show(); }在需要发送消息的地方使用Promise的resolve方法传递消息
public void onValidate(String result, String validate, String message) { //验证结果,valiadte,可以根据返回的三个值进行用户自定义二次验证 if (validate.length() > 0) { mPromise.resolve("success"); } else { mPromise.resolve("failed"); } }js端代码
调用RN函数,该函数会返回一个Promise对象,从该对象中得到安卓端发送过来的消息const onButtonPress = async () => { var msg= await NativeModules.CaptchaHelper.showCaptcha();//配合await否则会直接执行后面语句,而且await必须和async同时使用 if(msg== "success"){ Alert.alert("提示","验证通过"); }else{ Alert.alert("提示","验证失败"); } };
最终demo效果及源码
文章评论