React Native中的Android原生模塊
當使用 React Native 開發 Android 應用時,你可能需要使用沒有被 React Native 封裝的模塊。但你可以使用 Java 編寫原生模塊,然后選擇性的暴露公共接口到 React Native。一起來試一下!
我們要寫一個什么東西
在寫這篇文章時,React Native 包含了 ImagePickerIOS 組件,但是在 Android 平臺上卻沒有對應的 ImagePicker 組件。我們接下來就要為 Android 構建一個簡單的、和 ImagePickerIOS 大致相仿的 ImagePicker。
編寫一個 React Native 的 Android 原生模塊需要以下步驟:
- 創建一個 ReactPackage,把很多模塊(Native 和 Javascript)包含在一起,然后在 MainActivity 中的 getPackages 方法引用。
- 創建一個 Java 類,繼承 ReactContextBaseJavaModule 并實現需要的接口,然后注冊到我們的 ReactPackage。
- 覆寫上述類的 getName 方法,這個方法會作為 Javascript 的調用方法名。
- 使用 @ReactMethod 注解把需要的公共方法暴露給 Javascript。
- ***,在 Javascript 中通過 NativeModules 導入你的模塊。
讓我們一起實踐一下。
創建一個 ReactPackage
啟動 AndroidStudio 并且導航到 MyApp/android/app/src/main/java/com/myapp/MainActivity.java。它應該看起來像這樣:
- package com.myapp;
- import com.facebook.react.ReactActivity;
- import com.facebook.react.ReactPackage;
- import com.facebook.react.shell.MainReactPackage;
- import java.util.Arrays;
- import java.util.List;
- public class MainActivity extends ReactActivity {
- @Override
- protected String getMainComponentName() {
- return "MyApp";
- }
- @Override
- protected boolean getUseDeveloperSupport() {
- return BuildConfig.DEBUG;
- }
- @Override
- protected List<ReactPackage> getPackages() {
- return Arrays.<ReactPackage>asList(
- new MainReactPackage()
- );
- }
- }
我們先來引入一個尚未定義的包:
- import com.myapp.imagepicker.*; // import the package
- public class MainActivity extends ReactActivity {
- @Override
- protected List<ReactPackage> getPackages() {
- return Arrays.<ReactPackage>asList(
- new MainReactPackage(),
- new ImagePickerPackage() // include it in getPackages
- );
- }
- }
現在我們來編寫那個包。我們將會為它創建一個叫 imagepicker 的新目錄并且寫入 ImagePickerPackage:
- package com.myapp.imagepicker;
- 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 ImagePickerPackage implements ReactPackage {
- @Override
- public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
- List<NativeModule> modules = new ArrayList<>();
- modules.add(new ImagePickerModule(reactContext));
- return modules;
- }
- @Override
- public List<Class<? extends JavaScriptModule>> createJSModules() {
- return Collections.emptyList();
- }
- @Override
- public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
- return Collections.emptyList();
- }
- }
現在我們已經創建了一個包并且包含進 MainActivity 中了。
創建一個 ReactContextBaseJavaModule
我們將會以創建 ImagePickerModule 開始,繼承 ReactContextBaseJavaModule。
- package com.myapp.imagepicker;
- import com.facebook.react.bridge.ReactContextBaseJavaModule;
- public class ImagePickerModule extends ReactContextBaseJavaModule {
- public ImagePickerModule(ReactApplicationContext reactContext) {
- super(reactContext);
- }
- }
這是一個好的開始,為了 React Native 能從 NativeModules 找到我們的模塊,我們需要覆寫 getName 方法。
- @Override
- public String getName() {
- return "ImagePicker";
- }
我們現在有了一個可以被 JavaScript 代碼導入的 native 模塊,讓它做些有趣的事情吧。
暴露方法
ImagePickerIOS 定義了 openSelectDialog 方法,可以傳遞配置對象、失敗、成功的回調。讓我們在 ImagePickerModule 中定義一個相似的方法。
- import com.facebook.react.bridge.Callback;
- import com.facebook.react.bridge.ReadableMap;
- public class ImagePickerModule extends ReactContextBaseJavaModule {
- @ReactMethod
- public void openSelectDialog(ReadableMap config, Callback successCallback, Callback cancelCallback) {
- Activity currentActivity = getCurrentActivity();
- if (currentActivity == null) {
- cancelCallback.invoke("Activity doesn't exist");
- return;
- }
- }
- }
這里我們從 React Native 中導入了 Callback 和 ReadableMap 來對應 JavaScript 中的 function 和 object。我們為這個方法加上@ReactMethod 注解,從而使它作為 ImagePicker 的一部分被 JavaScript 引用。
在方法體中我們獲取當前的 activity,如果沒有獲取到 activity,就調用 cancel 的回調方法。我們現在有了一個可以運行的方法,但是它還不能做任何有趣的事情。讓我們用它打開相冊。
- public class ImagePickerModule extends ReactContextBaseJavaModule {
- private static final int PICK_IMAGE = 1;
- private Callback pickerSuccessCallback;
- private Callback pickerCancelCallback;
- @ReactMethod
- public void openSelectDialog(ReadableMap config, Callback successCallback, Callback cancelCallback) {
- Activity currentActivity = getCurrentActivity();
- if (currentActivity == null) {
- cancelCallback.invoke("Activity doesn't exist");
- return;
- }
- pickerSuccessCallback = successCallback;
- pickerCancelCallback = cancelCallback;
- try {
- final Intent galleryIntent = new Intent();
- galleryIntent.setType("image/*");
- galleryIntent.setAction(Intent.ACTION_GET_CONTENT);
- final Intent chooserIntent = Intent.createChooser(galleryIntent, "Pick an image");
- currentActivity.startActivityForResult(chooserIntent, PICK_IMAGE);
- } catch (Exception e) {
- cancelCallback.invoke(e);
- }
- }
- }
首先,我們設置了回調,然后,我們創建了一個 Intent 并把它傳遞給 startActivityForResult。***,我們把所有的東西都放在 try/catch 塊中來處理可能發生的異常。
當你調用 openSelectDialog 時,你應該可以看到一個相冊了。然而,當你選擇一張圖片時,相冊并不做任何事情。為了能夠處理圖片數據,我們需要在模塊中處理 activity 的返回值。
首先,我們需要在 react context 中添加 activity event listener:
- public class ImagePickerModule extends ReactContextBaseJavaModule implements ActivityEventListener {
- public ImagePickerModule(ReactApplicationContext reactContext) {
- super(reactContext);
- reactContext.addActivityEventListener(this);
- }
- }
現在我們可以獲取到相冊返回的數據了。
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
- if (pickerSuccessCallback != null) {
- if (resultCode == Activity.RESULT_CANCELED) {
- pickerCancelCallback.invoke("ImagePicker was cancelled");
- } else if (resultCode == Activity.RESULT_OK) {
- Uri uri = intent.getData();
- if (uri == null) {
- pickerCancelCallback.invoke("No image data found");
- } else {
- try {
- pickerSuccessCallback.invoke(uri);
- } catch (Exception e) {
- pickerCancelCallback.invoke("No image data found");
- }
- }
- }
- }
- }
在這里我們應該可以通過 success callback 獲取到圖片 URI。
- NativeModules.ImagePicker.openSelectDialog(
- {}, // no config yet
- (uri) => { console.log(uri) },
- (error) => { console.log(error) }
- )
為了和 ImagePickerIOS 的表現大致相仿,我們可以允許用戶選擇圖片、視頻或者直接打開相機。這些功能的寫法和上面基本一致,我們將會把它留給讀者作為練習。