rn ~ android 模块通讯混合跳转方案详解
android原生 接入rn模块
android原生 ~ rn 通信
Android系统为我们提供了webview来加载网页,为了让webview加载的网页可以与App交互,系统提供了一套机制帮助我们更方便的实现通信。同样为了实现React Native与原生App之间的通信,FB也实现了自己的一套交互机制。
通信原理
native 调用 rn
RCTDeviceEventEmitter
js端通过RCTDeviceEventEmitter监听native端发送的事件,来实现原生端主动到js端的通信。
就像Android中的广播,iOS中的通知中心
原生端:
public void accessRn() {
WritableMap params = Arguments.createMap();
params.putString("result", xxx);
sendEvent(getReactApplicationContext(), "Native2Rn", params);
}
private void sendEvent(ReactContext reactContext,String eventName, @Nullable WritableMap params) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
上述代码表示原生发送一个名为“Native2Rn”的事件给rn,如下,rn中会监听名为“Native2Rn”的事件
js端:
componentDidMount() {
//注册扫描监听
DeviceEventEmitter.addListener('Native2Rn',this.onScanningResult);
}
onScanningResult = (e)=> {
this.setState({
scanningResult: e.result,
});
// DeviceEventEmitter.removeListener('onScanningResult',this.onScanningResult);//移除扫描监听
}
在JS中通过DeviceEventEmitter注册监听了名为“Native2Rn”的事件,当原生模块发出名为“Native2Rn”的事件后,绑定在该事件上的onScanningResult = (e)会被回调。 然后通过e.result就可获得事件所携带的数据
注意: 如果在JS中有多处注册了onScanningResult事件,那么当原生模块发出事件后,这几个地方会同时收到该事件
rn 调用 native
上面通信资料中有讲解,rn调用native,需要native在module中通过@ReactMethod注解暴露方法给rn,然后rn就可以通过NativeModules组件访问到native的方法
callback
/**
* RN调用Android原生,使用Callback方式
* 注意Callback参数引入的必须是react的,别引入错了
* */
@ReactMethod
public void rnCallAndroidWithCallback(String msg, Callback callback) {
//处理msg...
String result = msg + "被Android原生处理啦,我要告诉RN处理结果";
//回调,可以有多个参数callback.invoke(result,"1","abc");
callback.invoke(result);
}
JS端:
CustomNativeModule = NativeModules.CustomNativeModule;
CustomNativeModule. rnCallAndroidWithCallback("msg", (result) => {
console.log(result);
});
promise
/**
* RN调用Android原生,使用Promise方式
*
* */
@ReactMethod
public void rnCallAndroidWithPromise(String msg, Promise promise) {
//处理msg...
String result = msg + "被Android原生处理啦,我要告诉RN处理结果";
//回调
promise.resolve(result);
//error
// promise.reject(XXXXXXXX);
}
注意:在原生模块中Promise类型的参数要放在最后一位,这样JS调用的时候才能返回一个Promise
JS端:
- 同步方式:
CustomNativeModule = NativeModules.CustomNativeModule;
try {
var {
result,
} = await CustomNativeModule.rnCallAndroidWithPromise("msg");
} catch (e) {
console.error(e);
}
- 异步方式:
CustomNativeModule = NativeModules.CustomNativeModule;
CustomNativeModule.rnCallAndroidWithPromise("msg").then(e=>{
this.setState({
result:msg
})
}).catch(error=>{
console.log(error);
});
注意:上面代码所在函数,函数名前加 async。如:async func() {xxx}
页面跳转方案
基于上面的通信讲解,native和rn能够相互给对方喊话,就可以跟对方说我要你干什么了。。
native 跳转 rn
基于android原生和rn通信讲解:入参 > rn判断加载不同的rn模块
无回调方式:native通过intent向rn传值
Android端Module新建方法
/**
* 获取当前activity传递的intent
*/
@ReactMethod
public void getDataFromIntent(Callback success, Callback error) {
try {
Activity activity = getCurrentActivity();
if (activity != null) {
String flag = activity.getIntent().getStringExtra("flag");
if (TextUtils.isEmpty(flag)) {
flag = "default value";
}
success.invoke(flag);
}
} catch (Exception e) {
error.invoke(e.getMessage());
throw new JSApplicationIllegalArgumentException(
"Could not open the activity : " + e.getMessage());
}
}
JS端调用
CustomNativeModule = NativeModules.CustomNativeModule;
CustomNativeModule.getDataFromIntent(success => {
console.warn(success);
}, error => {
console.warn(error);
});
根据native 到 rn的传参,在index.js中根据flag判断渲染不同的rn模块页面即可,原理跟ios是一样的,只是传参有一些差别。所以,这块,android,ios 页面跳转方案一样
startActivityForResult方式:rn关闭后,native拿到返回值
Android端
/**
* native 打开js for result
* js 的回调
*/
@ReactMethod
public void finishActivity(String result) {
Activity currentActivity = getCurrentActivity();
Intent intent = new Intent();
intent.putExtra("result", result);
currentActivity.setResult(100, intent);
currentActivity.finish();
}
JS端
finish() {
let CustomNativeModule = NativeModules.CustomNativeModule;
CustomNativeModule.finishActivity('result');
}
从上面的两种通讯方式可以理解到,原生 ~ rn 就是通过一个桥梁,向对方喊话,然后各干各的,因为跨语言的,不可能相互调用。只能通过一种协议。就是我们的module
rn 跳转 native
基于android原生和rn通信讲解:调用原生暴露的方法进行跨端控制
无回调:startActivity
Android端
/**
* js跳转nativeActivity
*/
@ReactMethod
public void startActivityByString(String activityName) {
try {
Activity currentActivity = getCurrentActivity();
if (null != currentActivity) {
Class aimActivity = Class.forName(activityName);
Intent intent = new Intent(currentActivity, aimActivity);
currentActivity.startActivity(intent);
}
} catch (Exception e) {
throw new JSApplicationIllegalArgumentException(
"Could not open the activity : " + e.getMessage());
}
}
JS端
startNative(activityName) {
CustomNativeModule.startActivityByString(text)
}
有回调:startActivityForResult
Android端
Module
@ReactMethod
public void startActivityForResult(String activityName, int requestCode, final Callback successCallback, Callback erroCallback) {
try {
Activity currentActivity = getCurrentActivity();
if (null != currentActivity) {
Class aimActivity = Class.forName(activityName);
Intent intent = new Intent(currentActivity, aimActivity);
currentActivity.startActivityForResult(intent, requestCode);
new Thread() {
@Override
public void run() {
String result = null;
try {
result = BaseApplication.myBlockingQueue.take();
successCallback.invoke(result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
} catch (Exception e) {
erroCallback.invoke(e.getMessage());
throw new JSApplicationIllegalArgumentException(
"Could not open the activity : " + e.getMessage());
}
}
原生Activity
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == 100) {
String result = data.getStringExtra("result");
if (TextUtils.isEmpty(result)) {
BaseApplication.myBlockingQueue.add(result);
} else {
BaseApplication.myBlockingQueue.add("no data");
}
} else {
BaseApplication.myBlockingQueue.add("null");
}
}
初始化ArrayBlockingQueue
public static ArrayBlockingQueue<String> myBlockingQueue = new ArrayBlockingQueue<String>(1);
JS端:
startNativeForResult(text) {
CustomNativeModule.startActivityForResult(text, 100,
(succ)=>{ToastAndroid.show(succ, ToastAndroid.SHORT)},
(err)=>{console.warn(err)})
}