如题RN的原生模块/Native Modules的开发是一项很重要的技能,但RN官网的示例又比较简单,然后最近我接触与使用、还有阅读了react-native-ble-manager的部份源码,发现里边完全包含了一个Native Modules所涉及的知识点/技术点,故特推荐给大家,共同学习与交流
react-native-ble-manager目前有1.8K的star
如下所示,react-native-ble-manager是RN开发环境下蓝牙低功耗库,用于RN应用下的低功耗蓝牙通讯功能的编程
react-native-ble-manager的代码结构
代码量不多,如果有原生平台下的蓝牙开发/API有基础的话,阅读起来会更容易些
于分层思想理解原生模块
如 Android 原生模块 的指引内容如下
在RN项目开发过程中,大多数情况下我的业务逻辑、基础库等基本是用JS/TS代码去组织与编写的,但实际我们用的大部份RN第三方面都是一个个原生模块形态的SDK。我们在项目中添加一个原生模块相对于构建一个原生模块形态的SDK要简单些,SDK的话还需要处理构建打包。如下是我于分层架构的思想对原生模块的理解。原生模块顾名思义就是需要依赖与使用到原生平台的API或功能的模块,该模块对于上层业务来说是一个普通的JS模块,故原生模块是实际上是存在两大子模块,一个是用JS模块,另一个是平台层模块,JS模块直接服务上层JS业务模块的,即称之为接口模块,然后平台层模块是内部的实现模块,使用平台层语言直接使用平台层的功能特性。那原生模块的JS代码跟平台层代码是如何通讯/交互的呢?这种场景是需要引入一个胶水层来处理,但我们不需要想Android jni开发那样去花大量的精力去构建胶水层,原因RN框架已经提供了这个胶水层的功能,所以我就按RN 原生模块开发的规范来构建与编写相关的代码就行了
从角色视角理解原生模块
通过react-native-ble-manager去看原生模块
介绍Android平台为主
主要文件
主要类图
从scan接口看JS与原生的交互
从JS的scan跟到Java层的scan接口,可以大概了解参数类型的转化,还有JS调用native方法后面回调处理,使用callback的方式
//js代码调scan,RN框架会帮忙代到并调到对应native层的scan方法,同时调用该方法时会做js数据类型到java数据类型的转换
bleManager.scan(
serviceUUIDs,
seconds,
allowDuplicates,
scanningOptions,
//下面这个就是一个js function对应到java就是一个callback类型
(error: string | null) => {
if (error) {
reject(error);
} else {
fulfill();
}
}
);
});
//如下是java代码,BleManager.java类中scan方法
@ReactMethod
public void scan(ReadableArray serviceUUIDs, final int scanSeconds, boolean allowDuplicates, ReadableMap options,
Callback callback) {
Log.d(LOG_TAG, "scan");
if (getBluetoothAdapter() == null) {
Log.d(LOG_TAG, "No bluetooth support");
callback.invoke("No bluetooth support");
return;
}
if (!getBluetoothAdapter().isEnabled()) {
return;
}
synchronized (peripherals) {
for (Iterator<Map.Entry<String, Peripheral>> iterator = peripherals.entrySet().iterator(); iterator
.hasNext(); ) {
Map.Entry<String, Peripheral> entry = iterator.next();
if (!(entry.getValue().isConnected() || entry.getValue().isConnecting())) {
iterator.remove();
}
}
}
if (scanManager != null)
scanManager.scan(serviceUUIDs, scanSeconds, options, callback);
}
注意观察上面的JS代码中调scan方法的参数,然后再对着下面JS数据类型跟JAVA数据类型的关系去看代码。
特别要重点体会callback跟function的映射,因为callback是native方法回调js时经常用到的方式
再来看一下native主动向js交互的case,目前是采用EventEmitter机制来交互的。即JS需要注册事件,然后native端发送对应的事件即可。这个事件从Jvmruntime到Js runtime的胶水层RN已经帮忙我们实现了,调用对应的emmiter组件即可
如下是JS端注册事件的代码
const BleManagerModule = NativeModules.BleManager;
const bleManagerEmitter = new NativeEventEmitter(BleManagerModule);
//注删事件ID是一个String,如下面的 BleManagerDiscoverPeripheral,然后我们去native端的代码去搜索BleManagerDiscoverPeripheral
....
const listeners = [
bleManagerEmitter.addListener(
'BleManagerDiscoverPeripheral',
handleDiscoverPeripheral,
),
bleManagerEmitter.addListener('BleManagerStopScan', handleStopScan),
bleManagerEmitter.addListener(
'BleManagerDisconnectPeripheral',
handleDisconnectedPeripheral,
),
bleManagerEmitter.addListener(
'BleManagerDidUpdateValueForCharacteristic',
handleUpdateValueForCharacteristic,
),
];
//通过BleManagerDiscoverPeripheral看到native的有这样的代码,
private void onDiscoveredPeripheral(final ScanResult result) {
.....
WritableMap map = peripheral.asWritableMap();
bleManager.sendEvent("BleManagerDiscoverPeripheral", map);
}
public void sendEvent(String eventName, @Nullable WritableMap params) {
getReactApplicationContext().getJSModule(RCTNativeAppEventEmitter.class).emit(eventName, params);
}
总结
1、native端用RCTNativeAppEventEmitter来发送事件,js端用NativeEventEmitter来注册事件,即达到下层可以主动向上层通讯
2、callback经常用来作native方法的调用结果返回到JS runtime的常规操作,即方法调用与结果返回,一来一回的双向操作。
建议
1、用webstorm打开整个目标,方便阅读js/ts代码
2、用AS打开Android目录,方便阅读java代码
3、用xcode或appcode打开ios目录,方便阅读ios代码