React Native中的Android原生模塊
當(dāng)使用 React Native 開(kāi)發(fā) Android 應(yīng)用時(shí),你可能需要使用沒(méi)有被 React Native 封裝的模塊。但你可以使用 Java 編寫原生模塊,然后選擇性的暴露公共接口到 React Native。一起來(lái)試一下!
我們要寫一個(gè)什么東西
在寫這篇文章時(shí),React Native 包含了 ImagePickerIOS 組件,但是在 Android 平臺(tái)上卻沒(méi)有對(duì)應(yīng)的 ImagePicker 組件。我們接下來(lái)就要為 Android 構(gòu)建一個(gè)簡(jiǎn)單的、和 ImagePickerIOS 大致相仿的 ImagePicker。
編寫一個(gè) React Native 的 Android 原生模塊需要以下步驟:
- 創(chuàng)建一個(gè) ReactPackage,把很多模塊(Native 和 Javascript)包含在一起,然后在 MainActivity 中的 getPackages 方法引用。
- 創(chuàng)建一個(gè) Java 類,繼承 ReactContextBaseJavaModule 并實(shí)現(xiàn)需要的接口,然后注冊(cè)到我們的 ReactPackage。
- 覆寫上述類的 getName 方法,這個(gè)方法會(huì)作為 Javascript 的調(diào)用方法名。
- 使用 @ReactMethod 注解把需要的公共方法暴露給 Javascript。
- ***,在 Javascript 中通過(guò) NativeModules 導(dǎo)入你的模塊。
讓我們一起實(shí)踐一下。
創(chuàng)建一個(gè) ReactPackage
啟動(dòng) AndroidStudio 并且導(dǎo)航到 MyApp/android/app/src/main/java/com/myapp/MainActivity.java。它應(yīng)該看起來(lái)像這樣:
- 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()
- );
- }
- }
我們先來(lái)引入一個(gè)尚未定義的包:
- 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
- );
- }
- }
現(xiàn)在我們來(lái)編寫那個(gè)包。我們將會(huì)為它創(chuàng)建一個(gè)叫 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();
- }
- }
現(xiàn)在我們已經(jīng)創(chuàng)建了一個(gè)包并且包含進(jìn) MainActivity 中了。
創(chuàng)建一個(gè) ReactContextBaseJavaModule
我們將會(huì)以創(chuàng)建 ImagePickerModule 開(kāi)始,繼承 ReactContextBaseJavaModule。
- package com.myapp.imagepicker;
- import com.facebook.react.bridge.ReactContextBaseJavaModule;
- public class ImagePickerModule extends ReactContextBaseJavaModule {
- public ImagePickerModule(ReactApplicationContext reactContext) {
- super(reactContext);
- }
- }
這是一個(gè)好的開(kāi)始,為了 React Native 能從 NativeModules 找到我們的模塊,我們需要覆寫 getName 方法。
- @Override
- public String getName() {
- return "ImagePicker";
- }
我們現(xiàn)在有了一個(gè)可以被 JavaScript 代碼導(dǎo)入的 native 模塊,讓它做些有趣的事情吧。
暴露方法
ImagePickerIOS 定義了 openSelectDialog 方法,可以傳遞配置對(duì)象、失敗、成功的回調(diào)。讓我們?cè)? ImagePickerModule 中定義一個(gè)相似的方法。
- 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 中導(dǎo)入了 Callback 和 ReadableMap 來(lái)對(duì)應(yīng) JavaScript 中的 function 和 object。我們?yōu)檫@個(gè)方法加上@ReactMethod 注解,從而使它作為 ImagePicker 的一部分被 JavaScript 引用。
在方法體中我們獲取當(dāng)前的 activity,如果沒(méi)有獲取到 activity,就調(diào)用 cancel 的回調(diào)方法。我們現(xiàn)在有了一個(gè)可以運(yùn)行的方法,但是它還不能做任何有趣的事情。讓我們用它打開(kāi)相冊(cè)。
- 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);
- }
- }
- }
首先,我們?cè)O(shè)置了回調(diào),然后,我們創(chuàng)建了一個(gè) Intent 并把它傳遞給 startActivityForResult。***,我們把所有的東西都放在 try/catch 塊中來(lái)處理可能發(fā)生的異常。
當(dāng)你調(diào)用 openSelectDialog 時(shí),你應(yīng)該可以看到一個(gè)相冊(cè)了。然而,當(dāng)你選擇一張圖片時(shí),相冊(cè)并不做任何事情。為了能夠處理圖片數(shù)據(jù),我們需要在模塊中處理 activity 的返回值。
首先,我們需要在 react context 中添加 activity event listener:
- public class ImagePickerModule extends ReactContextBaseJavaModule implements ActivityEventListener {
- public ImagePickerModule(ReactApplicationContext reactContext) {
- super(reactContext);
- reactContext.addActivityEventListener(this);
- }
- }
現(xiàn)在我們可以獲取到相冊(cè)返回的數(shù)據(jù)了。
- @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");
- }
- }
- }
- }
- }
在這里我們應(yīng)該可以通過(guò) success callback 獲取到圖片 URI。
- NativeModules.ImagePicker.openSelectDialog(
- {}, // no config yet
- (uri) => { console.log(uri) },
- (error) => { console.log(error) }
- )
為了和 ImagePickerIOS 的表現(xiàn)大致相仿,我們可以允許用戶選擇圖片、視頻或者直接打開(kāi)相機(jī)。這些功能的寫法和上面基本一致,我們將會(huì)把它留給讀者作為練習(xí)。