最近负责一个项目。里面语言是用RN开发的,现在想把RN逐步替换为原生进行开发,整理一下关于RN与原生之间相互调用的方法。
简述RN集成到Android原生项目:
https://www.jianshu.com/p/f546ad231382
一、页面跳转(RN与Android原生)
1、RN页面跳转原生
方式一:
通过下面即将讲述的方法调用实现,通过在RN中调用 NativeModule中暴露的方法,来实现跳转原生的指定页面。(此处不再细述)
方式二:
通过Scheme路由形式跳转,RN提供的Linking,可根据路由跳转到指定页面,可参考【React Native Linking与 Android原生页面路由跳转问题】。
2、原生跳转RN页面
通常我们加载RN页面是根据RN bundle文件的入口js文件(默认index.android.js,可自定义指定入口js文件)中暴露的ModuleName去加载入口页面,就好比我们Android中设置的启动页面一样,我们再在启动页面根据相关逻辑跳转相关页面。那么这里可能就涉及到了Android如何传递路由或者参数给RN?以及RN如何获取原生提供的路由与参数?通过查看Linking源码发现RN分别针对Android和IOS进行了封装映射,我们只需要把数据传送到对应的位置即可,
在Android中对应的是IntentAndroid,查看对应的源码:
通过上面源码也就了解了Android与RN如何根据Linking进行页面跳转,我先按照这种形式在Android上进行测试发现可行,当然也有bug,就是拿的时候可能为空,调查发现RN与原生页面装载之前就调用这个方法导致拿不到上下文,只要创建rootview就会执行rn的生命周期,而此时RN与原生还没有绑定,后来发现RN是能监听到原生页面的活动状态,此时再去获取路由即可。详细内容可参考React Native Linking与 Android原生页面路由跳转实现。
二、方法调用
RN通信原理简单地讲就是,一方native(java)将其部分方法注册成一个映射表,另一方(js)再在这个映射表中查找并调用相应的方法,而Bridge担当两者间桥接的角色。
其实方法调用大致分为2种情况:
· Android主动向JS端传递事件、数据
· JS端主动向Android询问获取事件、数据
RN调用Android需要module名和方法名相同,而Android调用RN只需要方法名相同。
(1)RCTDeviceEventEmitter 事件方式
优点:可任意时刻传递,Native主导控制。
(2)Callback 回调方式
优点:JS调用,Native返回。
缺点:CallBack为异步操作,返回时机不确定
(3)Promise
优点:JS调用,Native返回。
缺点:每次使用需要JS调用一次
(4)直传常量数据(原生向RN)
跨域传值,只能从原生端向RN端传递。RN端可通过 NativeModules.[module名].[参数名] 的方式获取。
例如下分别以 原生调用RN 和 RN调用原生 为例简单描述下:
1、原生调用RN
下面是RCTDeviceEventEmitter事件的简单事例,稍后封装下更方便与原生的通信交互。
RN中接收原生消息:
2、RN调用原生
创建MyNativeModule.java
package com.liujc.rnappdemo.hybride;
import android.app.Activity;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import java.util.HashMap;
import java.util.Map;
/**
* 类名称:MyNativeModule
* 创建者:Create by liujc
* 描述:原生预留给RN的调用方法
*/
public class MyNativeModule extends ReactContextBaseJavaModule {
public static final String REACT_NATIVE_CLASSNAME = "MyNativeModule";
private ReactApplicationContext mContext;
public static final String EVENT_NAME = "nativeCallRn";
public static final String TAG = "TAG";
public MyNativeModule(ReactApplicationContext reactContext) {
super(reactContext);
mContext = reactContext;
}
@Override
public String getName() {
//返回的这个名字是必须的,在rn代码中需要这个名字来调用该类的方法。
return REACT_NATIVE_CLASSNAME;
}
//函数不能有返回值,因为被调用的原生代码是异步的,原生代码执行结束之后只能通过回调函数或者发送信息给rn那边。
@ReactMethod
public void rnCallNative(String msg){
Toast.makeText(mContext,msg,Toast.LENGTH_SHORT).show();
}
@ReactMethod
public void startActivityRN(String name, String params) {
try {
Activity activity = getCurrentActivity();
if (activity != null) {
Class toClass = Class.forName(name);
Intent intent = new Intent(activity, toClass);
intent.putExtra("params", params);
activity.startActivity(intent);
}
} catch (Exception ex) {
throw new JSApplicationIllegalArgumentException("不能打开Activity " + ex.getMessage());
}
}
//后面该方法可以用Linking代替
@ReactMethod
public void getDataFromIntent(Callback success, Callback error) {
try {
Activity currentActivity = getCurrentActivity();
String result = currentActivity.getIntent().getStringExtra("result");//会有对应数据放入
if (!TextUtils.isEmpty(result)) {
success.invoke(result);
}
} catch (Exception ex) {
error.invoke(ex.getMessage());
}
}
/**
* Native调用RN
* @param msg
*/
public void nativeCallRn(String msg) {
mContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(EVENT_NAME,msg);
}
/**
* Callback 方式
* rn调用Native,并获取返回值
* @param msg
* @param callback
*/
@ReactMethod
public void rnCallNativeFromCallback(String msg, Callback callback) {
String result = "hello RN!Native正在处理你的callback请求";
// .回调RN,即将处理结果返回给RN
callback.invoke(result);
}
/**
* Promise
* @param msg
* @param promise
*/
@ReactMethod
public void rnCallNativeFromPromise(String msg, Promise promise) {
Log.e(TAG,"rnCallNativeFromPromise");
String result = "hello RN!Native正在处理你的promise请求" ;
promise.resolve(result);
}
/**
* 向RN传递常量
*/
@Nullable
@Override
public Map<String, Object> getConstants() {
Map<String,Object> params = new HashMap<>();
params.put("Constant","我是Native常量,传递给RN");
return params;
}
}
创建 MyReactPackage.java
/**
* 类名称:MyReactPackage
* 创建者:Create by liujc
* 描述:RN包管理器
*/
public class MyReactPackage implements ReactPackage {
public MyNativeModule myNativeModule;
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
myNativeModule = new MyNativeModule(reactContext);
List<NativeModule> modules = new ArrayList<>();
//将我们创建NativeModule添加进原生模块列表中
modules.add(myNativeModule);
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
//该处后期RN调用原生控件或自定义组件时可用到
return Collections.emptyList();
}
}
将我们创建的MyReactPackage添加到我们在AppApplication中创建的ReactNativeHost中。
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
//将我们创建的包管理器给添加进来
new MyReactPackage()
);
}
};
接下来我们在js文件中如何调用?
'use strict';
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
NativeModules,
View,
ToastAndroid,
DeviceEventEmitter
} from 'react-native';
let title = 'React Native界面';
class reactNative extends Component {
/**加载完成*/
componentWillMount() {
let result = NativeModules.MyNativeModule.Constant;
console.log('原生端返回的常量值为:' + result);
}
/**
* 原生调用RN
*/
componentDidMount() {
DeviceEventEmitter.addListener('nativeCallRn',(msg)=>{
title = "React Native界面,收到数据:" + msg;
ToastAndroid.show("发送成功", ToastAndroid.SHORT);
})
}
/**
* RN调用Native且通过Callback回调 通信方式
*/
callbackComm(msg) {
NativeModules.MyNativeModule.rnCallNativeFromCallback(msg,(result) => {
ToastAndroid.show("CallBack收到消息:" + result, ToastAndroid.SHORT);
})
}
/**
* RN调用Native且通过Promise回调 通信方式
*/
promiseComm(msg) {
NativeModules.MyNativeModule.rnCallNativeFromPromise(msg).then(
(result) =>{
ToastAndroid.show("Promise收到消息:" + result, ToastAndroid.SHORT)
}
).catch((error) =>{console.log(error)});
}
/**
* 调用原生代码
*/
call_button(){
NativeModules.MyNativeModule.rnCallNative('调用原生方法操作');
}
callNative(){
NativeModules.MyNativeModule.startActivityRN('com.liujc.rnappdemo.MyRNActivity','test');
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome} >
{title}
</Text>
<Text style={styles.welcome}
onPress={this.call_button.bind(this)}
>
React Native 调用原生方法操作!
</Text>
<Text style={styles.welcome}
//给此处的文字绑定一个事件,其中callNative为要调用的方法名。
onPress={this.callNative.bind(this)}>
跳转MyRNActivity!
</Text>
<Text style={styles.welcome} onPress={this.callbackComm.bind(this,'callback发送啦')}>
Callback通信方式
</Text>
<Text style={styles.welcome} onPress={this.promiseComm.bind(this,'promise发送啦')}>
Promise通信方式
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
AppRegistry.registerComponent('reactNative', () => reactNative);
三、 RN中加载Gif图片
需要添加facebook的两个图片加载库:(注意版本号尽量与你使用的RN版本内部使用的fresco版本保持一致)
implementation 'com.facebook.fresco:fresco:1.3.0'
implementation 'com.facebook.fresco:animated-gif:1.3.0'