Native Modules
Native Modules
需要本机代码的项目
此页面仅适用于react-native init
使用Create React Native App 制作的或使用此类应用程序弹出的项目。有关弹出的更多信息,请参阅创建React Native App存储库的指南。
有时候应用程序需要访问平台API,React Native目前还没有相应的模块。也许你想重用一些现有的Java代码,而不必用JavaScript重新实现它,或者编写一些高性能,多线程的代码,例如图像处理,数据库或任何数量的高级扩展。
我们设计了React Native,因此您可以编写真实的本机代码并访问平台的全部功能。这是一个更高级的功能,我们不希望它成为通常开发过程的一部分,但它存在必不可少。如果React Native不支持您需要的本地功能,您应该可以自己构建它。
启用Gradle
如果您打算对Java代码进行更改,我们建议启用Gradle Daemon以加快构建。
Toast模块
本指南将使用Toast示例。假设我们希望能够从JavaScript创建Toast消息。
我们首先创建一个本地模块。本地模块是一个Java类,通常扩展ReactContextBaseJavaModule
该类并实现JavaScript所需的功能。我们的目标是能够使用ToastExample.show('Awesome', ToastExample.SHORTJa
vaScript 编写在屏幕上显示简短的敬酒。
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;
import java.util.HashMap;
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
}
}
ReactContextBaseJavaModule
要求实现一个被调用的方法getName
。此方法的目的是NativeModule
在JavaScript中返回表示该类的字符串名称。所以我们在这里调用这个,ToastExample
以便我们可以通过React.NativeModules.ToastExample
JavaScript 访问它。
@Override
public String getName() {
return "ToastExample";
}
一个可选的方法调用getConstants
返回暴露给JavaScript的常量值。它的实现不是必需的,但对于需要同步从JavaScript到Java进行通信的关键预定义值非常有用。
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>(
constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT
constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG
return constants;
}
要向JavaScript公开方法,必须使用Java方法注释Java方法@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
阅读有关ReadableMap和ReadableArray的更多信息
注册模块
Java中的最后一步是注册模块; 这发生在createNativeModules
你的应用程序包中。如果一个模块没有注册,它将不能从JavaScript获得。
package com.facebook.react.modules.toast;
import com.facebook.react.ReactPackage;
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 AnExampleReactPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList(
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>(
modules.add(new ToastModule(reactContext)
return modules;
}
}
该包需要getPackages
在MainApplication.java
文件的方法中提供。该文件存在于react-native应用程序目录中的android文件夹下。这个文件的路径是:android/app/src/main/java/com/your-app-name/MainApplication.java
。
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new AnExampleReactPackage() // <-- Add this line with your package name.
}
为了更简单地从JavaScript访问您的新功能,通常会将本机模块包装到JavaScript模块中。这不是必须的,但是可以节省您的图书馆的消费者NativeModules
每次都需要关闭它。这个JavaScript文件也成为您添加JavaScript端功能的好地方。
'use strict';
/**
* This exposes the native ToastExample module as a JS module. This has a
* function 'show' 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 ToastExample.SHORT or
* ToastExample.LONG
*/
import { NativeModules } from 'react-native';
module.exports = NativeModules.ToastExample;
现在,从您的其他JavaScript文件中,您可以调用像这样的方法:
import ToastExample from './ToastExample';
ToastExample.show('Awesome', ToastExample.SHORT
Beyond Toasts
回调
本机模块还支持一种特殊的参数 - 回调。在大多数情况下,它用于将函数调用结果提供给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
}
本地模块应该只调用一次回调。但是,它可以存储回调并稍后调用它。
强调在本地函数完成后不立即调用回调是非常重要的 - 记住,桥接通信是异步的,这也与运行循环有关。
承诺
原生模块也可以实现承诺,这可以简化您的代码,特别是在使用ES2016的async/await
语法时。当桥接本机方法的最后一个参数是a时Promise
,其相应的JS方法将返回一个JS Promise
对象。
重构上述代码以使用promise而不是回调,如下所示:
import com.facebook.react.bridge.Promise;
public class UIManagerModule extends ReactContextBaseJavaModule {
...
private static final String E_LAYOUT_ERROR = "E_LAYOUT_ERROR";
@ReactMethod
public void measureLayout(
int tag,
int ancestorTag,
Promise promise) {
try {
measureLayout(tag, ancestorTag, mMeasureBuffer
WritableMap map = Arguments.createMap(
map.putDouble("relativeX", PixelUtil.toDIPFromPixel(mMeasureBuffer[0])
map.putDouble("relativeY", PixelUtil.toDIPFromPixel(mMeasureBuffer[1])
map.putDouble("width", PixelUtil.toDIPFromPixel(mMeasureBuffer[2])
map.putDouble("height", PixelUtil.toDIPFromPixel(mMeasureBuffer[3])
promise.resolve(map
} catch (IllegalViewOperationException e) {
promise.reject(E_LAYOUT_ERROR, e
}
}
...
此方法的JavaScript对应方返回Promise。这意味着您可以使用await
异步函数中的关键字来调用它并等待其结果:
async function measureLayout() {
try {
var {
relativeX,
relativeY,
width,
height,
} = await UIManager.measureLayout(100, 100
console.log(relativeX + ':' + relativeY + ':' + width + ':' + height
} catch (e) {
console.error(e
}
}
measureLayout(
思路
原生模块不应该对它们被调用的线程有任何假设,因为当前的任务将来可能会发生变化。如果需要阻塞调用,那么应该将繁重的工作分派给内部管理的工作者线程,并从那里分发任何回调。
发送事件到JavaScript
原生模块可以向JavaScript发送事件而不用直接调用。最简单的方法是使用RCTDeviceEventEmitter
可以从ReactContext
下面的代码片段中获得的。
...
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
然后,JavaScript模块可以通过addListenerOn
使用Subscribable
mixin 进行注册以接收事件。
import { DeviceEventEmitter } from 'react-native';
...
var ScrollResponderMixin = {
mixins: [Subscribable.Mixin],
componentWillMount: function() {
...
this.addListenerOn(DeviceEventEmitter,
'keyboardWillShow',
this.scrollResponderKeyboardWillShow
...
},
scrollResponderKeyboardWillShow:function(e: Event) {
this.keyboardWillOpenTo = e;
this.props.onKeyboardWillShow && this.props.onKeyboardWillShow(e
},
您也可以直接使用DeviceEventEmitter
模块来监听事件。
...
componentWillMount: function() {
DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) {
// handle event.
}
}
...
从中获取活动结果 startActivityForResult
onActivityResult
如果您想从您开始的活动中获得结果,您需要听取意见startActivityForResult
。要做到这一点,您必须扩展BaseActivityEventListener
或实施ActivityEventListener
。前者是首选,因为它对API更改具有更强的适应性。然后,你需要在模块的构造函数中注册监听器,
reactContext.addActivityEventListener(mActivityResultListener
现在您可以onActivityResult
通过执行以下方法来收听:
@Override
public void onActivityResult(
final Activity activity,
final int requestCode,
final int resultCode,
final Intent intent) {
// Your logic here
}
我们将实现一个简单的图像选择器来演示这一点。图像选择器会将该方法暴露pickImage
给JavaScript,它将在调用时返回图像的路径。
public class ImagePickerModule extends ReactContextBaseJavaModule {
private static final int IMAGE_PICKER_REQUEST = 467081;
private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
private static final String E_PICKER_CANCELLED = "E_PICKER_CANCELLED";
private static final String E_FAILED_TO_SHOW_PICKER = "E_FAILED_TO_SHOW_PICKER";
private static final String E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND";
private Promise mPickerPromise;
private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
if (requestCode == IMAGE_PICKER_REQUEST) {
if (mPickerPromise != null) {
if (resultCode == Activity.RESULT_CANCELED) {
mPickerPromise.reject(E_PICKER_CANCELLED, "Image picker was cancelled"
} else if (resultCode == Activity.RESULT_OK) {
Uri uri = intent.getData(
if (uri == null) {
mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, "No image data found"
} else {
mPickerPromise.resolve(uri.toString()
}
}
mPickerPromise = null;
}
}
}
};
public ImagePickerModule(ReactApplicationContext reactContext) {
super(reactContext
// Add the listener for `onActivityResult`
reactContext.addActivityEventListener(mActivityEventListener
}
@Override
public String getName() {
return "ImagePickerModule";
}
@ReactMethod
public void pickImage(final Promise promise) {
Activity currentActivity = getCurrentActivity(
if (currentActivity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist"
return;
}
// Store the promise to resolve/reject when picker returns data
mPickerPromise = promise;
try {
final Intent galleryIntent = new Intent(Intent.ACTION_PICK
galleryIntent.setType("image/*"
final Intent chooserIntent = Intent.createChooser(galleryIntent, "Pick an image"
currentActivity.startActivityForResult(chooserIntent, IMAGE_PICKER_REQUEST
} catch (Exception e) {
mPickerPromise.reject(E_FAILED_TO_SHOW_PICKER, e
mPickerPromise = null;
}
}
}
监听LifeCycle事件
监听活动的LifeCycle事件(例如onResume
,onPause
等等)与我们的实施方式非常相似ActivityEventListener
。该模块必须执行LifecycleEventListener
。然后,你需要在模块的构造函数中注册一个监听器,
reactContext.addLifecycleEventListener(this
现在您可以通过实施以下方法来收听活动的LifeCycle事件:
@Override
public void onHostResume() {
// Activity `onResume`
}
@Override
public void onHostPause() {
// Activity `onPause`
}
@Override
public void onHostDestroy() {
// Activity `onDestroy`
}