有时候APP需要做出React Native平台没有的功能,你也许会想用一些存在的java代码去解决问题,而不是用javascript脚本去去解决问题,或许写一些高性能,多线程的代码,列如图片处理,数据库,或者任何先进的继承
我们设计React Native的目的是尽可能 让你可以写一些真正的原生代码并且可以完全拥有系统的权限的能力,这是一个更加先进的特点,并且我们不希望这是传统开发过程中的一部分,然而它的存在是非常重要的。如果 React Native 不支持你需要的原生特征,那么你应该可以自己构建。
Toast 模块
首先,我们来写原生模块。一个原生模块的java类通的常继承 ReactContextBaseJavaModule 类,并且实现了 JavaScript 需要实现的方法。我们这里的目的是允许通过使用 JavaScript 调用 ToastAndroid.show('Awesome', ToastAndroid.SHORT);就可以在屏幕上面显示一个短短的 toast 消息。
- package com.facebook.react.modules.toast;
- import android.widget.Toast;
- import com.facebook.react.bridge.NativeModule;
- import com.facebook.react.bridge.ReactApplicationContext;
- import com.facebook.react.bridge.ReactContext;
- import com.facebook.react.bridge.ReactContextBaseJavaModule;
- import com.facebook.react.bridge.ReactMethod;
- import java.util.Map;
- public class ToastModule extends ReactContextBaseJavaModule {
- private static final String DURATION_SHORT_KEY = "SHORT";
- private static final String DURATION_LONG_KEY = "LONG";
- public ToastModule(ReactApplicationContext reactContext) {
- super(reactContext);
- }
- }
ToastModult继承了ReactContextBaseJavaModule这个类需要重写getName 的方法。这个方法就是返回值就是 JavaScript 里面调用 NativeModule 的方法名。在这里我们调用 ToastAndroid 因此我们可以在 JavaScript 里面使用 React.NativeModules.ToastAndroid 来使用这个类。
- @Override
- public String getName() {
- return "ToastAndroid";
- }
ToastModule里面重写了这个getConstants方法,这个方法会将传递给JavaScript 的常量返回。这个方法不是必须重写,它是非常重要的对于 JavaScript 和 Java 中同步的预定义的关键字的值。
- @Override
- public Map<String, Object> getConstants() {
- final Map<String, Object> constants = new HashMap<String,Object>();
- constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
- constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
- return constants;
- }
JavaScript d调用的这个方法需要使用 @ReactMethod 这个注解。这个方法的返回值类型是void类型。React Native 的桥接是异步的,因此将一个结果传递给 JavaScript 的唯一方式就是使用回调函数或者调用事件(见下面)。
- @ReactMethod
- public void show(String message, int duration) {
- Toast.makeText(getReactApplicationContext(), message, duration).show();
- }
参数类型
下面的参数类型是使用 @ReactMethod 注解的方法支持的,并且它们直接对应 JavaScript 中对应的值。
- Boolean -> Bool
- Integer -> Number
- Double -> Number
- Float -> Number
- String -> String
- Callback -> function
- ReadableMap -> Object
- ReadableArray -> Array
我们继承ReactContextBaseJavaModule的Java类最后一步就是注册这个模块,我们将在createNativeModules 这个包里面注册。如果这个继承的类没有被注册,那么javaScript 不可调用它
- @Override
- public List<NativeModule> createNativeModules(
- ReactApplicationContext reactContext) {
- List<NativeModule> modules = new ArrayList<NativeModule>();
- modules.add(new ToastModule(reactContext));
- return modules;
- }
- new ArrayList<NativeModule>();
- mReactInstanceManager = ReactInstanceManager.builder()
- .setApplication(getApplication())
- .setBundleAssetName("AnExampleApp.android.bundle")
- .setJSMainModuleName("Examples/AnExampleApp/AnExampleApp.android")
- .addPackage(new AnExampleReactPackage())
- .setUseDeveloperSupport(true)
- .setInitialLifecycleState(LifecycleState.RESUMED)
- .build();
为了更方便的从 JavaScript 访问新功能的时,通常会将原生模块包裹在一个 JavaScript 模块里面,不一定这样,但是节省了你的类库的使用者每次都要 pull NativeModules 的不便。这个 JavaScript 文件也为你增加任何 JavaScript 端功能提供了方便。
- /**
- * @providesModule ToastAndroid
- */
- 'use strict';
- /**
- * This exposes the native ToastAndroid module as a JS module. This has a function 'showText'
- * which takes the following parameters:
- *
- * 1. String message: A string with the text to toast
- * 2. int duration: The duration of the toast. May be ToastAndroid.SHORT or ToastAndroid.LONG
- */
- var { NativeModules } = require('react-native');
- module.exports = NativeModules.ToastAndroid;
现在,在JavaScript文件里面你可以类似下面这样调用方法:
- var ToastAndroid = require('ToastAndroid')
- ToastAndroid.show('Awesome', ToastAndroid.SHORT);
- // Note: We require ToastAndroid without any relative filepath because
- // of the @providesModule directive. Using @providesModule is optional.
回调
原生模块也提供了一种特殊的参数-一个回调。在大多数情况下这是给 JavaScript 返回结果使用的。
使用以下方法可以来访问在 JavaScript 里面可以使用:
原生模块也提供了一种特殊的参数-一个回调。在大多数情况下这是给 JavaScript 返回结果使用的。
- public class UIManagerModule extends ReactContextBaseJavaModule {
- ...
- @ReactMethod
- public void measureLayout(
- int tag,
- int ancestorTag,
- Callback errorCallback,
- Callback successCallback) {
- try {
- measureLayout(tag, ancestorTag, mMeasureBuffer);
- float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
- float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
- float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
- float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
- successCallback.invoke(relativeX, relativeY, width, height);
- } catch (IllegalViewOperationException e) {
- errorCallback.invoke(e.getMessage());
- }
- }
- ...
使用以下方法可以来访问在 JavaScript 里面可以使用:
- UIManager.measureLayout(
- 100,
- 100,
- (msg) => {
- console.log(msg);
- },
- (x, y, width, height) => {
- console.log(x + ':' + y + ':' + width + ':' + height);
- }
- );
使用以下方法可以来访问在 JavaScript
原生模块支持只调用一次它的回调。它可以保存这个回调,并且在以后调用。
有一点需要强调的就是,在原生方法完成之后这个回调并不是立即被调用,请记住桥接通信是异步的,因此这个也在运行时循环里面。
线程
原生模块不应该设想有它们将在哪些线程里面被调用,因为目前的任务在以后改变是主要的。如果一个块调用是必须的,那么耗时操作将会被分配到间歇性的工作线程中,并且任何回调将会从这里开始。
给 JavaScript 传递事件
原生模块可以不需要立即被调用就可以给 JavaScript 发送事件。最简单的方式就是使用从 ReactContext 获得的 RCTDeviceEventEmitter,就像下面的代码片段:
- ...
- private void sendEvent(ReactContext reactContext,
- String eventName,
- @Nullable WritableMap params) {
- reactContext
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
- .emit(eventName, params);
- }
- ...
- WritableMap params = Arguments.createMap();
- ...
- sendEvent(reactContext, "keyboardWillShow", params);
- var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
- ...
- var ScrollResponderMixin = {
- mixins: [Subscribable.Mixin],
- componentWillMount: function() {
- ...
- this.addListenerOn(RCTDeviceEventEmitter,
- 'keyboardWillShow',
- this.scrollResponderKeyboardWillShow);
- ...
- },
- scrollResponderKeyboardWillShow:function(e: Event) {
- this.keyboardWillOpenTo = e;
- this.props.onKeyboardWillShow && this.props.onKeyboardWillShow(e);
- },