汪建军的博客

休迅飞凫,飘忽若神,凌波微步,罗袜生尘。

Android developer, sometimes thinking, sometimes try it.


React Native原生模块与React模块之间的数据传递

我眼中的React Native

关于React Native的介绍与评价网上随处可见,比如知乎,初探RN,在我看来,RN是一种新的开发模式,前端语言写界面,写一次多端可用(Android、Ios、微信),在移动平台转换为原生体验,十分适用于快速迭代的展示型产品,当然热更新在移动端已不是什么稀奇的事,RN的优势在于节约开发成本、测试成本。虽然RN目前比较成熟,但我不认为它是用来取代native的,这也不是Facebook设计的初衷,它更像是对native的补充,一些列表页面、表单页面、需求变化较大的展示型页面用RN会比较合适,过于复杂的页面还不如用native开发更快。所以我也不认为一家公司只有前端人员就能顺利开发出Android、Ios端产品,随便遇到一点原生相关的问题就会被坑死。我本身是个Android开发者,所以在RN项目上我要做的就是写好原生模块以及关心原生模块与React模块之间的通信问题。

RN工程介绍

如果某个项目需要RN介入,意味着Android、Ios端都在一个大工程目录下,配置也是固定的,当然这些配置在创建RN项目的时候会自动生成,按照官网指引搭建一个开发环境,执行完测试安装步骤:

react-native init AwesomeProject

接下来会生成一个名为AwesomeProject的工程,目录结构大概是这样的 打开android目录会发现就是一个Android Studio项目,project层、app层的build.gradle都会自动生成相关配置。我的环境是react-native:0.52.2,buildToolsVersion '27.0.3',gradle:3.0.1,当时遇到了这样一个bug:classpath下找不到com.android.tools.build:gradle:3.0.1,如下修改project层的build.gradle即可:

buildscript {  
    repositories {
        //新的插件需要在google仓库下载
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
    }
}

言归正传,这个android目录直接可以用Android Studio打开,作为Android开发者可以直接视为一个单独的Android项目,不必关心上层,React模块的内容更新可通过AwesomeProject目录下执行

react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res

可以自动更新到app/src/main/assets下,执行前得先确保有android/app/src/main/assets/目录,没有的话手动生成。完了会生成这样两个文件

这个命令很重要,React部分一旦更新就要执行一次,这就保证了用Android Studio跑起来的是一个完整的项目。

打开java目录,会有一个自动生成的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.asList(
                    new MainReactPackage()
                    //自定义包管理器
                    , new MyReactPackage()
            );
        }

        @Override
        protected String getJSMainModuleName() {
            return "index";
        }
    };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }
}

其中的getJSMainModuleName返回值对应的是React模块的入口index.js文件。

以及一个自动生成的MainActivity

public class MainActivity extends ReactActivity {

    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "AwesomeProject";
    }
}

这个getMainComponentName返回值对应index.js里的AppRegistry.registerComponent('AwesomeProject', () => App);

原生模块与React模块之间的通信

有2种情况:

  • React模块主动调用原生模块,原生模块被动接收处理,完了可能给React模块返回(callback)一些数据
  • 原生模块主动向React模块发送数据,React模块被动监听接收

做过app里h5页面js桥接的同学应该非常清楚这些过程,链接

React调用原生模块

首先创建一个MyNativeModule类继承自ReactContextBaseJavaModule,到时候React就可以调用这里通过@ReactMethod注解的方法

public class MyNativeModule extends ReactContextBaseJavaModule {

    private ReactApplicationContext reactContext;

    public MyNativeModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
    }
        @Override
        public String getName() {
            //返回的这个名字是必须的,在rn代码中需要这个名字来调用该类的方法。
            return "MyNativeModule";
        }
        //函数不能有返回值,因为被调用的原生代码是异步的,原生代码执行结束之后只能通过回调函数或者发送信息给rn那边。
        @ReactMethod
        public void rnCallNative(String msg) {
            Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
        }
    }

再创建一个MyReactPackage类实现ReactPackage接口并把MyNativeModule添加进去

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();
    }
}

然后在自动生成的MainApplication中把MyReactPackage添加进去

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.asList(
                    new MainReactPackage(),
                    //将我们创建的包管理器给添加进来
                    new MyReactPackage()
            );
        }

        @Override
        protected String getJSMainModuleName() {
            return "index";
        }
    };
...
}

然后在AwesomeProject/目录自动生成的App.js文件中如下添加一个Button

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';  
import {  
    Button,
    NativeModules,
    Platform,
    StyleSheet,
    Text,
    View
} from 'react-native';

const instructions = Platform.select({  
    ios: 'Press Cmd+R to reload,\n' +
    'Cmd+D or shake for dev menu',
    android: 'Double tap R on your keyboard to reload,\n' +
    'Shake or press menu button for dev menu hahah',
});

export default class App extends Component<{}> {  
    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
                    title="调用原生方法"
                    onPress={() =>
                        NativeModules.MyNativeModule.rnCallNative('js传递的数据')
                    } />
            </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,
    },
});

然后执行react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res命令,最后把我们的Android程序跑起来,点击按钮弹出Toast,功能简单,一目了然。

原生模块处理完数据回调给React

还是刚刚的MyNativeModule类,把其中的rnCallNative方法改为

@ReactMethod
public void rnCallNative(String msg, Callback callback) {  
   Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
   callback.invoke("来自原生模块的回调数据");
}

然后把刚刚的App.js的Button改为

<Button  
   title="调用原生方法"
   onPress={() =>
      NativeModules.MyNativeModule.rnCallNative('js传递的数据', (result) => {
                            alert(result)
                        })} />

然后如上处理将Android程序跑起来,点击按钮弹出Toast,紧接着弹出一个Dialog,也是很简单明了的功能。

原生模块主动向React模块发送数据

还是刚刚的MyNativeModule类,添加如下方法

private void sendEvent(String eventName, @Nullable WritableMap params) {  
        reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }

使用举例:

WritableMap event = Arguments.createMap();  
event.putString("message", "字符串数据");  
event.putInt("number", 1);  
sendEvent("eventReceiveMessage", event);  

App.js改为:

import React, { Component } from 'react';  
import {  
    Button,
    NativeModules,
    DeviceEventEmitter,
    StyleSheet,
    View
} from 'react-native';

export default class App extends Component<{}> {

    componentDidMount() { //这是React的生命周期函数,会在界面加载完成后执行一次
        DeviceEventEmitter.addListener('eventReceiveMessage', this.onEvent);
    }
    componentWillUnmount() {
        DeviceEventEmitter.removeListener('eventReceiveMessage', this.onEvent);
    }
    onEvent = (e) => {
        alert(JSON.stringify(e));
    }

    render() {
        return (
            <View style={styles.container}>
                <Button
                    title="调用原生方法"
                    onPress={() =>
                        NativeModules.MyNativeModule.rnCallNative('js传递的数据', (result) => {
                            alert(result)
                        })
                    } />
            </View>
        );
    }
}

const styles = StyleSheet.create({  
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
    },
});

感谢

React—Native开发之原生模块向JavaScript发送事件

禁止商业用途转载,其他转载请告知,谢谢!

最近的文章

React Native原生页面与React页面之间的跳转

本文内容以及所提到的类均基于上一篇博客,由上一篇可知MainActivity相当于index.js的一个容器,index.js主要就一句AppRegistry.registerComponent('A…

前端继续阅读
更早的文章

给RecyclerView添加水印

目标: 给RecyclerView添加重复文字水印,水印随RecyclerView的滑动而RecyclerView,效果如下图: 我哥们通过RecyclerView.ItemDecoration的方式…

举个例子继续阅读
comments powered by Disqus