明天和意外你永远都不知道哪一个先来,编程界亦是如此。例如某个已经有原生代码开发模块的项目要求用RN扩张某些功能;又例如,RN中未封装到的组件非得求助于原生代码。所以RN与原生代码通讯对于混合编程是至关重要的。为了实现两者之间的通信,facebook也提供了三种通信方式。
- RCTDeviceEventEmitter消息机制:由Native主导控制,可以任意时刻传递
- Callback回调方式:由js代码调用,原生代码返回。
- Promise机制方式:由js调用,只是每次使用都需要调用。
一、RN调用安卓代码(简单)
RN调用安卓原生的代码,大致分为如下几步。
1、用Android Studio打开一个已经创建好的RN项目,选择android/build.gradle文件。
2、创建一个类继承ReactContextBaseJavaModule,在该类中我们应该要暴露出一些让RN调用的方法,封装成一个原生模块。
- 新建一个类MyReactPackage继承ReactContextBaseJavaModule
public class MyNativeModule extends ReactContextBaseJavaModule{
}
- 实现getName方法。该方法用于返回RN代码需要寻找的类的名称。(alt+enter快捷键可快速实现父类方法)
@Override
public String getName() {
// 一定要有名字 RN代码要通过名字来调用该类的方法
return "ToastModule";
}
- 实现类的构造方法。在这里将传入的上下文赋值给类内部私有的上下文
// 创建一个上下文,放到构造函数中,得到reactContext
private ReactApplicationContext mContext;
public MyNativeModule(ReactApplicationContext reactContext){
super(reactContext);
mContext = reactContext;
}
- 创建暴露给RN调用的方法。但是要用注释符号@ReactMethod修饰。
//方法不能返回值 因为被调用的原生代码是异步的 原生代码执行结束之后只能通过回调函数或者发送消息给RN
@ReactMethod
public void rnCallNative(String msg){
//这个方法是说弹出一个弹窗到界面Toast.makeText(mContext,msg,Toast.LENGTH_LONG).show();
}
3、在原生代码中创建一个类实现接口ReactPackage包管理器,并且把第二步已经创建好的类加入到原生模块列表里。
实现接口ReactPackage的方法,当然我们现在只需要创建模块,所以在其他的实现先直接返回空集合即可。而在createNativeModules方法中,要先声明一个装有原生模块的列表。之后将原生模块加入到列表里。
public class MyReactPackage implements ReactPackage {
@Override
public List createNativeModules(ReactApplicationContext reactContext) {
List modules = new ArrayList<>();
modules.add(new MyNativeModule(reactContext));
return modules;
}
@Override
public List createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
4、将创建好的包管理器添加到ReactPackage列表里,也就是MainApplication代码中
在类里找到方法getPackages方法,将包管理器添加进去。
@Override
protected List getPackages() {
return Arrays.asList(
new MainReactPackage(),
new MyReactPackage()
);
}
5.在RN代码中用NativeModules组件去调用原生模块
- 导入组件
import {
AppRegistry,
StyleSheet,
Text,
View,
NativeModules,
} from 'react-native';
- 设置方法调用原生代码
call_button(){
NativeModules.ToastModule.rnCallNative('RN与安卓开发');
}
- 布置UI
在render方法里面设置当用户点击文字时,调用自定义的方法call_button。并且以这种形式创建的方法需要进行绑定
render() {
return(
测试原生通讯
);
}
- 处理好样式
const styles = StyleSheet.create({
container: {
flex:1,
backgroundColor:'deeppink',
flexDirection:'row',
justifyContent:'center',
alignItems:'center' },
});
至此,基本的RN调用安卓原生代码的方式就得以实现。再从RN的角度来回看整个过程。RN调用原生的方法,此时安卓的application就会启动,完成之后它会去找Package的列表,进而找到自己创建的列表。而在组件的列表里面有一个原生模块列表,到自己的模块列表里面调用模块里的方法就完成了调用。
效果图如下:
二、RN用消息机制方式与安卓原生代码切换
实现效果:在原生代码中添加一个按钮,当用户从RN界面调用原生代码就会进入到原生代码开发的界面中,而点击原生代码中的按钮就会返回到RN界面。
接上一节的代码。
1、在与MainApplication同级的目录下创建一个Activity。Activity是android系统最小的调度单位。
创建名称为MyActivity的空活动。它会帮助我们生成一个自动布局文件做布局的工作。(此时若遇到错误,可以选择build->clean)
2、command+enter点击进入activity_my中,此时会打开布局文件。将左下角的Design切换成Text文件。在该文件中,为原生界面创建一个按钮并且布局。
xmlns:android表示设置xmlns的命名空间,没有这句话就无法设置属性的约束。
在该界面上创建一个按钮,为按钮绑定一个方法onBack。
3、回到新创建的MyActivity代码,实现onBack方法。
//点击按钮,直接完成
public void onBack(View v){
finish();
}
4、到MyNativeModule原生模块中去实现Activity
上文中已经说过,Activity是android系统的最小调度单位,而Intent则是安卓的进程之间、activity之间、线程之间交换数据的载体。
//方法不能返回值 因为被调用的原生代码是异步的 原生代码执行结束之后只能通过回调函数或者发送消息给RN
@ReactMethod
public void rnCallNative(String msg){
Toast.makeText(mContext,msg,Toast.LENGTH_LONG).show();
Intent intent = new Intent(mContext,MyActivity.class); //创建一个意图,意图是android进程之间、线程之间、交换数据的载体
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //一定要加上这句
mContext.startActivity(intent);
}
效果图如下:
三、RN用Promise机制与安卓原生代码通信
使用Promise机制也是RN与原生通信的一种方式。在原生代码的MyNativeModule文件中创建桥接方法。当桥接的原生方法的最后一个参数是一个Promise对象,那么该方法会返回一个JS的Promise对象给与之对应的js方法。
与上文类似,需要暴露给RN的方法不能有返回值,并且要以注释@ReactMethod标识。
@ReactMethod
public void rnCallNative_promise(String msg,Promise promise){
Toast.makeText(mContext,msg,Toast.LENGTH_SHORT).show();
//得到组件名称
String componentName = getCurrentActivity().getComponentName().toString();
promise.resolve(componentName);
}
原生代码已经实现。接下来要实现的就是RN的代码。在RN中创建一个方法,这个方法内部使用NativeModules组件来调用原生模块提供的名称,进而找到要调用的原生方法。原生方法最后一个参数是一个promise,所以在js中用.then的方法实现即可。
callAndroid_promise(){
NativeModules.ToastModule.rnCallNative_promise('promise调用原生').then(
(msg) => {
console.log('promise收到消息:'+msg);
}
).catch(
(err)=>{
console.log(err);
}
)
}
到渲染方法中,调用方法
Promise通信
给Text组件设置样式
welcome: {
fontSize: 16,
textAlign: 'left',
margin: 10
}
结果图如下:
用Debug进行调试,得到结果如下:
四、RN用callback回调方式与安卓原生代码通信
按照上文中提到的方式,在原生模块中暴露一个桥接方法给RN调用。
参数传入一个成功的回调和一个失败的回调。
@ReactMethod
public void measureLayout(Callback errorCallback,Callback successCallback){
try {
successCallback.invoke(100,100,200,200); //调用回调函数,返回结果
}catch (IllegalViewOperationException e){
errorCallback.invoke(e.getMessage());
}
}
在js中实现回调方法。同样是通过NativeModules组件寻找到桥接名称ToastModule,进而找到想要调用的方法。拿到返回的参数,做功能处理。
callAndroid_callback(){
NativeModules.ToastModule.measureLayout(
(msg)=>{
console.log(msg);
},
(x,y,width,height)=>{
console.log('x坐标:'+x+'y坐标:'+y+'高:'+height+'宽'+width);
}
)
}
在Debug调试下,点击callback通信文字,可以看到如下结果:
在使用回调函数时会呈现出某些缺点,比如说每次调用只应当调用一次,多次调用可能会出现意想不到的结果,并且用这种方法安卓原生代码是无法主动发送信息给RN侧的。而消息机制的方式就可以进行消息的互相传递。