RN与原生互相通信

最近负责一个项目。里面语言是用RN开发的,现在想把RN逐步替换为原生进行开发,整理一下关于RN与原生之间相互调用的方法。

简述RN集成到Android原生项目:
https://www.jianshu.com/p/f546ad231382

页面跳转和方法调用

一、页面跳转(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进行了封装映射,我们只需要把数据传送到对应的位置即可,

在Android中对应的是IntentAndroid,查看对应的源码:


通过上面源码也就了解了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 和 RN调用原生 为例简单描述下:

1、原生调用RN

下面是RCTDeviceEventEmitter事件的简单事例,稍后封装下更方便与原生的通信交互。

RN中接收原生消息:

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阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容