前言
之前我们学习了React Native的部分api,可以看到React Native为我们封装了非常丰富的api,即使如此有时候我们的应用需要进行访问原生平台系统的api接口,但是React Native可能还没有封装相应功能组件或api, 这种情况我们可以自己封装原生平台的组件。本文将介绍对Android原生组件封装,使我们能在React Native中调用原生模块。下面以我们在Android原生开发中常见的Toast为例,进行封装之后,通过React Native中的JavaScript来进行弹出toast消息。
创建模块类
首先需要创建一个原生模块类,该原生模块类是继承ReactContextBaseJavaModule类的Java代码,它可以实现JavaScript所需的一些功能。我们的目标是使用JavaScript代码通过调用ToastModuleAndroid.show(‘Awesome’,ToastModuleAndroid.SHORT)方法显示一个toast消息提醒。
在项目的ExerciseProject\android\app\src\main\java\com\exerciseproject
目录下新建一个java文件ToastModule.java, 该类继承ReactContextBaseJavaModule。
package com.exerciseproject;
import android.widget.Toast;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.HashMap;
import java.util.Map;
public class ToastModule extends ReactContextBaseJavaModule {
private static final String DURATION_SHORT="SHORT";
private static final String DURATION_LONG="LONG";
public ToastModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "ToastModule";
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put(DURATION_SHORT, Toast.LENGTH_SHORT);
constants.put(DURATION_LONG, Toast.LENGTH_LONG);
return constants;
}
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
}
}
这里实现了四个方法:ToastModule、getName、getConstants、show。
ToastModule是构造方法,在Java中与类名名称相同的方法称为构造方法,通常用来初始化。这里只需通过 super(reactContext)调用父类ReactContextBaseJavaModule的构造方法,不需要额外的工作。
getName返回一个字符串信息,它决定了今后在JavaScript中使用什么名称来调用该模块。这里我们将该模块命名为”ToastModule”。
RN已经内置了一个名为ToastAndroid的模块,所以在练习时请勿使用ToastAndroid的名字,否则运行时会报错名字冲突!
@Override
public String getName() {
return "ToastModule";
}
getContants是一个可选的方法,返回了需要导出给JavaScript使用的常量。它并不一定需要实现,但在定义一些可以被JavaScript同步访问到的预定义的值时非常有用。
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put(DURATION_SHORT, Toast.LENGTH_SHORT);
constants.put(DURATION_LONG, Toast.LENGTH_LONG);
return constants;
}
show方法是我们自定义的方法,要导出一个方法给JavaScript使用,需要添加@ReactMethod注解。方法的返回类型必须为void。React Native的跨语言访问是异步进行的,所以想要给JavaScript返回一个值的唯一办法是使用回调函数或者发送事件。
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
}
参数类型说明
上面我们讲解到@ReactMethod注解的方法,在Java和JavaScript中的数据类型有一些区别,下面的参数类型在@ReactMethod注明的方法中,会被直接映射到它们对应的JavaScript类型。总体对应方式如下:
Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
参阅ReadableMap和ReadableArray。
模块注册
前面我们完成了组件模块的定义以及提供对外方法的封装,下面还需要在Java这边进行注册该模块。我们需要一个实现ReactPackage的子类。
在ToastModule.java同一目录下新建一个ToastModulePackage.java文件。
package com.exerciseproject;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ToastModulePackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ToastModule(reactContext));
return modules;
}
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
该类基本是一个模板,需要实现createNativeModules、createJSModules、createViewManagers这三个方法,React Native用这些方法来决定需要导出哪些模块。这里需要注意,在createNativeModule方法中添加前面自定义的那个模块ToastModule。如果没有注册,那么当前模块在JavaScript中是无法被访问到。
modules.add(new ToastModule(reactContext));
接着这个package需要在MainApplication.java文件的getPackages方法中提供。MainApplication位于ExerciseProject\android\app\src\main\java\com\exerciseproject\MainApplication.java
。之前在讲解数据库的配置时也有修改过MainApplication的getPackages方法。
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ToastModulePackage() // <-- 添加这一行,类名替换成你的Package类的名字.
);
}
在JavaScript端使用
为了使当前封装的模块在JavaScript中可以方便使用,通常会把该原生模块封装成JavaScript模块。这样可以省下每次访问组件都要加入NativeModules的步骤,不过该步骤不是必须的。
本例中在\ExerciseProject\src\12_nativeapi\
中新建一个ToastModuleAndroid.js文件
let { NativeModules } = require('react-native');
module.exports = NativeModules.ToastModule;
然后在别的JavaScript文件代码中就可以如下进行访问:
import ToastModuleAndroid from './ToastModuleAndroid';
ToastModuleAndroid.show("调用Android ToastModule弹出消息",ToastModuleAndroid.SHORT);
具体在JavaScript文件中的调用方法实例代码如下:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
TouchableHighlight,
} from 'react-native';
import ToastModuleAndroid from './ToastModuleAndroid';
export default class ModulesDemo extends Component {
render() {
return (
<View>
<TouchableHighlight
style={styles.button}
underlayColor="#a5a5a5"
onPress={()=>ToastModuleAndroid.show("调用Android ToastModule弹出消息",ToastModuleAndroid.SHORT)}>
<Text style={styles.buttonText}>自定义Toast</Text>
</TouchableHighlight>
</View>
);
}
}
const styles = StyleSheet.create({
button: {
margin:5,
backgroundColor: 'white',
padding: 15,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#cdcdcd',
},
});
最后看看运行效果:
示例源码:git地址