文章目录
前言
RN是一个跨平台的架构,一套代码可以运行在iOS和Android两端,然而当涉及调用原生API时,不同平台会有差异性,例如蓝牙操作等。RN通过NativeModule
将Java/Objective-C/C++ (native)
等原生类映射为JS对象,从而达到调用原生方法的目的。
NativeModule可以帮助你复用现有的原生方法,或者通过原生实现一些高性能,多线程的功能。
区分平台
很多时候我们会的代码会需要区分平台,例如一个按钮在iOS上显示蓝色,在Android上显示红色。RN提供两种方式用于区分平台。
- 通过
platform
模块 - 使用平台相关的文件拓展名
platform模块
import {Platform, StyleSheet} from 'react-native';
const styles = StyleSheet.create({
height: Platform.OS === 'ios' ? 200 : 100,
});
Platform.OS
包含了当前平台信息可选值为ios 和 android
import {Platform, StyleSheet} from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
...Platform.select({
ios: {
backgroundColor: 'red',
},
android: {
backgroundColor: 'green',
},
default: {
// other platforms, web for example
backgroundColor: 'blue',
},
}),
},
});
Platform.select
会根据当前系统返回不同的对象
import {Platform} from 'react-native';
if (Platform.Version === 25) {
console.log('Running on Nougat!');
}
const majorVersionIOS = parseInt(Platform.Version, 10);
if (majorVersionIOS <= 9) {
console.log('Work around a change in behavior');
}
Platform.Version
返回当前平台版本,注意ios需要通过parseInt(Platform.Version, 10)
转换一下
平台相关文件拓展名
通过 .ios.
或 .android.
拓展名可以定义区分平台的文件,例如
BigButton.ios.js
BigButton.android.js
这里定义的两个同名组件,当运行平台为iOS时,则加载BigButton.ios.js
Android NativeModule使用
我们通过RN调用原生日历API创建日历事件这个案例来熟悉如何使用NativeModule
- 在Android原生项目中新建文件
CalendarModule.kt
,路径为android/app/src/main/java/com/your-app-name/
package com.your-apps-package-name; // replace your-apps-package-name with your app’s package name
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
class CalendarModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
// add to CalendarModule.kt
override fun getName() = "CalendarModule"
}
对于提供给RN的类,需要继承ReactContextBaseJavaModule
,并且实现方法getName
返回模块名。getName
返回的名称也是RN调用时的名称
//JS 引入NativeModule
const {CalendarModule} = NativeModules?.CalendarModule;
- 通过@ReactMethod注解定义供RN使用的方法
import android.util.Log
//@ReactMethod(isBlockingSynchronousMethod = true)
@ReactMethod
fun createCalendarEvent(name: String, location: String) {
Log.d("CalendarModule", "Create event called with name: $name and location: $location")
}
@ReactMethod
注解可通过isBlockingSynchronousMethod
参数定义此函数是否加锁。定义好函数以后,RN可通过如下方式调用
NativeModules?.CalendarModule?.createCalendarEvent(name, location)
- 注册模块,定义好的类需要通过ReactPackage注册才可以使用
package com.your-app-name // replace your-app-name with your app’s name
import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager
class MyAppPackage : ReactPackage {
override fun createViewManagers(
reactContext: ReactApplicationContext
): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
override fun createNativeModules(
reactContext: ReactApplicationContext
): MutableList<NativeModule> = listOf(CalendarModule(reactContext)).toMutableList()
}
首先新建一个类继承ReactPackage
,然后在createNativeModules
方法中返回我们定义的模块,这里是可以返回一个列表,也就是说我们可以定义多个模块同时返回。ReactPackage
又该何时使用呢?
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
add(MyAppPackage())
}
在 ReactNativeHost
下有一个getPackages
方法, 在这里返回即可
ReactNativeHost 是 ReactApplication中必须通过getReactNativeHost返回的参数,也就是ReactNative在原生部分的开始。
到这里整个创建注册NativeModule流程已经结束。
参数转换
在调用原生方法时,JS的数据类型和JAVA/Kotlin数据类型之间要进行相互转换,转换方式如下:
对于以上没有定义的参数,需要自行转换。
NativeModule进阶
导出常量
ReactContextBaseJavaModule
还有一个可复写的函数getConstants
,此函数返回Map,我们可以在此定义常量
override fun getConstants(): MutableMap<String, Any> =
hashMapOf("DEFAULT_EVENT_NAME" to "New Event")
这样RN就可以通过getConstants获取到这些常量
console.log(CalendarModule.getConstants().DEFAULT_EVENT_NAME);
异步方法
Callbacks
对于想要知道异步方法的执行结果的情况,其中一个解决方法是使用callback回调,NativeModule提供一种特殊的参数,即Callback
。
package com.facebook.react.bridge;
/**
* Interface that represent javascript callback function which can be passed to the native module as
* a method parameter.
*/
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只有一个invoke方法,并且接受Object数组。
Promises
另一种异步方法的处理则是通过为原生方法新增Promises
参数
@ReactMethod
fun createCalendarEvent(name: String, location: String, promise: Promise) {
try {
val eventId = ...
promise.resolve(eventId)
} catch (e: Throwable) {
promise.reject("Create Event Error", e)
}
}
promise有resolve
和reject
两个方法,前者代表成功执行后的回调,后者代表出现异常时的回调。这样定义的原生方法,在JS中有两种处理方式:
const onSubmit = async () => {
try {
const eventId = await CalendarModule.createCalendarEvent(
'Party',
'My House',
);
console.log(`Created a new event with id ${eventId}`);
} catch (e) {
console.error(e);
}
};
第一种是通过await
关键词他会阻塞直到函数执行resolve返回后。需要注意的是await
关键词必须运行在async
修饰的函数
const onSubmit = () => {
CalendarModule.createCalendarEvent(
'Party',
'My House',
).then(eventId => {
console.log(`Created a new event with id ${eventId}`);
}).catch(e => {
console.error(e);
});
};
第二种是如上通过回调的处理方式
发送事件
原生模块可以向RN发送事件,RN可以注册监听想要的事件
...
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.Arguments
import com.facebook.react.modules.core.DeviceEventManagerModule
...
private fun sendEvent(reactContext: ReactContext, eventName: String, params: WritableMap?) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params)
}
...
val params = Arguments.createMap().apply {
putString("eventProperty", "someValue")
}
...
sendEvent(reactContext, "EventReminder", params)
发送事件需要明确事件名和参数,并通过emit函数传入
import {DeviceEventEmitter} from 'react-native';
...
useEffect(() => {
DeviceEventEmitter.addListener(
eventName,
notifyCallback,
)
return () => {
DeviceEventEmitter.removeAllListeners(eventName);
};
}, []);
RN通过DeviceEventEmitter进行监听