【React Native】文件翻譯閱讀紀錄 - 指南(Android) - 本機模組
Facebook Open Source React Native |
本機模組
有時,應用程序需要訪問React Native尚未擁有相應模塊的平台API。也許您希望重用一些現有的Java代碼,而不必在JavaScript中重新實現它,或者編寫一些高性能的多線程代碼,例如用於圖像處理,數據庫或任何數量的高級擴展。
我們設計了React Native,使您可以編寫真正的本機代碼並可以訪問平台的全部功能。這是一個更高級的功能,我們不希望它成為通常開發過程的一部分,但它必須存在。如果React Native不支持您需要的本機功能,您應該能夠自己構建它。
我們設計了React Native,使您可以編寫真正的本機代碼並可以訪問平台的全部功能。這是一個更高級的功能,我們不希望它成為通常開發過程的一部分,但它必須存在。如果React Native不支持您需要的本機功能,您應該能夠自己構建它。
啟用 Gradle
如果您打算在Java代碼中進行更改,我們建議您啟用 Gradle Daemon 以加快構建速度。
Toast 模塊
本指南將使用Toast示例。假設我們希望能夠從JavaScript創建一個Toast消息。
我們首先創建一個本機模塊。本機模塊是一個Java類,它通常擴展ReactContextBaseJavaModule類並實現JavaScript所需的功能。我們的目標是能夠編寫ToastExample.show('Awesome',ToastExample.SHORT);從JavaScript在屏幕上顯示一個簡短的Toast 。
我們首先創建一個本機模塊。本機模塊是一個Java類,它通常擴展ReactContextBaseJavaModule類並實現JavaScript所需的功能。我們的目標是能夠編寫ToastExample.show('Awesome',ToastExample.SHORT);從JavaScript在屏幕上顯示一個簡短的Toast 。
建立一個新的 Java Class
ToastModule.java
在android/app/src/main/java/com/your-app-name/ 資料夾下
// ToastModule.java
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 的方法。此方法的目的是返回在JavaScript中表示此類的 NativeModule 的字符串名稱。所以在這裡我們將調用此ToastExample,以便我們可以通過 JavaScript中的 React.NativeModules.ToastExample訪問它。 @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公開方法,必須使用@ReactMethod註釋Java方法。橋接方法的返回類型始終無效。 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獲得。
創建一個名為
CustomToastPackage.java
在android/app/src/main/java/com/your-app-name/
資料內容如下:// CustomToastPackage.java
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 CustomToastPackage 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;
}
}
需要在MainApplication.java文件的getPackages方法中提供該包。此文件存在於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 CustomToastPackage()); // <-- Add this line with your package name.
}
為了使從JavaScript訪問新功能變得更加簡單,通常將本機模塊包裝在JavaScript模塊中。這不是必需的,但每次都可以節省庫的使用者從NativeModules中取出它的需要。此JavaScript文件也成為添加任何JavaScript端功能的好位置。
/**
* 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提供函數調用結果。
import com.facebook.react.bridge.Callback;
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);
}
);
本機模塊應該僅調用一次回調。但是,它可以存儲回調並在以後調用它。
非常重要的是要強調在本機函數完成後不立即調用回調 - 請記住橋接通信是異步的,並且這也與運行循環相關聯。
非常重要的是要強調在本機函數完成後不立即調用回調 - 請記住橋接通信是異步的,並且這也與運行循環相關聯。
Promises(承諾)
本機模塊也可以履行承諾,這可以簡化您的代碼,尤其是在使用ES2016的async / await語法時。當橋接本機方法的最後一個參數是Promise時,其相應的JS方法將返回一個JS Promise對象。
重構上面的代碼以使用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模塊可以使用Subscribable mixin通過addListenerOn註冊接收事件。
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 獲取活動結果
如果要從使用startActivityForResult開始的活動中獲取結果,則需要收聽onActivityResult。為此,您必須擴展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`
}
0 意見