RN與原生通訊(安卓篇)

明天和意外你永遠(yuǎn)都不知道哪一個(gè)先來,編程界亦是如此。例如某個(gè)已經(jīng)有原生代碼開發(fā)模塊的項(xiàng)目要求用RN擴(kuò)張某些功能;又例如,RN中未封裝到的組件非得求助于原生代碼。所以RN與原生代碼通訊對(duì)于混合編程是至關(guān)重要的。為了實(shí)現(xiàn)兩者之間的通信,facebook也提供了三種通信方式。

看內(nèi)容.jpg
  • RCTDeviceEventEmitter消息機(jī)制:由Native主導(dǎo)控制,可以任意時(shí)刻傳遞
  • Callback回調(diào)方式:由js代碼調(diào)用,原生代碼返回。
  • Promise機(jī)制方式:由js調(diào)用,只是每次使用都需要調(diào)用。

一、RN調(diào)用安卓代碼(簡(jiǎn)單)

RN調(diào)用安卓原生的代碼,大致分為如下幾步。
1、用Android Studio打開一個(gè)已經(jīng)創(chuàng)建好的RN項(xiàng)目,選擇android/build.gradle文件。

01.jpg

2、創(chuàng)建一個(gè)類繼承ReactContextBaseJavaModule,在該類中我們應(yīng)該要暴露出一些讓RN調(diào)用的方法,封裝成一個(gè)原生模塊。

  • 新建一個(gè)類MyReactPackage繼承ReactContextBaseJavaModule
02.jpg
public class MyNativeModule extends ReactContextBaseJavaModule{
}
  • 實(shí)現(xiàn)getName方法。該方法用于返回RN代碼需要尋找的類的名稱。(alt+enter快捷鍵可快速實(shí)現(xiàn)父類方法)
 @Override
    public String getName() {
        // 一定要有名字 RN代碼要通過名字來調(diào)用該類的方法
        return "ToastModule";
    }
  • 實(shí)現(xiàn)類的構(gòu)造方法。在這里將傳入的上下文賦值給類內(nèi)部私有的上下文
 // 創(chuàng)建一個(gè)上下文,放到構(gòu)造函數(shù)中,得到reactContext
    private ReactApplicationContext mContext;

    public MyNativeModule(ReactApplicationContext reactContext){

        super(reactContext);

        mContext = reactContext;
    }
  • 創(chuàng)建暴露給RN調(diào)用的方法。但是要用注釋符號(hào)@ReactMethod修飾。
 //方法不能返回值 因?yàn)楸徽{(diào)用的原生代碼是異步的 原生代碼執(zhí)行結(jié)束之后只能通過回調(diào)函數(shù)或者發(fā)送消息給RN
    @ReactMethod
    public void rnCallNative(String msg){
        //這個(gè)方法是說彈出一個(gè)彈窗到界面Toast.makeText(mContext,msg,Toast.LENGTH_LONG).show();
    }

3、在原生代碼中創(chuàng)建一個(gè)類實(shí)現(xiàn)接口ReactPackage包管理器,并且把第二步已經(jīng)創(chuàng)建好的類加入到原生模塊列表里。

實(shí)現(xiàn)接口ReactPackage的方法,當(dāng)然我們現(xiàn)在只需要?jiǎng)?chuàng)建模塊,所以在其他的實(shí)現(xiàn)先直接返回空集合即可。而在createNativeModules方法中,要先聲明一個(gè)裝有原生模塊的列表。之后將原生模塊加入到列表里。

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ǎn)擊文字時(shí),調(diào)用自定義的方法call_button。并且以這種形式創(chuàng)建的方法需要進(jìn)行綁定
render() {
        return(
            <View style={styles.container}>
                <Text  onPress={this.call_button.bind(this)}>測(cè)試原生通訊</Text>
            </View>
        );
    }
  • 處理好樣式
const styles = StyleSheet.create({
    container: {
        flex:1,
        backgroundColor:'deeppink',
        flexDirection:'row',
        justifyContent:'center',
        alignItems:'center'    },
});

至此,基本的RN調(diào)用安卓原生代碼的方式就得以實(shí)現(xiàn)。再?gòu)腞N的角度來回看整個(gè)過程。RN調(diào)用原生的方法,此時(shí)安卓的application就會(huì)啟動(dòng),完成之后它會(huì)去找Package的列表,進(jìn)而找到自己創(chuàng)建的列表。而在組件的列表里面有一個(gè)原生模塊列表,到自己的模塊列表里面調(diào)用模塊里的方法就完成了調(diào)用。

效果圖如下:

03.jpg

二、RN用消息機(jī)制方式與安卓原生代碼切換

實(shí)現(xiàn)效果:在原生代碼中添加一個(gè)按鈕,當(dāng)用戶從RN界面調(diào)用原生代碼就會(huì)進(jìn)入到原生代碼開發(fā)的界面中,而點(diǎn)擊原生代碼中的按鈕就會(huì)返回到RN界面。


樹自行車分割線

接上一節(jié)的代碼。
1、在與MainApplication同級(jí)的目錄下創(chuàng)建一個(gè)Activity。Activity是android系統(tǒng)最小的調(diào)度單位。

04.jpg

創(chuàng)建名稱為MyActivity的空活動(dòng)。它會(huì)幫助我們生成一個(gè)自動(dòng)布局文件做布局的工作。(此時(shí)若遇到錯(cuò)誤,可以選擇build->clean)

05.jpg

2、command+enter點(diǎn)擊進(jìn)入activity_my中,此時(shí)會(huì)打開布局文件。將左下角的Design切換成Text文件。在該文件中,為原生界面創(chuàng)建一個(gè)按鈕并且布局。


06.jpg

xmlns:android表示設(shè)置xmlns的命名空間,沒有這句話就無法設(shè)置屬性的約束。
在該界面上創(chuàng)建一個(gè)按鈕,為按鈕綁定一個(gè)方法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代碼,實(shí)現(xiàn)onBack方法。

  //點(diǎn)擊按鈕,直接完成
    public void onBack(View v){
        finish();
    }

4、到MyNativeModule原生模塊中去實(shí)現(xiàn)Activity

上文中已經(jīng)說過,Activity是android系統(tǒng)的最小調(diào)度單位,而Intent則是安卓的進(jìn)程之間、activity之間、線程之間交換數(shù)據(jù)的載體。

 //方法不能返回值 因?yàn)楸徽{(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)建一個(gè)意圖,意圖是android進(jìn)程之間、線程之間、交換數(shù)據(jù)的載體
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);    //一定要加上這句
        mContext.startActivity(intent);
    }

效果圖如下:

07RN與原生切換.gif

三、RN用Promise機(jī)制與安卓原生代碼通信

使用Promise機(jī)制也是RN與原生通信的一種方式。在原生代碼的MyNativeModule文件中創(chuàng)建橋接方法。當(dāng)橋接的原生方法的最后一個(gè)參數(shù)是一個(gè)Promise對(duì)象,那么該方法會(huì)返回一個(gè)JS的Promise對(duì)象給與之對(duì)應(yīng)的js方法。

與上文類似,需要暴露給RN的方法不能有返回值,并且要以注釋@ReactMethod標(biāo)識(shí)。

  @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)實(shí)現(xiàn)。接下來要實(shí)現(xiàn)的就是RN的代碼。在RN中創(chuàng)建一個(gè)方法,這個(gè)方法內(nèi)部使用NativeModules組件來調(diào)用原生模塊提供的名稱,進(jìn)而找到要調(diào)用的原生方法。原生方法最后一個(gè)參數(shù)是一個(gè)promise,所以在js中用.then的方法實(shí)現(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é)果圖如下:

08.jpg

用Debug進(jìn)行調(diào)試,得到結(jié)果如下:

09.jpg

四、RN用callback回調(diào)方式與安卓原生代碼通信

按照上文中提到的方式,在原生模塊中暴露一個(gè)橋接方法給RN調(diào)用。
參數(shù)傳入一個(gè)成功的回調(diào)和一個(gè)失敗的回調(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中實(shí)現(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);
            }
        )
    }
10.jpg

在Debug調(diào)試下,點(diǎn)擊callback通信文字,可以看到如下結(jié)果:

11.jpg

在使用回調(diào)函數(shù)時(shí)會(huì)呈現(xiàn)出某些缺點(diǎn),比如說每次調(diào)用只應(yīng)當(dāng)調(diào)用一次,多次調(diào)用可能會(huì)出現(xiàn)意想不到的結(jié)果,并且用這種方法安卓原生代碼是無法主動(dòng)發(fā)送信息給RN側(cè)的。而消息機(jī)制的方式就可以進(jìn)行消息的互相傳遞。


小孩與狗童真分隔線
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容