期待已久的新课上线啦!解锁React Native开发新姿势,一网打尽React Native最新与最热技术,点我Get!!!

前言

一直想写一下我在React Native原生模块封装方面的一些经验和心得,来分享给大家,但实在抽不开身,今天看了一下日历发现2018年马上就结束了,所以就赶年底将这篇博文写好并发布(其实是两篇:要看iOS篇的点这里《React Native iOS原生模块开发》)。

我平时在用React Native开发App时会用到一些原生模块,比如:在做社会化分享、第三方登录、扫描、通信录,日历等等,想必大家也是一样。

关于在React Native中使用原生模块,在这里引用React Native官方文档的一段话:

有时候App需要访问平台API,但在React Native可能还没有相应的模块。或者你需要复用一些Java代码,而不想用JavaScript再重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、数据库、或者一些高级扩展等等。
我们把React Native设计为可以在其基础上编写真正的原生代码,并且可以访问平台所有的能力。这是一个相对高级的特性,我们并不期望它应当在日常开发的过程中经常出现,但它确实必不可少,而且是存在的。如果React Native还不支持某个你需要的原生特性,你应当可以自己实现对该特性的封装。

上面是我翻译React Native官方文档上的一段话,大家如果想看英文版可以点这里:Native Modules
在这篇文章中呢,我会带着大家来开发一个从相册获取照片并裁切照片的项目,并结合这个项目来具体讲解一下如何一步步开发React Native Android原生模块的。

提示:告诉大家一个好消息,React Native视频教程发布了,大家现可以看视频学React Native了。

首先,让我们先看一下,开发Android原生模块的主要流程。

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

在这里我把构建React Native Android原生模块的流程概括为以下三大步:

  1. 编写原生模块的相关Java代码;
  2. 暴露接口与数据交互;
  3. 注册与导出React Native原生模块;

接下来让我们一起来看一下每一步所需要做的一些事情。

原生模块开发实战

在这里我们就以开发一个从相册获取照片并裁切照片的实战项目,来具体讲解一下如何开发React Native Android原生模块的。

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

这一步我们需要用到AndroidStudio。
首先我们用AndroidStudio打开React Native项目根目录下的android目录,如图:

用AndroidStudio第一次打开这个Android项目的时候,AndroidStudio会下载一些此项目所需要的依赖,比如项目所依赖的Gradle版本等。这些依赖下载完成之后呢,AndroidStudio会对项目进行初始化,初始化成功之后在AndroidStudio的工具栏中可以看到一个名为“app”的一个可运行的模块,如图:

接下来呢,我们就可以编写Java代码了。

首先呢,我们先来实现一个Crop接口:

public interface Crop {/*** 选择并裁切照片* @param outputX* @param outputY* @param promise*/void selectWithCrop(int outputX,int outputY,Promise promise);
}

我们创建一个CropImpl.java,在这个类中呢,我们实现了从相册选择照片以及裁切照片的功能:

/*** React Native Android原生模块开发* Author: CrazyCodeBoy* 技术博文:http://www.devio.org* GitHub:https://github.com/crazycodeboy* Email:crazycodeboy@gmail.com*/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;}@Overridepublic 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);}
}

查看视频教程

关于Android拍照、从相册或文件中选择照片,裁剪以及压缩照片等更高级的功能实现,大家可以参考开源项目TakePhoto

实现了从相册选择照片以及裁切照片的功能之后呢,接下来我们需要将public void selectWithCrop(int aspectX, int aspectY, Promise promise)暴露给React Native,以供js调用。

暴露接口与数据交互

接下了我们就向React Native暴露接口以及做一些数据交互部分的操作。为了暴露接口以及进行数据交互我们需要借助React Native的ReactContextBaseJavaModule类,在这里我们创建一个ImageCropModule.java类让它继承自ReactContextBaseJavaModule

创建一个ReactContextBaseJavaModule

/*** React Native Android原生模块开发* Author: CrazyCodeBoy* 技术博文:http://www.devio.org* GitHub:https://github.com/crazycodeboy* Email:crazycodeboy@gmail.com*/public class ImageCropModule extends ReactContextBaseJavaModule implements Crop{private CropImpl cropImpl;public ImageCropModule(ReactApplicationContext reactContext) {super(reactContext);}@Overridepublic String getName() {return "ImageCrop";}//...省略部分代码@Override @ReactMethodpublic 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.java类中,我们重写了public String getName()方法,来暴露我们原生模块的名字。并在public void selectWithCrop(int aspectX, int aspectY, Promise promise)上添加了@ReactMethod注解来暴露接口,这样以来我们就可以在js文件中通过ImageCrop.selectWithCrop来调用我们所暴露给React Native的接口了。

接下来呢,我们来看一下原生模块和js模块是如何进行数据交互的?

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

在我们要实现的从相册选择照片并裁切的项目中,js模块需要告诉原生模块照片裁切的比例,等照片裁切完成后,原生模块需要对js模块进行回调来告诉js模块照片裁切的结果,在这里我们需要将照片裁切后生成的图片的路径告诉js模块。

提示:在所有的情况下js和原生模块之前进行通信都是在异步的情况下进行的。

接下来我们就来看下一JS是如何向原生模块传递数据的?

JS向原生模块传递数据:

为了实现JS向原生模块进行传递数据,我们可以直接通过调用原生模块所暴露出来的接口,来为接口方法设置参数。这样以来我们就可以将数据通过接口参数传递到原生模块中,如:

  /*** 选择并裁切照片* @param outputX* @param outputY* @param promise*/void selectWithCrop(int outputX,int outputY,Promise promise);

通过上述代码我们可以看出,js模块可以通过selectWithCrop方法来告诉原生模块要裁切照片的宽高比,最后一个参数是一个Promise,照片裁剪完成之后呢,原生模块可以通过Promise来对js模块进行回调,来告诉裁切结果。

既然是js和Java进行数据传递,那么他们两者之间是如何进行类型转换的呢:
在上述例子中我们通过@ReactMethod注解来暴露接口,被 @ReactMethod标注的方法支持如下几种数据类型。

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

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

原生模块向JS传递数据:

原生模块向JS传递数据我们可以借助Callbacks与Promises,接下来就讲一下如何通过他们两个进行数据传递的。

Callbacks

原生模块支持一个特殊类型的参数-Callbacks,我们可以通过它来对js进行回调,以告诉js调用原生模块方法的结果。
将我们selectWithCrop的参数改为Callbacks之后:

@Override
public void selectWithCrop(int aspectX, int aspectY, Callback errorCallback,Callback successCallback) {this.errorCallback=errorCallback;this.successCallback=successCallback;this.aspectX=aspectX;this.aspectY=aspectY;this.activity.startActivityForResult(IntentUtils.getPickIntentWithGallery(),RC_PICK);
}

在回调的时候,我们就可以这样写:

if (resultCode == Activity.RESULT_OK) {successCallback.invoke(outPutUri.getPath());
}else {errorCallback.invoke(CODE_ERROR_CROP,"裁剪失败");
}

在上述代码中我们通过Callbackinvoke方法来对js进行对调,下面我们来看一下Callback.java的源码:

public interface Callback {/*** Schedule javascript function execution represented by this {@link Callback} instance** @param args arguments passed to javascript callback method via bridge*/public void invoke(Object... args);
}

Callback.java的源码中我们可以看出,它是一个只有一个public void invoke(Object... args)方法的接口,invoke方法接受一个可变参数,所以我们可以向js传递多个参数。

接下来呢,我们在js中就可以这样来调用我们所暴露的接口:

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

提示:另外要告诉大家的是,无论是Callback还是我接下来要讲的Promise,我们只能调用一次,也就是"you call me once,I can only call you once"。

Promises

除了上文所讲的Callback之外React Native还为了我们提供了另外一种回调js的方式叫-Promise。如果我们暴露的接口方法的最后一个参数是Promise时,如:

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

那么当js调用它的时候将会返回一个Promsie:

ImageCrop.selectWithCrop(parseInt(x),parseInt(y)).then(result=> {this.setState({result: result})
}).catch(e=> {this.setState({result: e})
});

另外,我们也可以使用ES2016的 async/await语法,来简化我们的代码:

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

这样以来代码就简化了很多。

因为,基于回调的数据传递无论是Callback还是Promise,都只能调用一次。但,在实际项目开发中我们有时会向js多次传递数据,比如二维码扫描原生模块,针对这种多次数据传递的情况我们该怎么实现呢?

接下来我就为大家介绍一种原生模块可以向js多次传递数据的方式:

向js发送事件

在原生模块中我们可以向js发送多次事件,即使原生模块没有被直接的调用。为了向js传递事件我们需要用到RCTDeviceEventEmitter,它是原生模块和js之间的一个事件发射器。

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

在上述方法中我们可以向js模块发送任意次数的事件,其中eventName是我们要发送事件的事件名,params是此次事件所携带的数据,接下来呢我们就可以在js模块中监听这个事件了:

componentDidMount() {//注册扫描监听DeviceEventEmitter.addListener('onScanningResult',this.onScanningResult);
}
onScanningResult = (e)=> {this.setState({scanningResult: e.result,});
}

另外,不要忘记在组件被卸载的时候移除监听:

componentWillUnmount(){DeviceEventEmitter.removeListener('onScanningResult',this.onScanningResult);//移除扫描监听
}

到现在呢,暴露接口以及数据传递已经进行完了,接下来呢,我们就需要注册与导出React Native原生模块了。

注册与导出React Native原生模块

为了向React Native注册我们刚才创建的原生模块,我们需要实现ReactPackageReactPackage主要为注册原生模块所存在,只有已经向React Native注册的模块才能在js模块使用。

/*** React Native Android原生模块开发* Author: CrazyCodeBoy* 技术博文:http://www.devio.org* GitHub:https://github.com/crazycodeboy* Email:crazycodeboy@gmail.com*/
public class ImageCropReactPackage implements ReactPackage {@Overridepublic List<Class<? extends JavaScriptModule>> createJSModules() {return Collections.emptyList();}@Overridepublic List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {return Collections.emptyList();}@Overridepublic List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {List<NativeModule> modules = new ArrayList<>();modules.add(new ImageCropModule(reactContext));return modules;}
}

查看视频教程

在上述代码中,我们实现一个ReactPackage,接下来呢,我们还需要在android/app/src/main/java/com/your-app-name/MainApplication.java中注册我们的ImageCropReactPackage

@Override
protected List<ReactPackage> getPackages() {return Arrays.<ReactPackage>asList(new MainReactPackage(),new ImageCropReactPackage()//在这里将我们刚才创建的ImageCropReactPackage添加进来);
}

原生模块注册完成之后呢,我们接下来就需要为我们的原生模块导出一个js模块,以方便我们使用它。

我们创建一个ImageCrop.js文件,然后添加如下代码:

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

这样以来呢,我们就可以在其他地方通过下面方式来使用我们所导出的这个模块了:

import ImageCrop from './ImageCrop' //导入ImageCrop.js
//...省略部分代码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})});}
//...省略部分代码
}

查看视频教程

现在呢,我们这个原生模块就开发好了,而且我们也使用了我们的这个原生模块。关于Android拍照、从相册或文件中选择照片,裁剪以及压缩照片等更高级的功能实现,大家也可以参考开源项目TakePhoto

关于线程

在React Native中,JS模块运行在一个独立的线程中。在我们为React Native开发原生模块的时候,如果有耗时的操作比如:文件读写、网络操作等,我们需要新开辟一个线程,不然的话,这些耗时的操作会阻塞JS线程。在Android中我们可以借助AsyncTask来实现多线程。另外,如果原生模块中需要更新UI,我们需要获取主线程,然后在主线程中更新UI,如:http://coding.imooc.com/class/304.html

        activity.runOnUiThread(new Runnable() {@Overridepublic void run() {if (!activity.isFinishing()) {mSplashDialog = new Dialog(activity,fullScreen? R.style.SplashScreen_Fullscreen:R.style.SplashScreen_SplashTheme);mSplashDialog.setContentView(R.layout.launch_screen);mSplashDialog.setCancelable(false);if (!mSplashDialog.isShowing()) {mSplashDialog.show();}}}});

可参考:SplashScreen.java

告诉大家一个好消息,为大家精心准备的React Native视频教程发布了,大家现可以看视频学React Native了。

如果,大家在开发原生模块中遇到问题可以在课程的对应章节的右边进行留言,我看到了后会及时回复的哦。

推荐学习:视频教程《最新版React Native+Redux打造高质量上线App》

如何开发React Native 原生模块(Native Modules)?看完这篇文章就够了(Android)相关推荐

  1. Web前端开发的思考与感悟,看完这篇文章你再考虑是否入坑!

    最近几年对于web前端的传闻很多,比如人才稀缺,简单易学,待遇丰厚,整体势头发展良好等等.遇到过一个不太熟搞后台开发的同事跑来问我学习前端需要掌握哪些内容,也听说过一个搞IOS开发准备自学前端半个月然 ...

  2. 【React】看完这篇文章能够学会React初级技术

    本文是根据链接这个视频系列的笔记做的学习记录整理,讲课风格很有趣(2倍速跟小甲鱼声音很像嘿嘿嘿) 在深入学习react之前建议先看看一些react的思想,相关文章我已经总结出来啦!半小时理解react ...

  3. 看完这篇文章保你面试稳操胜券——React篇

    ✨ 进大厂收藏这一系列就够了,全方位搜集总结,为大家归纳出这篇面试宝典,面试途中祝你一臂之力!,共分为四个系列 ✨ 本 篇 为 < 看 完 这 篇 文 章 保 你 面 试 稳 操 胜 券 > ...

  4. Rust: 基于 napi-rs 开发 Node.js 原生模块

    Rust: 基于 napi-rs 开发 Node.js 原生模块 文章目录 Rust: 基于 napi-rs 开发 Node.js 原生模块 完整代码示例 背景 & napi 环境/工具链准备 ...

  5. Vue开发入门看这篇文章就够了

    摘要: 很多值得了解的细节. 原文:Vue开发看这篇文章就够了 作者:Random Fundebug经授权转载,版权归原作者所有. 介绍 Vue 中文网 Vue github Vue.js 是一套构建 ...

  6. 收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!

    Python Web全栈开发入门实战教程教程    大家好,我叫亓官劼(qí guān jié ),这个<Python Web全栈开发入门实战教程教程>是一个零基础的实战教程,手把手带你开 ...

  7. react native 原生模块桥接的简单说明

    原文出自:https://github.com/prscX/awes... 博客链接:https://ssshooter.com/2019-02... Android 创建原生模块包 通过继承 Rea ...

  8. asp开发工具_VSCode搭建完美的asp.net core开发环境,看完这篇就够了

    引言 由于.net core的全面跨平台,我也在之前的一篇文章中介绍了如何在深度Deepin操作系统上安装并搭建.net core的开发环境,当时介绍的是安装.net core和使用Rider.net ...

  9. web开发中的长度单位(px,em,ex,rem),如何运用,看完这篇就够了!

    原创 2017-03-08 web小二 web前端开发 作为一名前端开发人员,css中的长度单位,都是我们在工作中非常熟悉的名词,因为没有它们,我们就不能声明某个字符应该多大,或者某些图像周围应该留白 ...

最新文章

  1. 使用java导入某个msn帐号的好友列表并发送消息
  2. windows下memcache的安装总结
  3. 第二次尝试修复Hbase2出现Region不一致,使用 HBCK2 - 2021.11.15
  4. 火力发电厂与变电站设计防火标准_真题—火力发电厂1
  5. 基于OpenCL的数字地形分析之坡度坡向提取
  6. 米勒机会信托或将通过GBTC投资比特币
  7. 编程实现strstr函数
  8. matlab中使用xlsread导入excel数据
  9. 机器翻译 | 反向翻译 (back-translation) 笔记
  10. 物联网全景动态图谱2.0|PaaS物联网平台汇总(上篇)
  11. Java/JVM垃圾回收机制和算法总结
  12. 计算机启动时蓝屏后自动重起,升级Win10系统之后一开机就蓝屏且电脑不断重启怎么解决?...
  13. 北理计算机实验18,18北理应用统计经验贴
  14. 服务器装系统步骤图解win7,win7 硬盘重装系统步骤图解|win7系统硬盘重装教程
  15. 压敏电阻MOV特性及选用
  16. 雨林木风linux安装教程,雨林木风修复win7系统虚拟机安装linux提示network error的办法...
  17. Web前端之样式继承与其他概念
  18. 知识贴:电子面单与传统面单的区别
  19. php网盘代码,php网盘源码
  20. petalinux uboot源码怎么打补丁

热门文章

  1. IPCA: Instrumented Principal Component Analysis 计算部分
  2. ttl和stlink_修改TTL值其实非常简单
  3. 九月十月百度,迅雷,华为,阿里巴巴最新校招笔试面试三十题(10.18)
  4. 阴影映射(Shadow Map)的研究(三)
  5. 记第一次ssm整合的配置文件
  6. 融合计费打造全业务核心竞争力
  7. 《淘宝网开店 进货 运营 管理 客服 实战200招》——1.7 网上开店为什么会失败...
  8. Java布局管理器详解
  9. 原生小程序学习小结,mpvue+mpvue-weui+fyl.js小程序项目搭建笔记
  10. Transfromers的tokenizer