React Native Android原生模块开发实战教程

Viewed 0

React Native Android原生模块开发实战教程

前言

我一直想分享在React Native原生模块封装方面的经验和心得。在开发React Native应用时,经常需要使用原生模块,例如社会化分享、第三方登录、扫描、通信录和日历等功能。

React Native官方文档指出,当需要访问平台API或复用Java代码时,可以编写原生模块。原生模块允许你实现高性能、多线程的代码,如图片处理或数据库操作。本文将引导你开发一个从相册选择并裁切照片的项目,并以此为例详细讲解React Native Android原生模块的开发步骤。

开发Android原生模块的主要流程

构建React Native Android原生模块主要包括三个步骤:

  1. 编写原生模块的Java代码。
  2. 暴露接口并处理数据交互。
  3. 注册和导出React Native原生模块。

下面将详细介绍每个步骤。

原生模块开发实战

我们将通过一个从相册获取照片并裁切的项目来具体讲解React Native Android原生模块的开发。

编写原生模块的相关Java代码

首先,使用Android Studio打开React Native项目中的android目录。项目初始化后,可以开始编写Java代码。

我们首先定义一个Crop接口:

public interface Crop {
    void selectWithCrop(int outputX, int outputY, Promise promise);
}

接下来,创建CropImpl.java类来实现从相册选择照片及裁切功能:

public class CropImpl implements ActivityEventListener, Crop {
    private final int RC_PICK = 50081;
    private final int RC_CROP = 50082;
    private final String CODE_ERROR_PICK = "用户取消";
    private final String CODE_ERROR_CROP = "裁切失败";

    private Promise pickPromise;
    private Uri outPutUri;
    private int aspectX;
    private int aspectY;
    private Activity activity;

    public static CropImpl of(Activity activity) {
        return new CropImpl(activity);
    }

    private CropImpl(Activity activity) {
        this.activity = activity;
    }

    public void updateActivity(Activity activity) {
        this.activity = activity;
    }

    @Override
    public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
        if (requestCode == RC_PICK) {
            if (resultCode == Activity.RESULT_OK && data != null) {
                outPutUri = Uri.fromFile(Utils.getPhotoCacheDir(System.currentTimeMillis() + ".jpg"));
                onCrop(data.getData(), outPutUri);
            } else {
                pickPromise.reject(CODE_ERROR_PICK, "没有获取到结果");
            }
        } else if (requestCode == RC_CROP) {
            if (resultCode == Activity.RESULT_OK) {
                pickPromise.resolve(outPutUri.getPath());
            } else {
                pickPromise.reject(CODE_ERROR_CROP, "裁剪失败");
            }
        }
    }

    private void onCrop(Uri targetUri, Uri outputUri) {
        this.activity.startActivityForResult(IntentUtils.getCropIntentWith(targetUri, outputUri, aspectX, aspectY), RC_CROP);
    }

    @Override
    public void selectWithCrop(int outputX, int outputY, Promise promise) {
        this.aspectX = outputX;
        this.aspectY = outputY;
        this.pickPromise = promise;
        this.activity.startActivityForResult(IntentUtils.getPickIntentWithGallery(), RC_PICK);
    }
}

暴露接口与数据交互

为了向React Native暴露接口并进行数据交互,需要创建一个继承自ReactContextBaseJavaModule的类。

创建一个ReactContextBaseJavaModule

创建ImageCropModule.java类:

public class ImageCropModule extends ReactContextBaseJavaModule implements Crop {
    private CropImpl cropImpl;

    public ImageCropModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return "ImageCrop";
    }

    @Override
    @ReactMethod
    public void selectWithCrop(int aspectX, int aspectY, Promise promise) {
        getCrop().selectWithCrop(aspectX, aspectY, promise);
    }

    private CropImpl getCrop() {
        if (cropImpl == null) {
            cropImpl = CropImpl.of(getCurrentActivity());
            getReactApplicationContext().addActivityEventListener(cropImpl);
        } else {
            cropImpl.updateActivity(getCurrentActivity());
        }
        return cropImpl;
    }
}

ImageCropModule中,重写getName方法以暴露模块名称,并在selectWithCrop方法上添加@ReactMethod注解来暴露接口。这样,在JavaScript中可以通过ImageCrop.selectWithCrop调用该接口。

原生模块和JS进行数据交互

原生模块和JavaScript之间的通信是异步的。JavaScript通过调用暴露的接口传递数据,例如裁切照片的宽高比。原生模块完成操作后,通过Promise回调JavaScript。

@ReactMethod标注的方法支持以下数据类型参数:

  • Boolean -> Bool
  • Integer -> Number
  • Double -> Number
  • Float -> Number
  • String -> String
  • Callback -> function
  • ReadableMap -> Object
  • ReadableArray -> Array

使用Promise进行回调

selectWithCrop方法中,最后一个参数是Promise。当照片裁切完成后,原生模块通过Promise解析或拒绝来回调JavaScript。

在JavaScript中,可以这样调用:

ImageCrop.selectWithCrop(parseInt(x), parseInt(y))
    .then(result => {
        console.log(result);
    })
    .catch(e => {
        console.log(e);
    });

或者使用async/await语法:

async onSelectCrop() {
    var result = await ImageCrop.selectWithCrop(parseInt(x), parseInt(y));
}

向JS发送事件

对于需要多次传递数据的情况,如二维码扫描,可以使用RCTDeviceEventEmitter向JavaScript发送事件。

在原生模块中发送事件:

private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
    reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit(eventName, params);
}

在JavaScript中监听事件:

componentDidMount() {
    DeviceEventEmitter.addListener('onScanningResult', this.onScanningResult);
}

onScanningResult = (e) => {
    console.log(e.result);
};

componentWillUnmount() {
    DeviceEventEmitter.removeListener('onScanningResult', this.onScanningResult);
}

注册与导出React Native原生模块

为了向React Native注册原生模块,需要实现ReactPackage接口。

创建ImageCropReactPackage.java:

public class ImageCropReactPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new ImageCropModule(reactContext));
        return modules;
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

然后在MainApplication.java中注册该Package:

@Override
protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new ImageCropReactPackage()
    );
}

最后,导出一个JavaScript模块以便使用。创建ImageCrop.js文件:

import { NativeModules } from 'react-native';
export default NativeModules.ImageCrop;

在其他地方导入并使用:

import ImageCrop from './ImageCrop';

onSelectCrop() {
    let x = this.aspectX ? this.aspectX : ASPECT_X;
    let y = this.aspectY ? this.aspectY : ASPECT_Y;
    ImageCrop.selectWithCrop(parseInt(x), parseInt(y))
        .then(result => {
            this.setState({ result: result });
        })
        .catch(e => {
            this.setState({ result: e });
        });
}

关于线程

在React Native中,JavaScript运行在独立线程中。如果原生模块有耗时操作,如文件读写或网络请求,需要在新线程中执行,以避免阻塞JavaScript线程。在Android中,可以使用AsyncTask实现多线程。如果需要更新UI,必须在主线程中进行:

activity.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        if (!activity.isFinishing()) {
            // 更新UI代码
        }
    }
});

通过以上步骤,你可以成功开发并集成React Native Android原生模块,增强应用的功能和性能。

0 Answers