明天和意外你永遠(yuǎn)都不知道哪一個先來,編程界亦是如此。例如某個已經(jīng)有原生代碼開發(fā)模塊的項目要求用RN擴(kuò)張某些功能儿奶;又例如沾凄,RN中未封裝到的組件非得求助于原生代碼梗醇。所以RN與原生代碼通訊對于混合編程是至關(guān)重要的。為了實現(xiàn)兩者之間的通信撒蟀,facebook也提供了三種通信方式叙谨。
- RCTDeviceEventEmitter消息機(jī)制:由Native主導(dǎo)控制,可以任意時刻傳遞
- Callback回調(diào)方式:由js代碼調(diào)用保屯,原生代碼返回手负。
- Promise機(jī)制方式:由js調(diào)用,只是每次使用都需要調(diào)用配椭。
一虫溜、RN調(diào)用安卓代碼(簡單)
RN調(diào)用安卓原生的代碼,大致分為如下幾步股缸。
1衡楞、用Android Studio打開一個已經(jīng)創(chuàng)建好的RN項目,選擇android/build.gradle文件敦姻。
2瘾境、創(chuàng)建一個類繼承ReactContextBaseJavaModule,在該類中我們應(yīng)該要暴露出一些讓RN調(diào)用的方法,封裝成一個原生模塊镰惦。
- 新建一個類MyReactPackage繼承ReactContextBaseJavaModule
public class MyNativeModule extends ReactContextBaseJavaModule{
}
- 實現(xiàn)getName方法迷守。該方法用于返回RN代碼需要尋找的類的名稱。(alt+enter快捷鍵可快速實現(xiàn)父類方法)
@Override
public String getName() {
// 一定要有名字 RN代碼要通過名字來調(diào)用該類的方法
return "ToastModule";
}
- 實現(xiàn)類的構(gòu)造方法旺入。在這里將傳入的上下文賦值給類內(nèi)部私有的上下文
// 創(chuàng)建一個上下文兑凿,放到構(gòu)造函數(shù)中凯力,得到reactContext
private ReactApplicationContext mContext;
public MyNativeModule(ReactApplicationContext reactContext){
super(reactContext);
mContext = reactContext;
}
- 創(chuàng)建暴露給RN調(diào)用的方法。但是要用注釋符號@ReactMethod修飾礼华。
//方法不能返回值 因為被調(diào)用的原生代碼是異步的 原生代碼執(zhí)行結(jié)束之后只能通過回調(diào)函數(shù)或者發(fā)送消息給RN
@ReactMethod
public void rnCallNative(String msg){
//這個方法是說彈出一個彈窗到界面Toast.makeText(mContext,msg,Toast.LENGTH_LONG).show();
}
3咐鹤、在原生代碼中創(chuàng)建一個類實現(xiàn)接口ReactPackage包管理器,并且把第二步已經(jīng)創(chuàng)建好的類加入到原生模塊列表里圣絮。
實現(xiàn)接口ReactPackage的方法祈惶,當(dāng)然我們現(xiàn)在只需要創(chuàng)建模塊,所以在其他的實現(xiàn)先直接返回空集合即可扮匠。而在createNativeModules方法中捧请,要先聲明一個裝有原生模塊的列表。之后將原生模塊加入到列表里棒搜。
public class MyReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new MyNativeModule(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
4疹蛉、將創(chuàng)建好的包管理器添加到ReactPackage列表里,也就是MainApplication代碼中
在類里找到方法getPackages方法,將包管理器添加進(jìn)去帮非。
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new MyReactPackage()
);
}
5.在RN代碼中用NativeModules組件去調(diào)用原生模塊
- 導(dǎo)入組件
import {
AppRegistry,
StyleSheet,
Text,
View,
NativeModules,
} from 'react-native';
- 設(shè)置方法調(diào)用原生代碼
call_button(){
NativeModules.ToastModule.rnCallNative('RN與安卓開發(fā)');
}
- 布置UI
在render方法里面設(shè)置當(dāng)用戶點擊文字時氧吐,調(diào)用自定義的方法call_button。并且以這種形式創(chuàng)建的方法需要進(jìn)行綁定
render() {
return(
<View style={styles.container}>
<Text onPress={this.call_button.bind(this)}>測試原生通訊</Text>
</View>
);
}
- 處理好樣式
const styles = StyleSheet.create({
container: {
flex:1,
backgroundColor:'deeppink',
flexDirection:'row',
justifyContent:'center',
alignItems:'center' },
});
至此末盔,基本的RN調(diào)用安卓原生代碼的方式就得以實現(xiàn)筑舅。再從RN的角度來回看整個過程。RN調(diào)用原生的方法陨舱,此時安卓的application就會啟動翠拣,完成之后它會去找Package的列表,進(jìn)而找到自己創(chuàng)建的列表游盲。而在組件的列表里面有一個原生模塊列表误墓,到自己的模塊列表里面調(diào)用模塊里的方法就完成了調(diào)用。
效果圖如下:
二益缎、RN用消息機(jī)制方式與安卓原生代碼切換
實現(xiàn)效果:在原生代碼中添加一個按鈕谜慌,當(dāng)用戶從RN界面調(diào)用原生代碼就會進(jìn)入到原生代碼開發(fā)的界面中,而點擊原生代碼中的按鈕就會返回到RN界面莺奔。
接上一節(jié)的代碼欣范。
1、在與MainApplication同級的目錄下創(chuàng)建一個Activity令哟。Activity是android系統(tǒng)最小的調(diào)度單位恼琼。
創(chuàng)建名稱為MyActivity的空活動。它會幫助我們生成一個自動布局文件做布局的工作屏富。(此時若遇到錯誤晴竞,可以選擇build->clean)
2、command+enter點擊進(jìn)入activity_my中狠半,此時會打開布局文件噩死。將左下角的Design切換成Text文件颤难。在該文件中,為原生界面創(chuàng)建一個按鈕并且布局甜滨。
xmlns:android表示設(shè)置xmlns的命名空間乐严,沒有這句話就無法設(shè)置屬性的約束。
在該界面上創(chuàng)建一個按鈕衣摩,為按鈕綁定一個方法onBack。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_width = "match_parent"
android:layout_height="match_parent"
xmlns:android = "http://schemas.android.com/apk/res/android"
>
<Button
android:text="goBack"
android:onClick="onBack"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</RelativeLayout>
3捂敌、回到新創(chuàng)建的MyActivity代碼艾扮,實現(xiàn)onBack方法。
//點擊按鈕占婉,直接完成
public void onBack(View v){
finish();
}
4泡嘴、到MyNativeModule原生模塊中去實現(xiàn)Activity
上文中已經(jīng)說過,Activity是android系統(tǒng)的最小調(diào)度單位逆济,而Intent則是安卓的進(jìn)程之間酌予、activity之間、線程之間交換數(shù)據(jù)的載體奖慌。
//方法不能返回值 因為被調(diào)用的原生代碼是異步的 原生代碼執(zhí)行結(jié)束之后只能通過回調(diào)函數(shù)或者發(fā)送消息給RN
@ReactMethod
public void rnCallNative(String msg){
Toast.makeText(mContext,msg,Toast.LENGTH_LONG).show();
Intent intent = new Intent(mContext,MyActivity.class); //創(chuàng)建一個意圖抛虫,意圖是android進(jìn)程之間、線程之間简僧、交換數(shù)據(jù)的載體
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //一定要加上這句
mContext.startActivity(intent);
}
效果圖如下:
三建椰、RN用Promise機(jī)制與安卓原生代碼通信
使用Promise機(jī)制也是RN與原生通信的一種方式。在原生代碼的MyNativeModule文件中創(chuàng)建橋接方法岛马。當(dāng)橋接的原生方法的最后一個參數(shù)是一個Promise對象棉姐,那么該方法會返回一個JS的Promise對象給與之對應(yīng)的js方法。
與上文類似啦逆,需要暴露給RN的方法不能有返回值伞矩,并且要以注釋@ReactMethod標(biāo)識。
@ReactMethod
public void rnCallNative_promise(String msg,Promise promise){
Toast.makeText(mContext,msg,Toast.LENGTH_SHORT).show();
//得到組件名稱
String componentName = getCurrentActivity().getComponentName().toString();
promise.resolve(componentName);
}
原生代碼已經(jīng)實現(xiàn)夏志。接下來要實現(xiàn)的就是RN的代碼乃坤。在RN中創(chuàng)建一個方法,這個方法內(nèi)部使用NativeModules組件來調(diào)用原生模塊提供的名稱盲镶,進(jìn)而找到要調(diào)用的原生方法侥袜。原生方法最后一個參數(shù)是一個promise,所以在js中用.then的方法實現(xiàn)即可溉贿。
callAndroid_promise(){
NativeModules.ToastModule.rnCallNative_promise('promise調(diào)用原生').then(
(msg) => {
console.log('promise收到消息:'+msg);
}
).catch(
(err)=>{
console.log(err);
}
)
}
到渲染方法中枫吧,調(diào)用方法
<Text style={styles.welcome} onPress={this.callAndroid_promise.bind(this)}>Promise通信</Text>
給Text組件設(shè)置樣式
welcome: {
fontSize: 16,
textAlign: 'left',
margin: 10
}
結(jié)果圖如下:
用Debug進(jìn)行調(diào)試,得到結(jié)果如下:
四宇色、RN用callback回調(diào)方式與安卓原生代碼通信
按照上文中提到的方式九杂,在原生模塊中暴露一個橋接方法給RN調(diào)用颁湖。
參數(shù)傳入一個成功的回調(diào)和一個失敗的回調(diào)。
@ReactMethod
public void measureLayout(Callback errorCallback,Callback successCallback){
try {
successCallback.invoke(100,100,200,200); //調(diào)用回調(diào)函數(shù)例隆,返回結(jié)果
}catch (IllegalViewOperationException e){
errorCallback.invoke(e.getMessage());
}
}
在js中實現(xiàn)回調(diào)方法甥捺。同樣是通過NativeModules組件尋找到橋接名稱ToastModule,進(jìn)而找到想要調(diào)用的方法镀层。拿到返回的參數(shù)镰禾,做功能處理。
callAndroid_callback(){
NativeModules.ToastModule.measureLayout(
(msg)=>{
console.log(msg);
},
(x,y,width,height)=>{
console.log('x坐標(biāo):'+x+'y坐標(biāo):'+y+'高:'+height+'寬'+width);
}
)
}
在Debug調(diào)試下唱逢,點擊callback通信文字吴侦,可以看到如下結(jié)果:
在使用回調(diào)函數(shù)時會呈現(xiàn)出某些缺點,比如說每次調(diào)用只應(yīng)當(dāng)調(diào)用一次坞古,多次調(diào)用可能會出現(xiàn)意想不到的結(jié)果备韧,并且用這種方法安卓原生代碼是無法主動發(fā)送信息給RN側(cè)的。而消息機(jī)制的方式就可以進(jìn)行消息的互相傳遞痪枫。