最近偷懒了一段时间(折腾了下小程序、跨平台开发框架Weex / React Native / Flutter),到现在接入RN混合开发也已经七八个月的时间了,今天针对RN在Android混编项目做个整体的复盘反思。以下内容是之前总结,本次不再重复记录。
本次记录不作为使用指南记录,因此不再描述项目初始化等相关方面,需要的可以参考上面链接,这里主要站在我们业务选择和使用RN的角度来整体上分析跨平台RN开发的优缺点,及对React Native与 Android 混编项目简单做下复盘反思。
一、跨平台RN开发的优缺点:
-
优点:
- 提高研发和测试效率
- 各终端逻辑保持统一
之前Android、iOS、M同一需求需三端开发人员各自去实现,以各自对需求的理解、各自的实现方式去码不同的代码,各自的逻辑和展示也可能就各不相同,很难保证功能一致性,自然测试人员需要针对三端各自测试,逻辑bug产生相对比较零散,各端各不相同,验证也相对比较耗时耗力,采用RN同一功能,同一套代码逻辑方式,测试起来发现逻辑bug相对统一,一些平台差异性兼容性bug除外,大大提高测试效率,同时各终端业务逻辑也相对统一,提高了研发效率。
-
缺点:
因平台差异性可能会存在一些兼容性问题,如果想在前期就能抹平各端差异性问题及解决各平台兼容型问题,就需要RN开发人员熟悉Android、IOS、Web三端开发技术,对技术广度稍微有点高。
二、React Native在Android混编项目中的页面跳转和方法调用
Android与RN通信.png大致通过上面这张简图来描述下:
-
页面跳转(RN与Android原生)
调查背景:在设计与RN交互时,并不是仅站在Android一端的角度去设计,而是考虑通用型,尽量用RN本身特性去抹平差异性,避免RN在代码层面进行差异化处理,比如说页面跳转处理:- Android的页面跳转是通过Intent跳转
- RN是通过路由(M版也通过路由跳转)
如果保持各自特性,则两者直接页面互相跳转就需要原生借助JS暴露接口给RN来实现了,这样的话RN就需要根据终端环境进行差异化处理,为了避免RN在代码层面进行差异化处理,尽量寻找统一性方案确保整个项目的统一,通过调查发现RN提供的Linking方式进行跳转,那么就有两个问题,RN怎么拿?原生怎么传?然后通过源码发现RN分别针对Android和IOS进行了封装映射,我们只需要把数据传送到对应的位置即可,
const LinkingManager = Platform.OS === 'android' ? NativeModules.IntentAndroid : NativeModules.LinkingManager;
在Android中对应的是IntentAndroid,查看对应的源码:
/** * Return the URL the activity was started with * * @param promise a promise which is resolved with the initial URL */ @ReactMethod public void getInitialURL(Promise promise) { try { Activity currentActivity = getCurrentActivity(); String initialURL = null; if (currentActivity != null) { Intent intent = currentActivity.getIntent(); String action = intent.getAction(); Uri uri = intent.getData(); if (Intent.ACTION_VIEW.equals(action) && uri != null) { initialURL = uri.toString(); } } promise.resolve(initialURL); } catch (Exception e) { promise.reject(new JSApplicationIllegalArgumentException( "Could not get the initial URL : " + e.getMessage())); } }
通过上面源码也就了解了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层调用Native层进行界面跳转时,需要设置FLAG_ACTIVITY_NEW_TASK标志。
例如:下面是RCTDeviceEventEmitter事件的简单事例,稍后封装下更方便与原生的通信交互。
public class EventEmitter {
private static final String TAG = "EventEmitter";
// 在ReactPackage中的createNativeModules()初始化,RNEventEmitter.setReactContext(reactContext);
private static ReactApplicationContext mReactContext;
public static void setReactContext(ReactApplicationContext mReactContext) {
RNEventEmitter.mReactContext = mReactContext;
}
/**
* 显示RN中loading
* @param data show:显示,false:隐藏
*/
public static void showLoading(boolean show) {
nativeCallRn("showloading", show);
}
public static void nativeCallRn(String eventName, Object msg) {
if (mReactContext == null) {
Log.e(TAG, "ReactContext is null");
return;
}
mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName,msg);
}
}
RN中接收原生消息:
/**
* 接收原生调用
*/
componentDidMount() {
DeviceEventEmitter.addListener('showLoading',(msg)=>{
ToastAndroid.show("发送成功"+msg, ToastAndroid.SHORT);
})
//通过DeviceEventEmitter注册监听,类似于Android中的监听事件。第一个参数标识名称,要与Module中emit的Event Name相同。第二个参数即为处理回调时间。
}
三、Andorid 与 RN 传参数据类型映射关系:
Android | React Native | 备注 |
---|---|---|
Boolean | Bool | |
Integer | Number | |
Double | Number | |
Float | Number | |
String | String | |
Callback | function | |
ReadableMap / WritableMap | Object | RN中的对象 |
ReadableArray / WritableArray | Array |
观察着8种参数类型发现,其中有ReadableMap 和 ReadableArray类型,对应JavaScript的Object和Array。而在Java原生中,可以发现facebook定义了ReadableArray和ReadableMap接口,一层一层找一下,找到了WritableArray和WritableMap接口,以及实现了他们的WritableNativeArray和WritableNativeMap,我尝试利用WritableNativeArray push了几个参数,成功的传递到了参数:
//Android
@ReactMethod
public void show(Callback callback) {
WritableArray writableArray = new WritableNativeArray();
writableArray.pushString("one");
writableArray.pushString("two");
writableArray.pushString("three");
callback.invoke(writableArray);
}
//React Native
MyTest.show((result)=> {
ToastAndroid.show("结果是:" + result[2], ToastAndroid.SHORT);
}
);
//Android
@ReactMethod
public void show(Callback callback) {
WritableMap writableMap = new WritableNativeMap();
writableMap.putString("1", "first");
writableMap.putString("2", "second");
writableMap.putString("3", "third");
callback.invoke(writableMap);
}
//React Native
MyTest.show((result)=> {
ToastAndroid.show("结果是:" + result["2"], ToastAndroid.SHORT);
}
);
备注: 如何更好地将RN与原生进行参数传递呢?虽说上面的映射关系可以让我们去准确的传递参数,但是比如说在Android原生中ReadableMap / WritableMap
对应着RN中的 Object
, 但是我们原生里面并不会全局使用ReadableMap / WritableMap
来替换现有的Map或者HashMap吧,为尽量避免RN的侵入型,我们需要Native Module层进行抽象封装处理,将RN中的数据类型与Android原生的数据类型进行互转,稍后会整理相关转化工具类,方便Android与原生快速交互。
网友评论