React Native调用原生组件

在React Native开发过程中,有时候我们可能需要访问平台的API,但react Native还没有相应的实现,或者是React Native还不支持一些原生的属性,我们需要调用原生代码来实现,或者是我们需要复用一些原来的Java代码,这个时候我们就需要创建一个原生模块来自己实现对我们需要功能的封装。 相关文档可以参照官方的介绍。

实例

下面我们就通过实现一个自定义模块,来熟悉编写原生模块需要用的一些知识。该模块主要实现调用一些Android原生的功能,比如弹Toast,启动Activity等。

实现模块

首先来创建一个原生模块。一个原生模块是一个继承了 ReactContextBaseJavaModule 的Java类,它有一个必须实现的方法getName(),它返回一个字符串名字,在js中我们就使用这个名字调用这个模块;还有构造函数NativeModule。

public class MyNativeModule extends ReactContextBaseJavaModule {
    private final static String MODULE_NAME = "MyNativeModule";
    private static final  String TestEvent = "TestEvent";
    private ReactApplicationContext mContext;
    public MyNativeModule(ReactApplicationContext reactContext) {
        super(reactContext);
        mContext = reactContext;
    }

    @Override
    public String getName() {
        return MODULE_NAME;
    }

    @Nullable
    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put("SHORT", Toast.LENGTH_SHORT);
        constants.put("LONG", Toast.LENGTH_LONG);
        constants.put("NATIVE_MODULE_NAME", MODULE_NAME);
        constants.put(TestEvent, TestEvent);
        return constants;
    }

    @ReactMethod
    public void startActivity(){
        Intent intent = new Intent(mContext,SecondActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(intent);
    }

    @ReactMethod
    public void showToast(String msg, int duration){
        Toast.makeText(mContext, msg, duration).show();
    }
}

这里需要对React Native和原生的类型映射做一个简单的介绍。详细的还可以参考ReadableMapReadableArray

Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array

例如,实现getContants方法导出需要给JavaScript使用的常量。

@Nullable
    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put("SHORT", Toast.LENGTH_SHORT);
        constants.put("LONG", Toast.LENGTH_LONG);
        constants.put("NATIVE_MODULE_NAME", MODULE_NAME);
        constants.put(TestEvent, TestEvent);
        return constants;
    }

注册模块

接下来我们需要向系统注册这个模块,通过实现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<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

添加模块

在Application的getPackages()方法中添加上面的模块。

 @Override
        protected List<ReactPackage> getPackages() {
            return Arrays.<ReactPackage>asList(
                    new MainReactPackage(), 
                    //添加的模块
                    new MyReactPackage()
            );
        }

或者这MainActivity的onCreate中,添加如下代码:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mReactRootView = new ReactRootView(this);
        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setBundleAssetName("index.android.bundle")
                .setJSMainModuleName("index.android")
                .addPackage(new MainReactPackage())
                .addPackage(new MyReactPackage())
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
        mReactRootView.startReactApplication(mReactInstanceManager, "HelloWorld", null);
        setContentView(mReactRootView);
    }

Js端封装模块

为了使javascript端访问起来更为方便,通常我们都会把原生模块封装成一个JavaScript模块。

import { NativeModules } from 'react-native'; 

// 这里的MyNativeModule必须对应
// public String getName()中返回的字符串

export default NativeModules.MyNativeModule;

接下来,就可以直接使用了。

import MyNativeModule from './MyNativeModule'; 
class HelloWorld extends React.Component {
  startActivity(){
    console.log("MODULE NAME: ",MyNativeModule.NATIVE_MODULE_NAME);
    MyNativeModule.startActivity();
  }
  showToast(){
    console.log("MODULE NAME: ",MyNativeModule.NATIVE_MODULE_NAME);
    MyNativeModule.showToast("From JS", MyNativeModule.LONG);
  }
  render() {
    return (
      <View style={styles.container}>
        <TouchableOpacity onPress={this.startActivity}>  
          <Text style={styles.hello}>start Activity</Text>  
        </TouchableOpacity>
      </View>
    )
  }
}

其他知识

React Native的跨语言访问是异步进行的,所以想要给JavaScript返回一个值的唯一办法是使用回调函数或者发送事件。

回调函数

原生模块还支持一种特殊的参数——回调函数。它提供了一个函数来把返回值传回给JS。

@ReactMethod
    public void testCallback(int para1, int para2, Callback resultCallback){
        int result = para1 + para2;
        resultCallback.invoke(result);
    }

也可以在JS中调用。例如:

testCallback(){
    MyNativeModule.testCallback(100,100,(result) => {
    console.log("result: ",result); //'result: ', 200
    });
  }

原生模块通常只应调用回调函数一次。但是,它可以保存callback并在将来调用。 callback并非在对应的原生函数返回后立即被执行——注意跨语言通讯是异步的,这个执行过程会通过消息循环来进行。

RCTDeviceEventEmitter

生模块可以在没有被调用的情况下往JavaScript发送事件通知。最简单的办法就是通过RCTDeviceEventEmitter,这可以通过ReactContext来获得对应的引用。RCTDeviceEventEmitter相当于客户端的广播机制。

public void sendEvent(){
        WritableMap params = Arguments.createMap();
        params.putString("module", "MyNativeModule");
        mContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(TestEvent, params);
    }

在JS中调用的代码如下:

import { DeviceEventEmitter } from 'react-native';
......
  componentWillMount() {
    console.log("componentWillMount");
    //接收事件
    DeviceEventEmitter.addListener(MyNativeModule.TestEvent, info => {
      console.log(info);
    });
  }

startActivityForResult

如果需要监听activity的生命周期事件(比如onResume, onPause等等),模块必须实现LifecycleEventListener,然后需要在构造函数中注册一个监听函数。

public MyNativeModule(ReactApplicationContext reactContext) {
        super(reactContext);
        mContext = reactContext;
        //添加监听
        reactContext.addLifecycleEventListener(this);
    }

实现LifecycleEventListener的几个接口。

@Override
    public void onHostResume() {
        Log.e(MODULE_NAME, "onHostResume");
    }

    @Override
    public void onHostPause() {
        Log.e(MODULE_NAME, "onHostPause");
    }

    @Override
    public void onHostDestroy() {
        Log.e(MODULE_NAME, "onHostDestroy");
    }  

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android 研究

APK安装流程详解10——PackageParser解析APK(下)

其中第一个parseBaseApk(File, AssetManager, int)方法,已经讲解过了,请参考APK安装流程详解9——PackageParser...

2451
来自专栏向治洪

React Native调用原生组件

在React Native开发过程中,有时候我们可能需要访问平台的API,但react Native还没有相应的实现,或者是React Native还不支持一些...

2248
来自专栏非著名程序员

Android WebView 上传文件支持全解析

声明:原文地址:http://blog.isming.me/2015/12/21/android-webview-upload-file/,转载请注明出处。 默...

9177
来自专栏技术小黑屋

Android扫描多媒体文件剖析

这篇文章从系统源代码分析,讲述如何将程序创建的多媒体文件加入系统的媒体库,如何从媒体库删除,以及大多数程序开发者经常遇到的无法添加到媒体库的问题等。本人将通过对...

1531
来自专栏计算机编程

Android 自定义 svg 颜色

源码注释告诉了我们:此获取的drawable不与其他drawable 共享,简而言之,就是构建单独的内存模块来存储此drawable达到相互不影响的状态。

1883
来自专栏郭霖

Android Context完全解析,你所不知道的Context的各种细节

前几篇文章,我也是费劲心思写了一个ListView系列的三部曲,虽然在内容上可以说是绝对的精华,但是很多朋友都表示看不懂。好吧,这个系列不仅是把大家给难倒了,也...

2909
来自专栏向治洪

电话拦截

首先需要 android 源码文件NeighboringCellInfo.aidl和ITelephony.aidl,新建文件夹android.telephony...

2247
来自专栏everhad

Android AppBar

AppBar官方文档摘记 2016-6-12 本文摘自Android官方文档,为方便自己及其他开发者朋友阅读。 章节目录为“Develop > Trainin...

2376
来自专栏javathings

Spring 中的感知接口

如其名字一样,实现这种接口的 Bean,能自身感知到容器的存在,容器在操作 Bean 的过程中,会调用感知接口中的方法。Spring 设计的这些接口,等于埋下了...

1712
来自专栏懒人开发

鸿洋AutoLayout代码分析(二):获取Manifest中的值

从AndroidManifest.xml中去取值,自己觉得应该和 PackageManager 或 Manifest 有关 (Manifest 简单是一个容器...

1912

扫码关注云+社区

领取腾讯云代金券