• ReactNative进阶(五):React Native与原生通信


    一 、前言

    RN项目开发阶段,需要实现RN与原生应用间进行数据通讯。

    二、RN与安卓通信

    2.1 RN 调用安卓原生组件

    1. 用Android Studio打开一个已经创建好的RN项目,选择android/build.gradle文件。
    2. 创建一个类MyNativeModule继承ReactContextBaseJavaModule,暴露出一些让RN调用的方法,封装成一个原生模块。
    public class MyNativeModule extends ReactContextBaseJavaModule{
    }
    
    • 1
    • 2

    实现getName方法,用于返回RN代码需要寻找的类的名称。

    //RN代码要通过名字来调用该类的方法
    @Override
    public String getName() {
        return "ToastModule";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实现类的构造方法,将传入的上下文赋值给类内部私有的上下文。

    // 创建一个上下文,放到构造函数中,得到reactContext
    private ReactApplicationContext mContext;
    public MyNativeModule(ReactApplicationContext reactContext){
       super(reactContext);
       mContext = reactContext;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建暴露给RN调用的方法,需要用注释符号@ReactMethod修饰。

    //方法不能返回值。因为被调用的原生代码是异步的。原生代码执行结束之后只能通过回调函数或者发送消息给RN。
    @ReactMethod
    public void rnCallNative(String msg){
      //这个方法是说弹出一个弹窗到界面Toast.makeText(mContext,msg,Toast.LENGTH_LONG).show();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 在原生代码中创建一个类实现接口ReactPackage包管理器,并且把第二步已经创建好的类加入到原生模块列表里。
    public class MyReactPackage implements ReactPackage {
    	@Override
    	public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
    	    List<NativeModule> modules = new ArrayList<>();
    	    modules.add(new MyNativeModule(reactContext));
    	    return modules;
    	}
    	@Override
    	public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    	    return Collections.emptyList();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 将创建好的包管理器添加到ReactPackage列表里,也就是MainApplication代码中,在类里找到方法getPackages方法,将包管理器添加进去。
    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
              new MyReactPackage()
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 在RN代码中用NativeModules组件去调用原生模块。
      导入组件
    import {
        AppRegistry,
        StyleSheet,
        Text,
        View,
        NativeModules,
    } from 'react-native';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    设置方法调用原生代码

    call_button(){
        NativeModules.ToastModule.rnCallNative('RN与安卓开发');
    }
    
    • 1
    • 2
    • 3

    布置UI 在render方法里面设置当用户点击文字时,调用自定义的方法call_button。并且以这种形式创建的方法需要进行绑定。

    render() {
       return(
           <View style={styles.container}>
               <Text  onPress={this.call_button.bind(this)}>测试原生通讯</Text>
           </View>
       );
    }
    ....
    const styles = StyleSheet.create({
       container: {
           flex:1,
           backgroundColor:'deeppink',
           flexDirection:'row',
           justifyContent:'center',
           alignItems:'center'    },
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    RN调用原生的方法,此时安卓的application就会启动,完成之后它会去找Package列表,进而找到自己创建的列表。而在组件的列表里面有一个原生模块列表,到自己的模块列表里面调用模块里的方法就完成了调用。

    2.2 RN 应用消息机制方式与安卓原生代码切换

    在原生代码中添加一个按钮,当用户从RN界面调用原生代码就会进入到原生代码开发的界面中,而点击原生代码中的按钮就会返回到RN界面。

    实施步骤如下:

    1. 在与MainApplication同级的目录下创建一个MyActivity,会自动生成一个自动布局文件activity_my做布局的工作。
    2. 打开布局文件,将左下角的Design切换成Text文件,为原生界面创建一个按钮并且布局。
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
       android:layout_width = "match_parent"
       android:layout_height="match_parent"
       xmlns:android = "http://schemas.android.com/apk/res/android"
       >
    
       <Button
           android:text="goBack"
           android:onClick="onBack"
           android:layout_centerInParent="true"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           />
    </RelativeLayout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 回到新创建的MyActivity代码,实现onBack方法。
    //点击按钮,直接完成
    public void onBack(View v){
        finish();
    }
    
    • 1
    • 2
    • 3
    • 4
    1. 在MyNativeModule原生模块中去实现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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.3 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在RN中创建一个方法,这个方法内部使用NativeModules组件来调用原生模块提供的名称,进而找到要调用的原生方法。原生方法最后一个参数是一个promise,所以在js中用.then的方法实现即可。

    callAndroid_promise(){
       NativeModules.ToastModule.rnCallNative_promise('promise调用原生').then(
               (msg) => {
                   console.log('promise收到消息:'+msg);
               }
           ).catch(
               (err)=>{
                   console.log(err);
               }
           )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    渲染方法中,调用方法。

    <Text style={styles.welcome} onPress={this.callAndroid_promise.bind(this)}>Promise通信</Text>
    ....
    welcome: {
        fontSize: 16,
        textAlign: 'left',
        margin: 10
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.4 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());
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在js中实现回调方法。同样是通过NativeModules组件寻找到桥接名称ToastModule,进而找到想要调用的方法。拿到返回的参数,做功能处理。

    callAndroid_callback(){
       NativeModules.ToastModule.measureLayout(
           (msg)=>{
               console.log(msg);
           },
           (x,y,width,height)=>{
               console.log('x坐标:'+x+'y坐标:'+y+'高:'+height+'宽'+width);
           }
       )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在使用回调函数时会呈现出某些缺点,比如说每次调用只应当调用一次,多次调用可能会出现意想不到的结果,并且用这种方法安卓原生代码是无法主动发送信息给RN侧的。

    三、RN与IOS通信

    首先 RN 与 IOS 通信,在原生端需创建一个bridge ,并遵守协议。

    #import <React/RCTBridgeModule.h>
    @interface PushModule : NSObject<RCTBridgeModule>
    
    • 1
    • 2

    其次,在@implementation中导出 Module

    @implementation PushModule
    RCT_EXPORT_MODULE()
    
    • 1
    • 2

    有以下3种通信方式。

    3.1 定义导出的方法名

    RCT_EXPORT_METHOD(pushEvent:(NSString *)event callback:(RCTResponseSenderBlock)callback){
      NSLog(@"----对React Native提供调用方法,Callback---%@",event);
      NSString *callbackData = @"原生数据被RN调用"; //准备回调回去的数据
      callback(@[[NSNull null],callbackData]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    定义一个方法后,RN 就可通过NativeModules获取到对于 Module后调用相应方法,event是 RN 传给 IOS 的值,IOS 这边可通过 callback 这个 block 回调给 RN 数据。

    callBackEvent (){
    	NativeModules.PushModule.pushEvent(('RN->原生的数据'),(error, events) => {
    		if (error) {
    			console.log(error);
    		}else {
    			alert(events)
    		}
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.2 promise 实现回调函数

    static RCTPromiseResolveBlock _resolve;//成功回调
    static RCTPromiseRejectBlock _reject;//失败回调
    
    RCT_REMAP_METHOD(pushPromisesEvent,
                     resolver:(RCTPromiseResolveBlock)resolve
                     rejecter:(RCTPromiseRejectBlock)reject){
      _resolve = resolve;
      _reject = reject;
     }
    
    //异步回调函数
    +(void) handleResult:(id)result{
      //原生Promises数据被RN调用
      if ([result isEqualToString:@"获取数据成功"]) {
         _resolve(@[result]);
      }else{
        //返回错误信息
        NSError *error=[NSError errorWithDomain:result code:101 userInfo:nil];
           _reject(@"no_events", @"There were no events", error);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    RN 侧方法调用。

    NativeModules.PushModule.pushPromisesEvent().then((events)=>{
    alert(events+1111)
    }).catch((e)=>{
    // alert(e)
            console.log("错误信息------"+e);
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.3 继承 RCTEventEmitter 类

    #import <React/RCTEventEmitter.h>
    #import <React/RCTBridgeModule.h>
    @interface PushModule : RCTEventEmitter<RCTBridgeModule>
    
    • 1
    • 2
    • 3

    .m 的实现中有几个继承方法需要实现。

    //IOS 回传给 RN 的通知方法
    - (NSArray*)supportedEvents{
      return @[@"Notice_name"]
    }
    
    - (void)startObserving
    {
      [PushTool sharedPushTool].isReady = YES;
      [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector(notice:)
                                                   name:@"event-notice"
                                                 object:nil];
    }
    
    -(void)notice:(NSNotification*)notification
    {
      NSDictionary*obj = notification.object;
      [self sendEventWithName:@"Notice_name" body:obj];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在 RN 侧实施监听。

    var module = new NativeEventEmitter(NativeModules.PushModule);
    module.addListener('Notice_name',(data)=>this.message(data));
    
    • 1
    • 2

    四、拓展阅读

  • 相关阅读:
    Python实践:脚本调用exe与exe输出获取的方法总结
    20240301-1-ZooKeeper面试题(一)
    Java异常、继承结构、自定义异常、SpringBoot中捕获处理异常
    力扣(LeetCode)864. 获取所有钥匙的最短路径(C++)
    [附源码]计算机毕业设计springboot基于Java的员工管理系统
    【面试普通人VS高手系列】Redis和Mysql如何保证数据一致性
    代码随想录算法训练营day56|583. 两个字符串的删除操作|72. 编辑距离
    2023-2028年中国硫酸钇市场发展态势及前景预测报告
    探索k8s集群的存储卷 emptyDir hostPath nfs
    Flyway报错源码分析——Validate failed: Migrations have failed validation
  • 原文地址:https://blog.csdn.net/sunhuaqiang1/article/details/136560915