
最近在因?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)。本文主要介紹RN與Android原生之間的一些交互操作,以及原生中間件的封裝流程。涉及如何調(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è)置Button的Text屬性:
// 導(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)映射的事件通知方法
- 在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");
- 在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方法。
- 在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)。
- 在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);
}}
/>