【RN】ReactNative與原生交互之Android篇


最近在因?yàn)轫?xiàng)目需求需要,需要在原本的Android工程中集成RN,用RN來(lái)開(kāi)發(fā)需求經(jīng)常變更的、變更周期短的業(yè)務(wù)。寫(xiě)下這篇文章用來(lái)記述集成過(guò)程中的細(xì)節(jié)注意點(diǎn)以及一些學(xué)習(xí)經(jīng)驗(yàn)。本文主要介紹RNAndroid原生之間的一些交互操作,以及原生中間件的封裝流程。涉及如何調(diào)用原生接口、傳參、獲取回調(diào)值、獲取常量值、調(diào)用原生UI、監(jiān)聽(tīng)原生發(fā)送的事件、線(xiàn)程操作等。

一、自定義原生模塊

  • 創(chuàng)建自定義模塊
  • 注冊(cè)自定義模塊
  • 在RN中使用自定義模塊
  • 獲取原生模塊預(yù)設(shè)常量值
  • 導(dǎo)出帶參函數(shù)方法
  • 導(dǎo)出帶參函數(shù)方法,并使用Callback回調(diào)函數(shù)返回結(jié)果信息
  • 導(dǎo)出帶參函數(shù)方法,并使用Promises返回結(jié)果信息

1、創(chuàng)建自定義模塊

ReactNative在設(shè)計(jì)之初就考慮能夠在其基礎(chǔ)上通過(guò)原生代碼封裝來(lái)間接達(dá)到編寫(xiě)原生代碼的能力。比如當(dāng)我們需求在RN中調(diào)用原生的某個(gè)模塊功能時(shí),我們可以通過(guò)將原生代碼封裝成可以提供給RN調(diào)用的中間件形式,提供相應(yīng)的功能。通常這樣的原生模塊需要繼承ReactContextBaseJavaModule的Java類(lèi)。

  • Android項(xiàng)目中創(chuàng)建CustomModule.java,并繼承自ReactContextBaseJavaModule類(lèi)
  • 實(shí)現(xiàn)初始化函數(shù):
    public CustomModule(ReactApplicationContext reactContext) { super(reactContext) }
  • 實(shí)現(xiàn)getName方法,該方法返回自定義模塊名稱(chēng),在RN中我們將通過(guò)NativateModules.自定義模塊名稱(chēng)的形式訪(fǎng)問(wèn)該模塊
public class CustomModule extends ReactContextBaseJavaModule {

    public CustomModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    // 設(shè)置模塊名稱(chēng),需要與RN中調(diào)用時(shí)的模塊名稱(chēng)保持一致
    public String getName() {
        return "CustomModule";
    }
}

完成上述步驟我們就簡(jiǎn)單創(chuàng)建了一個(gè)提供RN使用的原生功能組件,但是現(xiàn)在RN中還不能夠直接調(diào)用該模塊。我們還需要向RN注冊(cè)該模塊,將模塊的功能代碼注入到JavaScript中,最終才能在RN中才能夠使用。接下來(lái)我們?nèi)プ?cè)模塊...

2、注冊(cè)自定義模塊

我們通過(guò)ReactPackage類(lèi)的createNativeModules方法中添加自定義模塊實(shí)例,實(shí)現(xiàn)自定義模塊向RN的注冊(cè)。為了方便后期項(xiàng)目中統(tǒng)一管理自定義模塊的注冊(cè),我們創(chuàng)建一個(gè)AndroidReactPackage管理類(lèi),并實(shí)現(xiàn)ReactPackage類(lèi)的構(gòu)造方法。

public class AndroidReactPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new CustomModule(reactContext));
        return modules;
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

完成模塊注冊(cè)的最后一步,就是將自定義的AndroidReactPackage添加到ReactPkage中。具體方法就是在MainApplication.java文件中的getPackages方法中添加AndroidReactPackage的實(shí)例。

@Override
protected List<ReactPackage> getPackages() {
   return Arrays.<ReactPackage>asList(
       new MainReactPackage(),
       new AndroidReactPackage()
   );
}

3、在RN中使用自定義原生模塊

在原生項(xiàng)目中實(shí)現(xiàn)向RN中注冊(cè)自定義模塊之后,我們可以在JavaScript中通過(guò)NativeModules獲取對(duì)應(yīng)的模塊。通常我們將原生模塊封裝成一個(gè)JavaScript模塊,省去直接從NativeModules中獲取對(duì)應(yīng)模塊的步驟。

// CustomModule.js
/**
 * This exposes the native CustomModule module as a JS module. This has a
 * function 'sendRequest' which takes the following parameters:
 *
 * 1. String parmas: A string with the parmas
 */
import { NativeModules } from "react-native";

module.exports = NativeModules.CustomModule;

注意:這兒NativeModules.后面的名稱(chēng)是原生模塊中getName方法返回的字符串,必須要保持一致。

JavaScript中引用封裝的JS模塊:

import CustomModule from '../NativeModules/CustomModule'

4、獲取模塊預(yù)設(shè)常量值

自定義模塊的時(shí)候,通常我們的模塊中會(huì)有一些預(yù)設(shè)的參數(shù)值。在RN中想要獲取這些數(shù)值時(shí),需要在原生的模塊代碼中實(shí)現(xiàn)getConstants方法,該方法返回一個(gè)Map<String, Object>。HashMap的Key值為RN中對(duì)應(yīng)的屬性名稱(chēng),Value值為RN中對(duì)應(yīng)的屬性的值。

    private static final String CUSTOM_CONST_KEY = "TEXT";

    @Nullable
    @Override
    // 獲取模塊預(yù)定義的常量值
    public Map<String, Object> getConstants() {

        final Map<String, Object> constants = new HashMap<>();
        constants.put(CUSTOM_CONST_KEY, "這是模塊預(yù)設(shè)常量值");
        return constants;
    }

RN視圖中顯示自定義模塊預(yù)設(shè)常量值:

<Text>{ CustomModule.TEXT }</Text>

5、導(dǎo)出帶參函數(shù)方法

使用@ReactMethod注解可以導(dǎo)出函數(shù)方法供JavaScript調(diào)用:

    // 導(dǎo)出帶參函數(shù)方法
    @ReactMethod
    public void sendRequest(String parmas) {
        // To Do Something
        Toast.makeText(getReactApplicationContext(), parmas, Toast.LENGTH_SHORT).show();
    }

JavaScript中調(diào)用函數(shù)方法,并傳遞參數(shù):

import CustomModule from '../NativeModules/CustomModule'

CustomModule.sendRequest("This is a string of parma");

6、導(dǎo)出帶參函數(shù),并使用Callback返回結(jié)果

如果函數(shù)方法需要返回結(jié)果,那么可以使用Callback回調(diào)函數(shù),將返回值傳回給JavaScript

    // 導(dǎo)出帶參函數(shù)方法,并使用Callback回調(diào)函數(shù)返回回調(diào)結(jié)果
    @ReactMethod
    public void sendRequest(String message, Callback success, Callback failture) {

        try {
            String parma1 = message;
            String parma2 = "收到回調(diào)信息";
            // 回調(diào)成功,返回結(jié)果信息
            success.invoke(parma1, parma2);
        }catch (IllegalViewOperationException e) {
            // 回調(diào)失敗,返回錯(cuò)誤信息
            failture.invoke(e.getMessage());
        }
    }

JavaScript中調(diào)用帶Callbak回調(diào)函數(shù)的方法,并返回結(jié)果信息:

CustomModule.sendRequest(
    "這是帶Callback回調(diào)的函數(shù)方法",
     (parma1, parma2) => {
        var result = parma1 + parma2;
        console.log(result);
    },
    errMsg => {
        console.log(errMsg);
    }
);

7、導(dǎo)出帶參函數(shù),并使用Promises返回結(jié)果

除了設(shè)置Callback回調(diào)函數(shù)的方法外,RN還提供了設(shè)置橋接方法的最后一個(gè)參數(shù)為一個(gè)Promise,并搭配 ES2016(ES7)標(biāo)準(zhǔn)的async/await語(yǔ)法的方式來(lái)返回調(diào)用結(jié)果的方式。

    private static final String E_FUNCTION_ERROR = "E_FUNCTION_ERROR";

    // 導(dǎo)出帶參函數(shù)方法,并使用Promise簡(jiǎn)化回調(diào)結(jié)果方法
    @ReactMethod
    public void sendRequest(String message, Promise promise) {

        try {
            String result = message + ",收到回調(diào)信息";

            WritableMap map = Arguments.createMap();
            map.putString("content", result);

            // 回調(diào)成功,返回結(jié)果信息
            promise.resolve(map);
        }catch (IllegalViewOperationException e) {
            // 回調(diào)失敗,返回錯(cuò)誤信息
            promise.reject(E_FUNCTION_ERROR, e);
        }
    }

JavaScript端調(diào)用上面方法后會(huì)返回一個(gè)promise對(duì)象。在一個(gè)聲明了async的異步函數(shù)內(nèi)使用await關(guān)鍵字來(lái)調(diào)用,并等待結(jié)果的返回。

async function testSendRequest() {
    
    try {
        var { content } = await CustomModule.sendRequest(
            "這是使用Promise回調(diào)的函數(shù)方法",
        );
        console.log(content);
    }catch (e) {
        console.error(e);
    }
}

testSendRequest();

二、自定義視圖組件

ReactNative除了可以封裝原生模塊之外,還可以將原生UI視圖封裝成組件后供RN使用。接下來(lái)我們來(lái)說(shuō)說(shuō)如何封裝一個(gè)原生的UI視圖組件及其屬性值設(shè)置、事件通知,視圖跳轉(zhuǎn)等。

  • 創(chuàng)建自定義視圖組件
  • 注冊(cè)自定義視圖組件
  • 封裝對(duì)應(yīng)的JavaScript組件代碼
  • 導(dǎo)出自定義視圖組件屬性設(shè)置器
  • 處理自定義原生視圖組件的事件通知
  • 跳轉(zhuǎn)Activity視圖

1、創(chuàng)建自定義視圖組件

在自定義原生模塊的時(shí)候,我們知道第一步就是創(chuàng)建一個(gè)繼承ReactContextBaseJavaModule類(lèi)的子類(lèi)。同樣的,創(chuàng)建自定義原生UI視圖需要被一個(gè)ViewMangager或者SimpleViewManager的派生類(lèi)創(chuàng)建和管理。一個(gè)SimpleViewManager的子類(lèi)包含許多公共屬性,包括背景色、透明度、Flexbox布局等。每一個(gè)子類(lèi)都是一個(gè)單例類(lèi)。它們被NativeViewHierarchyManager所管理,在合適的時(shí)候NativeViewHierarchyManager會(huì)委托原生UI視圖組件去更新相應(yīng)的視圖屬性。 ViewMangager還會(huì)代理原生視圖的所有委托,在適當(dāng)?shù)臅r(shí)候向RN發(fā)送對(duì)應(yīng)的事件通知。
簡(jiǎn)單創(chuàng)建自定義按鈕視圖組件的具體步驟如下:

  • Android項(xiàng)目中創(chuàng)建SimpleViewManager的子類(lèi)。<Button>指定RCTCustomButton這個(gè)視圖管理類(lèi)所管理的對(duì)象類(lèi)型是原生組件Button類(lèi)型。
  • 實(shí)現(xiàn)getName方法,該方法返回自定義視圖組件的名稱(chēng)
  • 實(shí)現(xiàn)createViewInstance方法,創(chuàng)建原生Button實(shí)例并返回。
package com.rnproject;

import android.widget.Button;

import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;

public class RCTCustomButton extends SimpleViewManager<Button> {

    private ThemedReactContext mReactContext;

    @Override
    public String getName() {
        return "RCTCustomButton";
    }

    @Override
    protected Button createViewInstance(ThemedReactContext reactContext) {

        this.mReactContext = reactContext;
        Button button = new Button(reactContext);
        return button;
    }
}

2、注冊(cè)自定義視圖組件

我們通過(guò)ReactPackage類(lèi)的createViewManagers方法中添加自定義視圖組件實(shí)例,實(shí)現(xiàn)自定義視圖組件向RN的注冊(cè)。之前我們創(chuàng)建一個(gè)AndroidReactPackage管理類(lèi),并實(shí)現(xiàn)了ReactPackage類(lèi)的構(gòu)造方法。我們可以在這個(gè)Package中注冊(cè)自定義的視圖組件。

public class AndroidReactPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {

        List<NativeModule> modules = new ArrayList<>();
        modules.add(new CustomModule(reactContext));
        return modules;
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {

        List<ViewManager> managers = new ArrayList<>();
        managers.add(new RCTCustomButton());
        return managers;
    }
}

3、封裝對(duì)應(yīng)的JavaScript組件代碼

JavaScript中,我們通過(guò)requireNativeComponent引入自定義視圖組件,requireNativeComponent接受的參數(shù)為視圖組件的名稱(chēng),與原生組件中getName方法返回的字符串保持一致。

// RCTCustomButton.js

import { requireNativeComponent } from "react-native";
/**
 * Composes `Button`.
 */
module.exports = requireNativeComponent("RCTCustomButton");

RN布局中使用自定義視圖組件:

import RCTCustomButton from '../RCTCustomButton'

<RCTCustomButton style = {{width:160,height:50}}/>

4、導(dǎo)出視圖的屬性設(shè)置器

使用 @ReactProp或者@ReactPropGroup注解可以將視圖屬性設(shè)置器的導(dǎo)出,提供RN設(shè)置原生視圖控件屬性的方法。
比如設(shè)置ButtonText屬性:

    // 導(dǎo)出視圖屬性設(shè)置器
    @ReactProp(name = "text")
    public void setText(Button button, String text) {
        button.setText(text);
    }

RN布局代碼中設(shè)置自動(dòng)定義按鈕組件的標(biāo)題:

<RCTCustomButton text="原生自定義按鈕組件" style = {{width:160,height:50}}/>

5、處理自定義原生視圖組件的事件通知

我們知道原生按鈕是有點(diǎn)擊事件的,那么我們?nèi)绾螌粹o的點(diǎn)擊事情傳遞給JavaScript端呢?RN如何來(lái)處理原生的事件通知呢?在原生我們封裝的JavaScript組件代碼中沒(méi)有任何的處理事件通知的方法,那么接下來(lái)我們就去處理原生組件的事件通知。

  • 首先我們需要將封裝的JavaScript端的組件代碼(RCTCustomButton.js)進(jìn)行改動(dòng),將其封裝成React組件
  • Android原生視圖組件代碼(RCTCustomButton.java文件)中重寫(xiě)addEventEmitters方法,添加按鈕點(diǎn)擊事件監(jiān)聽(tīng)通知
  • 在點(diǎn)擊事件的實(shí)現(xiàn)方法中調(diào)用getJSModule(RCTEventEmitter.class).receiveEvent方法,傳遞事件通知。
    receiveEvent方法參數(shù)說(shuō)明:
    第一個(gè)參數(shù)通過(guò)getId()方法將原生組件和React組件關(guān)聯(lián)在一起
    第二個(gè)參數(shù)是事件映射到JavaScript中的Key
    第三個(gè)參數(shù)是事件傳遞到JavaScript端的數(shù)據(jù)
  • 在Android原生視圖組件代碼(RCTCustomButton.java文件)中覆寫(xiě)getExportedCustomBubblingEventTypeConstants方法將事件通知映射到JavaScript
  • JavaScript端的組件代碼(RCTCustomButton.js)中實(shí)現(xiàn)映射的事件通知方法
  1. 在RN中重新封裝RCTCustomButton.js代碼,將其封裝成React組件
// RCTCustomButton.js

import React, { Component } from 'react';
import { requireNativeComponent } from "react-native";

export default class RCTCustomButton extends Component {

    render() {
        return <CustomButton {...this.props}/>;
    }
}
var CustomButton = requireNativeComponent("RCTCustomButton");
  1. Android原生視圖組件RCTCustomButton.java中添加事件監(jiān)聽(tīng),關(guān)聯(lián)視圖組件并傳遞事件通知及其數(shù)據(jù)
    private static final String EVENT_NATIVE_ONCLICK_NAME = "onNativeClick";

    // 重寫(xiě)addEventEmitters方法,傳遞點(diǎn)擊事件
    @Override
    protected void addEventEmitters(final ThemedReactContext reactContext, final Button button) {
        super.addEventEmitters(reactContext, button);

        // 添加按鈕點(diǎn)擊事件監(jiān)聽(tīng)
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // 返回?cái)?shù)據(jù)
                WritableMap dataMap = Arguments.createMap();
                dataMap.putString("msg", "這是原生按鈕點(diǎn)擊事件");
                // 傳遞事件及其數(shù)據(jù)
                reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
                        view.getId(),
                        EVENT_NATIVE_ONCLICK_NAME,
                        dataMap
                );
            }
        });
    }

注意:如果不重寫(xiě)addEventEmitters方法,在createViewInstance方法在添加事件監(jiān)聽(tīng)也是可以的。但是優(yōu)先級(jí)低于addEventEmitters方法。

  1. 在Android原生視圖組件代碼(RCTCustomButton.java文件)中覆寫(xiě)getExportedCustomBubblingEventTypeConstants方法將事件通知映射到JavaScript
    // 覆寫(xiě)getExportedCustomBubblingEventTypeConstants方法,將事件映射到JavaScript端
    @Nullable
    @Override
    public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
        return MapBuilder.<String, Object>builder().put(
                EVENT_NATIVE_ONCLICK_NAME,
                MapBuilder.of(
                        "phasedRegistrationNames",
                        MapBuilder.of(
                                "bubbled",
                                EVENT_JS_ONCLICK_NAME
                        )
                )
        ).build();
    }

踩坑啦,踩坑啦:這兒只需要更改EVENT_NATIVE_ONCLICK_NAME對(duì)應(yīng)原生組件事件Key值,EVENT_JS_ONCLICK_NAME對(duì)應(yīng)JS端事件的Key值,其他的直接復(fù)制粘貼,尤其是字符串“phasedRegistrationNames”“bubbled”不要更改,不然會(huì)導(dǎo)致映射失敗,點(diǎn)擊按鈕發(fā)現(xiàn)沒(méi)有反應(yīng)。

  1. JavaScript端的組件代碼(RCTCustomButton.js)中實(shí)現(xiàn)映射的事件通知方法,綁定到封裝的組件的方法上。
// RCTCustomButton.js

import React, { Component } from 'react';
import { requireNativeComponent } from "react-native";

export default class RCTCustomButton extends Component {

    constructor(props){
        super(props)
        this._onJSClickEvent = this._onJSClickEvent.bind(this);
    }

    _onJSClickEvent(event: Event) {

        if (!this.props.onClick) {
            return;
        }

        // 獲取原生事件傳遞的數(shù)據(jù)
        this.props.onClick(event.nativeEvent.msg);
    }

    render() {
        return <CustomButton {...this.props} onJSClick={ this._onJSClickEvent }/>;
    }
}

var CustomButton = requireNativeComponent("RCTCustomButton");

完成上述步驟后,我們就可以通過(guò)onClick屬性方法回調(diào)原生組件的點(diǎn)擊事件了O(∩_∩)O~)

在JS中調(diào)用我們自定義的原生組件:

import RCTCustomButton from '../RCTCustomButton'

<RCTCustomButton
    text="原生自定義按鈕組件"
    style = {{width:160,height:50}}
    onClick={(msg) => {
       Alert.alert(msg);
    }}
 />
最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,366評(píng)論 25 708
  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 14,142評(píng)論 2 59
  • 那只是一方小小的窗臺(tái)。 那時(shí)的我必須爬上板凳,才能夠找外面的世界。樓房并不高,但足以俯瞰并眺望遠(yuǎn)方。...
    Letsstart閱讀 170評(píng)論 0 0
  • 兒子小時(shí)候真是多災(zāi)多難,總是碰到驚險(xiǎn)的事。那時(shí)候我一個(gè)人帶孩子,平時(shí)其本每天有男孩子來(lái)幫忙帶的。有的歲數(shù)小點(diǎn),有的...
    A寒秋閱讀 551評(píng)論 12 13
  • 沒(méi)有一個(gè)女人不美——一位色鬼朋友 在哈市,老房子是韶華已逝的美人馬迭爾眼尾紋里有寧?kù)o風(fēng)暴允許你進(jìn)入她身體或者內(nèi)心索...
    阿劍啊閱讀 318評(píng)論 5 13

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