【简述RN集成到Android原生项目】
【Android项目集成RN系列:RN使用Android原生控件或自定义组件】
【React Native Linking与 Android原生页面路由跳转问题】
React Native在Android混编项目中的页面跳转和方法调用大致可以通过上面这张简图来描述下:
一、页面跳转(RN与Android原生)
-
RN页面跳转原生
- 方式一:
通过下面即将讲述的方法调用实现,通过在RN中调用 NativeModule中暴露的方法,来实现跳转原生的指定页面。(此处不再细述)
- 方式二:
通过Scheme路由形式跳转,RN提供的Linking,可根据路由跳转到指定页面,可参考【React Native Linking与 Android原生页面路由跳转问题】。
- 方式一:
-
原生跳转RN页面
通常我们加载RN页面是根据RN bundle文件的入口js文件(默认index.android.js,可自定义指定入口js文件)中暴露的ModuleName去加载入口页面,就好比我们Android中设置的启动页面一样,我们再在启动页面根据相关逻辑跳转相关页面。那么这里可能就涉及到了Android如何传递路由或者参数给RN?以及RN如何获取原生提供的路由与参数?通过查看Linking源码发现RN分别针对Android和IOS进行了封装映射,我们只需要把数据传送到对应的位置即可,
在Android中对应的是IntentAndroid,查看对应的源码:const LinkingManager = Platform.OS === 'android' ? NativeModules.IntentAndroid : NativeModules.LinkingManager;
通过上面源码也就了解了Android与RN如何根据Linking进行页面跳转,我先按照这种形式在Android上进行测试发现可行,当然也有bug,就是拿的时候可能为空,调查发现RN与原生页面装载之前就调用这个方法导致拿不到上下文,只要创建rootview就会执行rn的生命周期,而此时RN与原生还没有绑定,后来发现RN是能监听到原生页面的活动状态,此时再去获取路由即可。详细内容可参考React Native Linking与 Android原生页面路由跳转实现。/** * 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())); } }
二、 方法调用
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层调用NativeModule层进行界面跳转时,需要设置FLAG_ACTIVITY_NEW_TASK标志。
例如下分别以原生调用RN
和RN调用原生
为例简单描述下:
-
原生调用RN
下面是RCTDeviceEventEmitter事件的简单事例,稍后封装下更方便与原生的通信交互。
RN中接收原生消息: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) { sendEventToRn("showloading", show); } public static void sendEventToRn(String eventName, Object msg) { if (mReactContext == null) { Log.e(TAG, "ReactContext is null"); return; } mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(eventName,msg); } }
/** * 接收原生调用 */ componentDidMount() { DeviceEventEmitter.addListener('showLoading',(msg)=>{ ToastAndroid.show("发送成功"+msg, ToastAndroid.SHORT); }) //通过DeviceEventEmitter注册监听,类似于Android中的监听事件。第一个参数标识名称,要与Module中emit的Event Name相同。第二个参数即为处理回调时间。 }
- 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'