當使用 React Native 開發 Android 應用時,你可能需要使用沒有被 React Native 封裝的模塊。但你可以使用 Java 編寫原生模塊,然后選擇性的暴露公共接口到 React Native。一起來試一下!
一、我們要寫一個什么東西
在寫這篇文章時,React Native 包含了 ImagePickerIOS 組件,但是在 Android 平臺上卻沒有對應的 ImagePicker 組件。我們接下來就要為 Android 構建一個簡單的、和 ImagePickerIOS 大致相仿的 ImagePicker。
1 編寫一個 React Native 的 Android 原生模塊需要以下步驟:
創建一個 ReactPackage,把很多模塊(Native 和 Javascript)包含在一起,然后在 MainActivity 中的 getPackages 方法引用。
創建一個 Java 類,繼承 ReactContextBaseJavaModule 并實現需要的接口,然后注冊到我們的 ReactPackage。
覆寫上述類的 getName 方法,這個方法會作為 Javascript 的調用方法名。
使用 @ReactMethod 注解把需要的公共方法暴露給 Javascript。
后,在 Javascript 中通過 NativeModules 導入你的模塊。
讓我們一起實踐一下。
2 創建一個 ReactPackage
啟動 AndroidStudio 并且導航到 MyApp/android/app/src/main/java/com/myapp/MainActivity.java。它應該看起來像這樣:
package com.myapp;
import com..react.ReactActivity;
import com..react.ReactPackage;
import com..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..react.ReactPackage;
import com..react.bridge.JavaScriptModule;
import com..react.bridge.NativeModule;
import com..react.bridge.ReactApplicationContext;
import com..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 中了。
3 創建一個 ReactContextBaseJavaModule
我們將會以創建 ImagePickerModule 開始,繼承 ReactContextBaseJavaModule。
package com.myapp.imagepicker;
import com..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 模塊,讓它做些有趣的事情吧。
4 暴露方法
ImagePickerIOS 定義了 openSelectDialog 方法,可以傳遞配置對象、失敗、成功的回調。讓我們在 ImagePickerModule 中定義一個相似的方法。
import com..react.bridge.Callback;
import com..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 的表現大致相仿,我們可以允許用戶選擇圖片、視頻或者直接打開相機。這些功能的寫法和上面基本一致,我們將會把它留給讀者作為練習。
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。