React Native与Android原生之间的通讯
在React Native中可以通过在java层自定义@ReactMethod
方式给JavaScript调用,这样在JavaScript层就可以直接调用Android中的Native方法。但是很多场景下需要我们去回调获取结果。这时候就需要实现RN与原生之间的通讯。
对于RN与Android原生之间的通讯,主要有以下几种方式帮助我们来实现:
- Callback
- Promise
- RCTDeviceEventEmitter
- 从
startActivityForResult
中获取结果
1. Callback
Callback
提供了一个函数来把返回值传回给JavaScript。
Callback
是react.bridge中的一个接口,它作为ReactMethod
的一个传参,用来映射JavaScript的回调函数(function)。Callback
接口只定义了一个方法invoke
,invoke
接受多个参数,这个参数必须是react.bridge中支持的参数。
一下的例子都以Toast作为例子。
定义用于实现RN的回调:
class ToastModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName(): String {
return "ToastExample"
}
接下来实现使用Callback方式的ReactMethod,在这里只实现了一个Callback的方式,你也可以将Callback分成两个,分别是successCallback与errorCallBack来区分成功与失败的情况:
/**
* Callback方式实现通信
* */
@ReactMethod
fun showToastWithCallback(message: String, callback: Callback) {
try {
Toast.makeText(reactApplicationContext, message, Toast.LENGTH_SHORT).show()
callback.invoke("Callback Invoke Message")
} catch (e: Exception) {
callback.invoke(e.message)
}
}
在JavaScript里可以这样使用,在点击后回调会弹出Toast与Alert窗口:
<Button
onPress={() => {
NativeModules.ToastExample.showToastWithCallback("CallBack", msg => {
Alert.alert("Message", `Message: ${msg}`, null);
});
}}
title="Callback"
color="#841584"
/>
但是需要注意的是:callback 并非在对应的原生函数返回后立即被执行——注意跨语言通讯是异步的,这个执行过程会通过消息循环来进行。
2. Promises
Promise是ES6中增加的对于异步编程和回调更加友好的API,使用Promise可以更简洁,更灵活地处理回调。搭配 ES2016(ES7)标准的async/await语法则效果更佳。
在react.briage中定义的Promise
接口,实现了resolve
和reject
的方法,resolve
用来处理正确处理结果的情况,reject
用来处理异常的情况。
使用Promise定义ReactMethod:
/**
* Promise方式实现通信
* */
@ReactMethod
fun showToastWithPromise(message: String, promise: Promise) {
try {
Toast.makeText(reactApplicationContext, message, Toast.LENGTH_SHORT).show()
val thread = Thread.currentThread().name
promise.resolve("Promise Resolve Message and Current Thread: $thread")
} catch (e: Exception) {
promise.reject("Error", e)
}
}
JavaScript 端的方法会返回一个 Promise:
<Button
onPress={() => {
NativeModules.ToastExample.showToastWithPromise("Promise")
.then(threadName =>
Alert.alert("Message", `${threadName}`, null)
)
.catch(err =>
Alert.alert(
"Error",
`get thread name error: ${err.message}`,
null
)
);
}}
title="Promise"
color="#379031"
/>
使用Promise比使用Callback更加的简洁,还能更加灵活的在多线程之间进行切换。
3. RCTDeviceEventEmitter
还有一种简单的方式是使用RCTDeviceEventEmitter
。
在Java中的实现如下:
/**
* RCTDeviceEventEmitter(组件通讯)实现
* */
@ReactMethod
fun showToastWithSendEvent(message: String) {
val params = Arguments.createMap().apply {
putString("key", message)
}
this.reactApplicationContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit("onSendEventResult", params)
}
在JS则需要增加一些操作:
import { DeviceEventEmitter } from "react-native";
...
export default class XXX extends Component<Props> {
componentDidMount() {
//接受到通知后的处理
this.listener = DeviceEventEmitter.addListener("onSendEventResult", msg => {
NativeModules.ToastExample.showToast(msg.key, ToastExample.LONG);
});
}
componentWillUnmount() {
//异除,不要忘记
this.listener.remove();
}
...
<Button
onPress={() => {
//点击事件
NativeModules.ToastExample.showToastWithSendEvent("Send Event");
}}
title="Send Event"
color="#771232"
/>
}
4. 从startActivityForResult中获取结果
如果你使用startActivityForResult
调起了一个 activity 并想从其中获取返回结果,那么你需要监听onActivityResult
事件。具体的做法是继承BaseActivityEventListener
或是实现ActivityEventListener
。我们推荐前一种做法,因为它相对来说不太会受到 API 变更的影响。然后你需要在模块的构造函数中注册这一监听事件。
其实是对Promise方式的一种延伸。
reactContext.addActivityEventListener(mActivityResultListener);
现在你可以通过重写下面的方法来实现对onActivityResult
的监听:
@Override
public void onActivityResult(
final Activity activity,
final int requestCode,
final int resultCode,
final Intent intent) {
// 在这里实现你自己的逻辑
}
下面我们是简单的图片选择器来实践一下。这个图片选择器会把pickImage方法暴露给 JavaScript,而这个方法在调用时就会把图片的路径返回到 JS 端。
public class ImagePickerModule extends ReactContextBaseJavaModule {
private static final int IMAGE_PICKER_REQUEST = 467081;
private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
private static final String E_PICKER_CANCELLED = "E_PICKER_CANCELLED";
private static final String E_FAILED_TO_SHOW_PICKER = "E_FAILED_TO_SHOW_PICKER";
private static final String E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND";
private Promise mPickerPromise;
private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
if (requestCode == IMAGE_PICKER_REQUEST) {
if (mPickerPromise != null) {
if (resultCode == Activity.RESULT_CANCELED) {
mPickerPromise.reject(E_PICKER_CANCELLED, "Image picker was cancelled");
} else if (resultCode == Activity.RESULT_OK) {
Uri uri = intent.getData();
if (uri == null) {
mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, "No image data found");
} else {
mPickerPromise.resolve(uri.toString());
}
}
mPickerPromise = null;
}
}
}
};
public ImagePickerModule(ReactApplicationContext reactContext) {
super(reactContext);
// Add the listener for `onActivityResult`
reactContext.addActivityEventListener(mActivityEventListener);
}
@Override
public String getName() {
return "ImagePickerModule";
}
@ReactMethod
public void pickImage(final Promise promise) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
return;
}
// Store the promise to resolve/reject when picker returns data
mPickerPromise = promise;
try {
final Intent galleryIntent = new Intent(Intent.ACTION_PICK);
galleryIntent.setType("image/*");
final Intent chooserIntent = Intent.createChooser(galleryIntent, "Pick an image");
currentActivity.startActivityForResult(chooserIntent, IMAGE_PICKER_REQUEST);
} catch (Exception e) {
mPickerPromise.reject(E_FAILED_TO_SHOW_PICKER, e);
mPickerPromise = null;
}
}
}
5. 总结
方法 | 优点 | 缺点 |
---|---|---|
Callback | JS调用,Native返回 | CallBack为异步操作,返回时机不确定 |
Promise | JS调用,Native返回 | 每次使用需要JS调用一次 |
RCTDeviceEventEmitter | 可任意时刻传递,Native主导控制 | 记得移除Listener并且保持通知名称一致(细节问题) |