RN学习笔记之和本地交互
学习了RN的基本内容,虽然可以开发个全RN的项目,但基本上没有完全来使用RN构建一个项目的,都要使用到RN和本地的交互。
以Android为例(IOS原理一样),RN和本地的交互主要分为以下几种:
1、本地展示RN的component
2、RN调用本地的代码方法
3、RN传递参数给本地
4、本地传递参数给RN
5、RN展示本地的View
掌握了以上几种,基本上就可以开始开发混合了RN的项目了。本文主要记录一下以上几种方式的相关内容.
本地展示RN component
如果想本地展示RN的component,我们都知道Android本地是以activity或者fragment为载体,加载view来展示的。所以基本上可以确定,RN的Component也会作为一个view依赖于fragment或者activity来展示。
RN中作为展示的view对象叫做ReactRootView
,根据其源码可以看出,
public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
public class SizeMonitoringFrameLayout extends FrameLayout
ReactRootView
根本上是一个FrameLayout
,更详细的源码不做过多分析。而ReactRootView
的用法如下:
ReactRootView mReactRootView = new ReactRootView(context);
mReactRootView.startReactApplication(ReactInstanceManager rm, String componentName, Bundle b);
由此方法可以看出,我们想让ReactRootView
生效,需要一个ReactInstanceManager
、componentName
、Bundle
,三个参数分别是
1、ReactInstanceManager 管理JS context的管理器
2、需要加载Component名字
3、需要传递的参数,Bundle类型
而ReactInstanceManager是根据ReactNativeHost
对象获取的(其底层也是通过ReactInstanceManager.builder
来实现的,我们也可以自己去实现这个,但是一般会用ReactNativeHost
,这样便于管理)ReactNativeHost
对象是通过new ReactNativeHost(Application application)
获取的。所以有上可以得出,我们需要几步来实现本地调用RN的component。
Android中需要做的:
1、在项目的application中实例化ReactNativeHost
2、通过ReactNativeHost获取ReactInstanceManager
3、在需要展示RN界面的地方实例化ReactRootView并加载对应注册的的component
RN中要做的
1、将对应的RN component进行注册,注册的名字和RN中的加载的component对应。
所以我们查阅大部分blog写出的demo都可以看到会创建一个工程,自定义一个application实现ReactApplication
接口,里面实现一个public ReactNativeHost getReactNativeHost()
的方法,然后在activity中集成ReactActivity,实现protected String getMainComponentName()
的方法。或者我们自己去写一个类,提供ReactInstanceManager对象,这样就不用实现ReactActivity方法了,就有了更大的灵活性。
具体如下:
/**
* RN pacakge 管理类
* Created by AnonyPer on 2017/5/8.
*/
public class ReactNativePackageManager {
private static final ReactNativePackageManager reactNativePackageManager = new ReactNativePackageManager();
private ReactNativeHost mReactNativeHost;
/**
* ReactContext上下文,后续传递参数给RN的时候用到
*
*/
private ReactContext mReactContext;
/**
* 单例模式 实例化时实现ReactNativeHost
*/
private ReactNativePackageManager() {
//实现ReactNativeHost 并将application传递过来
mReactNativeHost = new ReactNativeHost(AppApplication.getInstance()) {
/**判断是否是debug模式,会自动开启摇一摇调试
* @return
*/
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
/**
* 注册本地的ReactPackage,这样才能在RN中调用
* @return
*/
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(), new NativeReactPackage()
);
}
/**获取JS bundle文件的地址,这里会在以后热更新部署的时候用到,程序加载JS的时候都是通过对应的bundle文件来加载的
* @return
*/
@Nullable
@Override
protected String getJSBundleFile() {
return null;
}
};
registerReactInstanceEventListener();
}
public static ReactNativePackageManager getInstance() {
return reactNativePackageManager;
}
public ReactContext getReactContext() {
return mReactContext;
}
/**
* 提供接口,暴露ReactInstanceManager对象
* @return
*/
public ReactInstanceManager getReactInstanceManager() {
return mReactNativeHost.getReactInstanceManager();
}
private void registerReactInstanceEventListener() {
mReactNativeHost.getReactInstanceManager().addReactInstanceEventListener(mReactInstanceEventListener);
}
private void unRegisterReactInstanceEventListener() {
mReactNativeHost.getReactInstanceManager().removeReactInstanceEventListener(mReactInstanceEventListener);
}
/**
* 注册ReactInstanceEventListener,在ReactContextInitialized后获取ReactContext对象
*/
private final ReactInstanceManager.ReactInstanceEventListener mReactInstanceEventListener =
new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext context) {
mReactContext = context;
}
};
}
Android中调用
mReactRootView = new ReactRootView(context);
mReactInstanceManager = ReactNativePackageManager.getInstance().getReactInstanceManager();
mReactRootView.startReactApplication(mReactInstanceManager, getComponentName(), translateModel != null ? translateModel.getBundle() : null);
mReactRootView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
layout.addView(mReactRootView);
需要注意的是,RN中Component要用到相关的点击事件,需要在依赖的activity的onResume
中将当前的activity对象传递给ReactInstanceManager
,代码如下:
原方法:onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl)
ReactNativePackageManager.getInstance().getReactInstanceManager().onHostResume(this.getActivity(), null);
这样在处理当前activity事件的时候才可以有效的将相关事件传递给RN,不然RN接收不到点击长按等事件。
代码如下:
/**
* RN本地的通用Fragment,集成项目中原本通用BaseFragment,定义了RN需要的方法
* Created by AnonyPer on 2017/5/4.
*/
public class ReactBaseFragment extends BaseFragment {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
private String componentName = "XXXXXXXXXX";
/**
* 在fragment基类中定义了一个getReactRootView方法,获取ReactRootView,便于加载到对应的view中
* @return
*/
@Override
public ReactRootView getReactRootView() {
return mReactRootView;
}
@Override
public void setArguments(Bundle bundle) {
super.setArguments(bundle);
componentName = bundle.getString(TranslateModel.KEY_COMPONENT);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
//以下的内容可以写在不同的方法中,只要在被调用显示前初始化好即可
mReactRootView = new ReactRootView(context);
mReactInstanceManager = ReactNativePackageManager.getInstance().getReactInstanceManager();
mReactRootView.startReactApplication(mReactInstanceManager, getComponentName(), translateModel != null ? translateModel.getBundle() : null);
}
@Override
public void onResume() {
super.onResume();
ReactNativePackageManager.getInstance().getReactInstanceManager().onHostResume(this.getActivity(), null);
}
@Override
public void onPause() {
super.onPause();
ReactNativePackageManager.getInstance().getReactInstanceManager().onHostPause(this.getActivity());
}
/**
* 获取对应的Component name
* @return
*/
protected String getComponentName() {
return componentName;
}
@Override
public void onDestroy() {
super.onDestroy();
ReactNativePackageManager.getInstance().getReactInstanceManager().onHostDestroy(this.getActivity());
}
/**
* 发送数据给RN
* @param eventName 事件对应的name,RN中会根据该名字找到对应的方法
* @param params 需要传递的参数
*/
protected void sendEvent(String eventName,
@Nullable WritableMap params) {
if (ReactNativePackageManager.getInstance().getReactContext() != null) {
ReactNativePackageManager.getInstance().getReactContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
}
部分BaseFragment代码如下:
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
...
mContentView = getReactRootView();
if (mContentView != null) {
mContentView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
//layout是自定义的一个view,用来包容需要显示的view,可以是本地的也可以是RN的
layout.addView(mContentView);
}
...
}
//获取ReactRootView方法,默认为null,在用到RN的fragment中自己去重写。
public ReactRootView getReactRootView() {
return null;
}
RN中的关键代码如下:
registerComponent('com.XXX.XXX', () => TestComponent);
至此,Android本地已完美显示RN中的Component了。
RN调用本地的代码方法/RN传递参数给本地
RN调用本地的代码和方法以及RN传递参数给本地是一体的,这一部分挺简单,就不详细说了,具体代码如下:
Android本地代码
public class ToastModule extends ReactContextBaseJavaModule {
private static final String REACT_CLASS = "JFToast";
public ToastModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return REACT_CLASS;
}
/**
* 调用本地的方法,并传递一个字符串参数
* @param tip
*/
@ReactMethod
public void showDelayInfoTip(String tip) {
//用全局toast 因为有一些手机toast是和界面相关的,如果弹toast时界面消失,toast也会消失
if(!TextUtils.isEmpty(tip)){
Toast.makeText(AppApplication.getInstance(),tip,Toast.LENGTH_LONG).show();
}
}
/**调用本地方法,并传递一个对象
* @param readableMap 参数对象,以键值对方式存在
*/
@ReactMethod
public void executeCallback(ReadableMap readableMap) {
//具体代码
readableMap.getString("XX");
readableMap.getArray("XX");
readableMap.getBoolean("XX");
readableMap.getDouble("XX");
readableMap.getMap("XX");
}
/**
* RN调用本地执行登录的方法
*
* @param promise Promise是ES6中增加的对于异步编程和回调更加友好的API 有resolve和reject两个方法,resolve用来处理正确处理结果的情况,reject用来处理异常的情况
* 参考博文:https://segmentfault.com/a/1190000004508328
*/
@ReactMethod
public void executeLogin(final Promise promise) {
//执行登录操作,并将登录后的信息返回给RN
//jump to login
promise.resolve(UserInfoConfig.getInstance().getWritableMap());
}
/**
* RN调用本地判断是否登录的方法,如果已登录 直接返回,如果没有登录 直接跳转登录,然后登陆成功之后再次执行回调给RN
*
* @param callback RN传递过来的回调 Callback 的数据类型对应了react-native中的 function
* 在JavaScript调用Java之后,处理结果会以Callback的形式回到JavaScript中,在JavaScript中再对相应的结果进行处理。
* 参考博文:https://segmentfault.com/a/1190000004508328
*/
@ReactMethod
public void onLineExecute(final Callback callback) {
callback.invoke(null, UserInfoConfig.getInstance().getWritableMap());
}
}
然后将相关的module添加到ReactPackage中并通知RN
public class NativeReactPackage implements ReactPackage {
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> managers = new ArrayList<>();
managers.add(new RefreshControlViewManager());
return managers;
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ToastModule(reactContext));
return modules;
}
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(), new NativeReactPackage()
);
}
RN代码
class Home extends Component<any, any>{
render() {
return (
<TouchableHighlight onPress={()=>{
ToastModule.showDelayInfoTip("测试的内容");
}}>
<Text >Component</Text>
</TouchableHighlight>
);
}
}
本地传递参数给RN
本地传递给RN参数分为两种,其实上面的代码中都包含了,一种是在RN调用本地方法时回调给RN,一种是主动地发送消息给RN,代码如下:
protected void sendEvent(String eventName,
@Nullable WritableMap params) {
if (ReactNativePackageManager.getInstance().getReactContext() != null) {
ReactNativePackageManager.getInstance().getReactContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
RN展示本地的View
RN调用本地的view的情况是因为有一些效果还是还是使用本地的方法比较方便,所以本地实现RN来调用,本地重要代码如下:
public class RefreshControlViewManager extends ViewGroupManager<PullRefreshView> {
private static final String REACT_CLASS = "ARefreshView";
@Override
public String getName() {
return REACT_CLASS;
}
/**
* 返回一个本地的view对象
* @param context
* @return
*/
@Override
public PullRefreshView createViewInstance(ThemedReactContext context) {
return new PullRefreshView(context);
}
}```
上面的NativeReactPackage中已经将view告诉RN了:
``` @Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> managers = new ArrayList<>();
managers.add(new RefreshControlViewManager());
return managers;
}```
RN中调用如下:
import { PropTypes } from 'react'
import {
requireNativeComponent,
View,
Platform
} from 'react-native';
var ARefreshView = undefined;
if (Platform.OS == 'android') {
let refresh = {
name: 'ARefreshView',
propTypes: {
...View.propTypes // 包含默认的View的属性
},
};
ARefreshView = requireNativeComponent('ARefreshView', refresh);
}
export default ARefreshView;
在Component中使用的时候如下:
return (<ARefreshView style={{ flex: 1 }}>{list}</ARefreshView>)
到这里,RN和本地交互用到的几种情况都有说完了,热更新部分待后续再议。