React Native自定义原生(iOS和Android)模块Module(以控制手电筒手电筒为例)

BG:为什么需要封装原生module?

引用官网介绍:

有时候 App 需要访问平台 API,但 React Native 可能还没有相应的模块封装;或者你需要复用 Objective-C、Swift 或 C++代码,而不是用 JavaScript 重新实现一遍;又或者你需要实现某些高性能、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等。

React Native 的高级的特性:可以在其基础上编写真正的原生代码,并且可以访问平台所有的能力。这是一个相对高级的特性,我们并不认为它应当在日常开发的过程中经常出现,但具备这样的能力是很重要的。如果 React Native 还不支持某个你需要的原生特性,你应当可以自己实现该特性的封装。

总结简单一句话就是:如果 React Native 不能满足你,那你就去自定义module。

闲话少说,开始实战,下面我们以实现调用原生的手电筒开关功能为例,此例子将展现RN和原生的双向通信:

iOS平台:

在 React Native 中,一个“iOS原生模块”就是一个实现了“RCTBridgeModule”协议的 Objective-C 类,其中 RCT 是 ReaCT 的缩写。
类名我们暂定为CameraControlUtil。
1.定义头文件

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@interface CameraControlUtil : NSObject<RCTBridgeModule>

@end

为了实现RCTBridgeModule协议,你的类需要包含RCT_EXPORT_MODULE()宏。这个宏也可以添加一个参数用来指定在 JavaScript 中访问这个模块的名字。如果你不指定,默认就会使用这个 Objective-C 类的名字。如果类名以 RCT 开头,则 JavaScript 端引入的模块名会自动移除这个前缀。

2.模块导出

#import "CameraControlUtil.h"
#import <AVFoundation/AVFoundation.h>
@interface CameraControlUtil ()
@property (nonatomic,assign) BOOL lightOn;//手电筒状态,true:开启,false:关闭
@end

@implementation CameraControlUtil
// 导出模块,不添加参数即默认为这个类名
RCT_EXPORT_MODULE();
@end

3.方法定义

你必须明确的声明要给 JavaScript 导出的方法,否则 React Native 不会导出任何方法。声明通过RCT_EXPORT_METHOD()宏来实现:

这里我们定义方法名:devicesetTorchOn;
入参:
state(控制手电筒开关,true:打开,false:关闭);
成功block回调:successCallback(是否成功,true:成功,false:失败);
失败block回调:failCallback(失败回调)

注意:1.JavaScript 方法名
导出到 JavaScript 的方法名是 Objective-C 的方法名的第一个部分。React Native 还定义了一个RCT_REMAP_METHOD()宏,它可以指定 JavaScript 方法名。因为 JavaScript 端不能有同名不同参的方法存在,所以当原生端存在重载方法时,可以使用这个宏来避免在 JavaScript 端的名字冲突。

注意:2.返回值类型必须是void
2.桥接到 JavaScript 的方法返回值类型必须是void。React Native 的桥接操作是异步的,所以要返回结果给 JavaScript,你必须通过回调或者触发事件来进行

/**
 操作手电筒方法
 @param state 控制手电筒开关,true:打开,false:关闭
 @param 操作结果的回调successCallback:是否成功,failCallback:失败回调)
 */
RCT_EXPORT_METHOD(devicesetTorchOn:(BOOL)state resolver:(RCTResponseSenderBlock)successCallback resolver:(RCTResponseSenderBlock)failCallback){
  NSString *callbackData = @"操作成功";
  BOOL isSucc = YES;
  _lightOn = !_lightOn;//可以定义一个变量记录手电筒的状态,也可以根据自己需要使用入参的state控制手电筒开闭。
  Class captureDeviceClass = NSClassFromString(@"AVCaptureDevice");
  if(captureDeviceClass !=nil) {
  AVCaptureDevice*device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
     if([device hasTorch]) { // 判断是否有手电筒
      // 请求独占访问硬件设备
      [device lockForConfiguration:nil];
        if(_lightOn) {
          [device setTorchMode:AVCaptureTorchModeOn];//手电筒开
        }else{
          [device setTorchMode:AVCaptureTorchModeOff]; // 手电筒关
          }
      // 请求解除独占访问硬件设备
       [device unlockForConfiguration];
     }else{
       isSucc = NO;
       callbackData = @"手电筒不可用!";
     }
  }else{
       isSucc = NO;
     callbackData = @"手电筒不可用!";
  }
   //准备回调回去的数据
  successCallback(@[[[NSNumber alloc]initWithBool:isSucc]]);
//  callback(@[[[NSNumber alloc]initWithBool:isSucc],callbackData]);
  failCallback(@[callbackData]);
}

如果只想写一个回调,可以通过数组组装自己的任意回调,比如:

callback(@[[[NSNumber alloc]initWithBool:isSucc],callbackData]);

4.Javascript 中进行调用

***导入模块写法一:
import {
    NativeModules
} from 'react-native';

***导入模块写法二:
var CameraControlUtil = require('react-native').NativeModules.CameraControlUtil;
***devicesetTorchOn方法调用:
//在按钮事件onPressConfirmBtn中调用
onPressConfirmBtn = (open)=>{
      NativeModules.CameraControlUtil.devicesetTorchOn((!this.state.flashlightPress),(success) => {
          //操作成功
          Utils.consoleLog(`打开手电筒成功--${success}`);
         ...//你的成功处理
    },(errorMsg) =>{
        //操作失败,你的失败处理,可以弹框提示
      Utils.consoleLog(`打开手电筒errorMsg--${errorMsg}`);
      Alert.alert(
        '提示',
        errorMsg,
        [
          {text: '我知道了' onPress: () => Utils.consoleLog('操作结果alert--press i know')}
        ])
    })
    }

Android平台:

1.创建模块
本着开发维护方便,在实现某个原生module的时候,我们最好在iOS平台和Android平台上定义相同的module名字以及method名字,最好连回调的数据也同一格式,这样js调用的时候不同平台就不用作不同的处理了。当然,某些特定的情况下,我们也需要提供不同的方法及回调~~~
我们在app的java文件夹下创建一个ModuleForRN文件夹,同一管理整个app中需要的原生module。我们同样创建一个名为CameraControlUtil的module:

package com.hyproducerrn.ModuleForRN;//hyproducerrn为你自己的项目包名
//android依赖
import android.content.Context;
import android.hardware.Camera;
import android.hardware.camera2.CameraManager;
import android.os.Build;
import android.util.Log;

//RN module依赖
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class CameraControlUtil extends ReactContextBaseJavaModule{
    ...模块导出、模块实现
}

2.模块实现
2.1构建方法

  private Camera camera;
  private Boolean isLightOn = false;//记录手电筒状态
  private final ReactApplicationContext myReactContext;
//构建方法
public CameraControlUtil(ReactApplicationContext reactContext) {
        super(reactContext);
        this.myReactContext = reactContext;
    }

2.2模块名称

 /**
     * 返回一个模块名称,rn通过NativeModules可以调用此模块
     */
    @Override
    public String getName() {
        return "CameraControlUtil";
    }

2.3模块实现

/**
     * @param state 控制手电筒开关,true:打开,false:关闭
     * @param successCallback 打开成功的回调
     * @param failCallback 打开失败的回调
     */
    @ReactMethod
    public void devicesetTorchOn(Boolean state, Callback successCallback, Callback failCallback) {
            Camera.Parameters params;
            if (!isLightOn) {
                camera = Camera.open();
                params = camera.getParameters();
                params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                camera.setParameters(params);
                camera.startPreview();
                isLightOn = true;
            } else {
                params = camera.getParameters();
                params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                camera.setParameters(params);
                camera.stopPreview();
                camera.release();
                isLightOn = false;
            }

        successCallback.invoke(true);
        failCallback.invoke("无错误");
    }

3.模块注册
3.1 在ModuleForRN文件夹下新建CustomModuleForRNPackage.java,实现ReactPackage的两个方法,在createNativeModules里添加注册CameraControlUtil模块:

package com.hyproducerrn.ModuleForRN;

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 CustomModuleForRNPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();

       //所有自定义module都写在这里
        modules.add(new OpenSettingsModule(reactContext));//打开设置页面
        modules.add(new AppInfo(reactContext));//获取app版本等信息
        modules.add(new CameraControlUtil(reactContext));//相机闪光灯控制
        return modules;
    }

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

3.2注册CustomModuleForRNPackage
在MainApplication.java的getPackages方法里添加刚才的CustomModuleForRNPackage包:

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

          new CustomModuleForRNPackage(),//添加自定义module包
      );
    }

4.Javascript 中进行调用
由于我们在Android和iOS平台下的module名字和method名字及回调都一样,因此RN调用时,就跟iOS的调用一模一样了,9️⃣不再写一遍啦~~~~😁

以上就是React Native自定义module的介绍了,希望对你有帮助,如有欢迎大家指正错误,欢迎大家留言交流~~O(∩_∩)O哈哈

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容