RN系列:Android原生与RN如何交互通信

【简述RN集成到Android原生项目】
【Android项目集成RN系列:RN使用Android原生控件或自定义组件】
【React Native Linking与 Android原生页面路由跳转问题】

Android与RN通信.png

React Native在Android混编项目中的页面跳转和方法调用大致可以通过上面这张简图来描述下:

一、页面跳转(RN与Android原生)

  1. RN页面跳转原生
    • 方式一:
      通过下面即将讲述的方法调用实现,通过在RN中调用 NativeModule中暴露的方法,来实现跳转原生的指定页面。(此处不再细述)
    • 方式二:
      通过Scheme路由形式跳转,RN提供的Linking,可根据路由跳转到指定页面,可参考【React Native Linking与 Android原生页面路由跳转问题】
  2. 原生跳转RN页面
    通常我们加载RN页面是根据RN bundle文件的入口js文件(默认index.android.js,可自定义指定入口js文件)中暴露的ModuleName去加载入口页面,就好比我们Android中设置的启动页面一样,我们再在启动页面根据相关逻辑跳转相关页面。那么这里可能就涉及到了Android如何传递路由或者参数给RN?以及RN如何获取原生提供的路由与参数?通过查看Linking源码发现RN分别针对Android和IOS进行了封装映射,我们只需要把数据传送到对应的位置即可,
    const LinkingManager =
        Platform.OS === 'android'
        ? NativeModules.IntentAndroid : NativeModules.LinkingManager;
    
    在Android中对应的是IntentAndroid,查看对应的源码:
    /**
     * Return the URL the activity was started with
     *
    * @param promise a promise which is resolved with the initial URL
    */
     @ReactMethod
     public void getInitialURL(Promise promise) {
       try {
         Activity currentActivity = getCurrentActivity();
         String initialURL = null;
    
         if (currentActivity != null) {
           Intent intent = currentActivity.getIntent();
           String action = intent.getAction();
           Uri uri = intent.getData();
    
           if (Intent.ACTION_VIEW.equals(action) && uri != null) {
             initialURL = uri.toString();
           }
         }
    
         promise.resolve(initialURL);
       } catch (Exception e) {
         promise.reject(new JSApplicationIllegalArgumentException(
             "Could not get the initial URL : " + e.getMessage()));
       }
     }
    
    通过上面源码也就了解了Android与RN如何根据Linking进行页面跳转,我先按照这种形式在Android上进行测试发现可行,当然也有bug,就是拿的时候可能为空,调查发现RN与原生页面装载之前就调用这个方法导致拿不到上下文,只要创建rootview就会执行rn的生命周期,而此时RN与原生还没有绑定,后来发现RN是能监听到原生页面的活动状态,此时再去获取路由即可。详细内容可参考React Native Linking与 Android原生页面路由跳转实现

二、 方法调用

RN通信原理简单地讲就是,一方native(java)将其部分方法注册成一个映射表,另一方(js)再在这个映射表中查找并调用相应的方法,而Bridge担当两者间桥接的角色。
其实方法调用大致分为2种情况:

  • Android主动向JS端传递事件、数据
  • JS端主动向Android询问获取事件、数据

RN调用Android需要module名和方法名相同,而Android调用RN只需要方法名相同。
(1)RCTDeviceEventEmitter 事件方式
​ 优点:可任意时刻传递,Native主导控制。
(2)Callback 回调方式
​ 优点:JS调用,Native返回。
​ 缺点:CallBack为异步操作,返回时机不确定
(3)Promise
​ 优点:JS调用,Native返回。
​ 缺点:每次使用需要JS调用一次
(4)直传常量数据(原生向RN)
​ 跨域传值,只能从原生端向RN端传递。RN端可通过 NativeModules.[module名].[参数名] 的方式获取。

注意:RN层调用NativeModule层进行界面跳转时,需要设置FLAG_ACTIVITY_NEW_TASK标志。

例如下分别以原生调用RNRN调用原生为例简单描述下:

  1. 原生调用RN
    下面是RCTDeviceEventEmitter事件的简单事例,稍后封装下更方便与原生的通信交互。
    public class EventEmitter {
        private static final String TAG = "EventEmitter";
    // 在ReactPackage中的createNativeModules()初始化,RNEventEmitter.setReactContext(reactContext);
        private static ReactApplicationContext mReactContext;
    
        public static void setReactContext(ReactApplicationContext mReactContext) {
            RNEventEmitter.mReactContext = mReactContext;
        }
        /**
         * 显示RN中loading
         * @param data show:显示,false:隐藏
       */
      public static void showLoading(boolean show) {
          sendEventToRn("showloading", show);
      }
      public static void sendEventToRn(String eventName, Object msg) {
          if (mReactContext == null) {
              Log.e(TAG, "ReactContext is null");
              return;
          }
          mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName,msg);
        }
    }
    
    RN中接收原生消息:
     /**
      * 接收原生调用
      */
     componentDidMount() {
       DeviceEventEmitter.addListener('showLoading',(msg)=>{
            ToastAndroid.show("发送成功"+msg, ToastAndroid.SHORT);
       })
       //通过DeviceEventEmitter注册监听,类似于Android中的监听事件。第一个参数标识名称,要与Module中emit的Event Name相同。第二个参数即为处理回调时间。
     }
    
  2. RN调用原生
  • 创建MyNativeModule.java
    package com.liujc.rnappdemo.hybride;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.support.annotation.Nullable;
    import android.text.TextUtils;
    import android.util.Log;
    import android.widget.Toast;
    
    import com.facebook.react.bridge.Callback;
    import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
    import com.facebook.react.bridge.Promise;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContextBaseJavaModule;
    import com.facebook.react.bridge.ReactMethod;
    import com.facebook.react.modules.core.DeviceEventManagerModule;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 类名称:MyNativeModule
     * 创建者:Create by liujc
     * 描述:原生预留给RN的调用方法
     */
    public class MyNativeModule extends ReactContextBaseJavaModule {
        public static final String REACT_NATIVE_CLASSNAME = "MyNativeModule";
        private ReactApplicationContext mContext;
        public static final String EVENT_NAME = "nativeCallRn";
        public static final String TAG = "TAG";
    
        public MyNativeModule(ReactApplicationContext reactContext) {
            super(reactContext);
            mContext = reactContext;
        }
    
        @Override
        public String getName() {
         //返回的这个名字是必须的,在rn代码中需要这个名字来调用该类的方法。
            return REACT_NATIVE_CLASSNAME;
        }
    
     //函数不能有返回值,因为被调用的原生代码是异步的,原生代码执行结束之后只能通过回调函数或者发送信息给rn那边。
        @ReactMethod
        public void rnCallNative(String msg){
            Toast.makeText(mContext,msg,Toast.LENGTH_SHORT).show();
        }
    
        @ReactMethod
        public void startActivityRN(String name, String params) {
            try {
                Activity activity = getCurrentActivity();
                if (activity != null) {
                    Class toClass = Class.forName(name);
                    Intent intent = new Intent(activity, toClass);
                    intent.putExtra("params", params);
                    activity.startActivity(intent);
                }
            } catch (Exception ex) {
                throw new JSApplicationIllegalArgumentException("不能打开Activity " + ex.getMessage());
            }
        }
         //后面该方法可以用Linking代替
        @ReactMethod  
        public void getDataFromIntent(Callback success, Callback error) {
            try {
                Activity currentActivity = getCurrentActivity();
                String result = currentActivity.getIntent().getStringExtra("result");//会有对应数据放入
                if (!TextUtils.isEmpty(result)) {
                    success.invoke(result);
                }
            } catch (Exception ex) {
                error.invoke(ex.getMessage());
            }
        }
    
        /**
         * Native调用RN
         * @param msg
         */
        public void nativeCallRn(String msg) {          
                mContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(EVENT_NAME,msg);
        }
    
        /**
         * Callback 方式
         * rn调用Native,并获取返回值
         * @param msg
         * @param callback
         */
        @ReactMethod
        public void rnCallNativeFromCallback(String msg, Callback callback) {
    
            String result = "hello RN!Native正在处理你的callback请求";
            // .回调RN,即将处理结果返回给RN
            callback.invoke(result);
        }
    
        /**
         * Promise
         * @param msg
         * @param promise
         */
        @ReactMethod
        public void rnCallNativeFromPromise(String msg, Promise promise) {
            Log.e(TAG,"rnCallNativeFromPromise");
            String result = "hello RN!Native正在处理你的promise请求" ;
            promise.resolve(result);
        }
        /**
         * 向RN传递常量
         */
        @Nullable
        @Override
        public Map<String, Object> getConstants() {
            Map<String,Object> params = new HashMap<>();
            params.put("Constant","我是Native常量,传递给RN");
            return params;
        }
    }
    
  • 创建 MyReactPackage.java
    /**
    * 类名称:MyReactPackage
    * 创建者:Create by liujc
    * 描述:RN包管理器
    */
    public class MyReactPackage implements ReactPackage {
    
        public MyNativeModule myNativeModule;
        @Override
        public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
            myNativeModule = new MyNativeModule(reactContext);
            List<NativeModule> modules = new ArrayList<>();
            //将我们创建NativeModule添加进原生模块列表中
            modules.add(myNativeModule);
            return modules;
        }
    
        @Override
        public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
            //该处后期RN调用原生控件或自定义组件时可用到
            return Collections.emptyList(); 
        }
    }
    
  • 将我们创建的MyReactPackage添加到我们在AppApplication中创建的ReactNativeHost中。
    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 MyReactPackage()
             );
         }
     };
    
  • 接下来我们在js文件中如何调用?
    'use strict';
    
    import React, { Component } from 'react';
    import {
      AppRegistry,
      StyleSheet,
      Text,
      NativeModules,
      View,
      ToastAndroid,
      DeviceEventEmitter
    } from 'react-native';
    
    let title = 'React Native界面';
    
    class reactNative extends Component {
    
        /**加载完成*/
        componentWillMount() {
          let result = NativeModules.MyNativeModule.Constant;
          console.log('原生端返回的常量值为:' + result);
        }
    
       /**
        * 原生调用RN
        */
       componentDidMount() {
           DeviceEventEmitter.addListener('nativeCallRn',(msg)=>{
                title = "React Native界面,收到数据:" + msg;
                ToastAndroid.show("发送成功", ToastAndroid.SHORT);
           })
       }
    
      /**
       * RN调用Native且通过Callback回调 通信方式
       */
       callbackComm(msg) {
           NativeModules.MyNativeModule.rnCallNativeFromCallback(msg,(result) => {
           ToastAndroid.show("CallBack收到消息:" + result, ToastAndroid.SHORT);
        })
       }
    
       /**
        * RN调用Native且通过Promise回调 通信方式
        */
       promiseComm(msg) {
           NativeModules.MyNativeModule.rnCallNativeFromPromise(msg).then(
            (result) =>{
                ToastAndroid.show("Promise收到消息:" + result, ToastAndroid.SHORT)
            }
        ).catch((error) =>{console.log(error)});
    }
    
      /**
       * 调用原生代码
       */
       call_button(){
              NativeModules.MyNativeModule.rnCallNative('调用原生方法操作');
       }
    
      callNative(){
           NativeModules.MyNativeModule.startActivityRN('com.liujc.rnappdemo.MyRNActivity','test');
      }
    
     render() {
          return ( 
        <View style={styles.container}>
              <Text style={styles.welcome} >
               {title}
              </Text>
    
              <Text style={styles.welcome}
              onPress={this.call_button.bind(this)}
              >
                React Native 调用原生方法操作!
              </Text>
    
             <Text style={styles.welcome}
                    //给此处的文字绑定一个事件,其中callNative为要调用的方法名。
                      onPress={this.callNative.bind(this)}>
                      跳转MyRNActivity!
              </Text>
    
             <Text style={styles.welcome} onPress={this.callbackComm.bind(this,'callback发送啦')}>
                Callback通信方式
             </Text>
             <Text style={styles.welcome} onPress={this.promiseComm.bind(this,'promise发送啦')}>
                Promise通信方式
             </Text>
         </View>
        );
       }
    }
    
    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('reactNative', () => reactNative);
    
    

三、 RN中加载Gif图片

需要添加facebook的两个图片加载库:(注意版本号尽量与你使用的RN版本内部使用的fresco版本保持一直)

implementation 'com.facebook.fresco:fresco:1.3.0'
implementation 'com.facebook.fresco:animated-gif:1.3.0'
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • 引言 写这篇文章的目的是为了记录自己在试用cocopods时所爬过的坑,也给今后遇到相同情况的小伙伴指条明路。因为...
    Tioks阅读 1,492评论 0 3
  • 由于网络问题,OpenStack官方文档时常不能正常访问或者加载速度非常慢,于是就想办法把文档下载到本地,这样查看...
    向杨垫下阅读 1,154评论 0 1
  • 格奥尔格·威廉·弗里德里希·黑格尔(公元1770年8月27日—公元1831年11月14日), 是德国19世纪唯心论...
    Phantom_醋醋阅读 868评论 0 0
  • 运动是经常有,但是在毕业之前并没有热爱上跑步这件小事。只是觉着运动是必要的应该做的,类似多吃蔬菜少吃肉这样的观点。...
    余小唰阅读 411评论 4 4