React Native 中的 Android 原生模块

力谱宿云 发布于 2016/11/22 10:15
阅读 1K+
收藏 2

我们要写一个什么东西

在写这篇文章时,React Native 包含了 ImagePickerIOS 组件,但是在 Android 平台上却没有对应的 ImagePicker 组件。我们接下来就要为 Android 构建一个简单的、和 ImagePickerIOS 大致相仿的 ImagePicker。

编写一个 React Native 的 Android 原生模块需要以下步骤:

  1. 创建一个 ReactPackage,把很多模块(Native 和 Javascript)包含在一起,然后在 MainActivity 中的 getPackages 方法引用。
  2. 创建一个 Java 类,继承 ReactContextBaseJavaModule 并实现需要的接口,然后注册到我们的 ReactPackage。
  3. 覆写上述类的 getName 方法,这个方法会作为 Javascript 的调用方法名。
  4. 使用 @ReactMethod 注解把需要的公共方法暴露给 Javascript。
  5. 最后,在 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 getPackages() {
        return Arrays.asList(
            new MainReactPackage()
        );
    }
}

我们先来引入一个尚未定义的包:

import com.myapp.imagepicker.*; // import the package

public class MainActivity extends ReactActivity {
    @Override
    protected List getPackages() {
        return Arrays.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 createNativeModules(ReactApplicationContext reactContext) {
        List modules = new ArrayList<>();

        modules.add(new ImagePickerModule(reactContext));

        return modules;
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List 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 的表现大致相仿,我们可以允许用户选择图片、视频或者直接打开相机。这些功能的写法和上面基本一致,我们将会把它留给读者作为练习。


作者信息

原文作者: Ryan Linton
原文链接:http://t.cn/Rfadv1R
翻译自MaxLeap团队_前端研发人员:Henry Bai
MaxLeap技术博客首发:https://blog.maxleap.cn/

相关文章

使用 React Native 构建 Facebook Paper 类似的 UI

作者往期佳作

ES6 Generators 工作原理

欢迎关注微信公众号:MaxLeap_yidongyanfa

OSCHINA
登录后可查看更多优质内容
返回顶部
顶部