最近在因?yàn)轫?xiàng)目需求需要模闲,需要在原本的Android工程中集成RN,用RN來開發(fā)需求經(jīng)常變更的、變更周期短的業(yè)務(wù)存璃。寫下這篇文章用來記述集成過程中的細(xì)節(jié)注意點(diǎn)以及一些學(xué)習(xí)經(jīng)驗(yàn)叉存。本文主要介紹RN與Android原生之間的一些交互操作,以及原生中間件的封裝流程昙衅。涉及如何調(diào)用原生接口扬霜、傳參、獲取回調(diào)值而涉、獲取常量值著瓶、調(diào)用原生UI、監(jiān)聽原生發(fā)送的事件啼县、線程操作等材原。
一、自定義原生模塊
- 創(chuàng)建自定義模塊
- 注冊自定義模塊
- 在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ǔ)上通過原生代碼封裝來間接達(dá)到編寫原生代碼的能力子刮。比如當(dāng)我們需求在RN中調(diào)用原生的某個(gè)模塊功能時(shí)威酒,我們可以通過將原生代碼封裝成可以提供給RN調(diào)用的中間件形式,提供相應(yīng)的功能挺峡。通常這樣的原生模塊需要繼承ReactContextBaseJavaModule的Java類葵孤。
- 在Android項(xiàng)目中創(chuàng)建CustomModule.java,并繼承自ReactContextBaseJavaModule類
- 實(shí)現(xiàn)初始化函數(shù):
public CustomModule(ReactApplicationContext reactContext) { super(reactContext) }
- 實(shí)現(xiàn)
getName
方法橱赠,該方法返回自定義模塊名稱尤仍,在RN中我們將通過NativateModules.自定義模塊名稱
的形式訪問該模塊
public class CustomModule extends ReactContextBaseJavaModule {
public CustomModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
// 設(shè)置模塊名稱,需要與RN中調(diào)用時(shí)的模塊名稱保持一致
public String getName() {
return "CustomModule";
}
}
完成上述步驟我們就簡單創(chuàng)建了一個(gè)提供RN使用的原生功能組件狭姨,但是現(xiàn)在RN中還不能夠直接調(diào)用該模塊吓著。我們還需要向RN注冊該模塊,將模塊的功能代碼注入到JavaScript中送挑,最終才能在RN中才能夠使用绑莺。接下來我們?nèi)プ阅K...
2、注冊自定義模塊
我們通過ReactPackage類的createNativeModules
方法中添加自定義模塊實(shí)例惕耕,實(shí)現(xiàn)自定義模塊向RN的注冊纺裁。為了方便后期項(xiàng)目中統(tǒng)一管理自定義模塊的注冊,我們創(chuàng)建一個(gè)AndroidReactPackage
管理類,并實(shí)現(xiàn)ReactPackage
類的構(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();
}
}
完成模塊注冊的最后一步栋豫,就是將自定義的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中注冊自定義模塊之后,我們可以在JavaScript中通過NativeModules獲取對應(yīng)的模塊嫩絮。通常我們將原生模塊封裝成一個(gè)JavaScript模塊丛肢,省去直接從NativeModules中獲取對應(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.后面的名稱是原生模塊中getName方法返回的字符串剿干,必須要保持一致蜂怎。
在JavaScript中引用封裝的JS模塊:
import CustomModule from '../NativeModules/CustomModule'
4、獲取模塊預(yù)設(shè)常量值
自定義模塊的時(shí)候置尔,通常我們的模塊中會有一些預(yù)設(shè)的參數(shù)值杠步。在RN中想要獲取這些數(shù)值時(shí),需要在原生的模塊代碼中實(shí)現(xiàn)getConstants
方法榜轿,該方法返回一個(gè)Map<String, Object>
幽歼。HashMap的Key值為RN中對應(yīng)的屬性名稱,Value值為RN中對應(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語法的方式來返回調(diào)用結(jié)果的方式脑沿。
private static final String E_FUNCTION_ERROR = "E_FUNCTION_ERROR";
// 導(dǎo)出帶參函數(shù)方法藕畔,并使用Promise簡化回調(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)用上面方法后會返回一個(gè)promise對象韭邓。在一個(gè)聲明了async的異步函數(shù)內(nèi)使用await關(guān)鍵字來調(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使用辜御。接下來我們來說說如何封裝一個(gè)原生的UI視圖組件及其屬性值設(shè)置鸭你、事件通知,視圖跳轉(zhuǎn)等擒权。
- 創(chuàng)建自定義視圖組件
- 注冊自定義視圖組件
- 封裝對應(yīng)的JavaScript組件代碼
- 導(dǎo)出自定義視圖組件屬性設(shè)置器
- 處理自定義原生視圖組件的事件通知
- 跳轉(zhuǎn)Activity視圖
1袱巨、創(chuàng)建自定義視圖組件
在自定義原生模塊的時(shí)候,我們知道第一步就是創(chuàng)建一個(gè)繼承ReactContextBaseJavaModule類的子類菜拓。同樣的瓣窄,創(chuàng)建自定義原生UI視圖需要被一個(gè)ViewMangager或者SimpleViewManager的派生類創(chuàng)建和管理笛厦。一個(gè)SimpleViewManager的子類包含許多公共屬性纳鼎,包括背景色、透明度裳凸、Flexbox布局等贱鄙。每一個(gè)子類都是一個(gè)單例類。它們被NativeViewHierarchyManager所管理姨谷,在合適的時(shí)候NativeViewHierarchyManager會委托原生UI視圖組件去更新相應(yīng)的視圖屬性逗宁。 ViewMangager還會代理原生視圖的所有委托,在適當(dāng)?shù)臅r(shí)候向RN發(fā)送對應(yīng)的事件通知梦湘。
簡單創(chuàng)建自定義按鈕視圖組件的具體步驟如下:
- 在Android項(xiàng)目中創(chuàng)建SimpleViewManager的子類瞎颗。
<Button>
指定RCTCustomButton這個(gè)視圖管理類所管理的對象類型是原生組件Button類型。- 實(shí)現(xiàn)
getName
方法捌议,該方法返回自定義視圖組件的名稱- 實(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瓣颅、注冊自定義視圖組件
我們通過ReactPackage類的createViewManagers
方法中添加自定義視圖組件實(shí)例倦逐,實(shí)現(xiàn)自定義視圖組件向RN的注冊。之前我們創(chuàng)建一個(gè)AndroidReactPackage
管理類宫补,并實(shí)現(xiàn)了ReactPackage
類的構(gòu)造方法檬姥。我們可以在這個(gè)Package中注冊自定義的視圖組件。
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粉怕、封裝對應(yīng)的JavaScript組件代碼
在JavaScript中健民,我們通過requireNativeComponent
引入自定義視圖組件,requireNativeComponent
接受的參數(shù)為視圖組件的名稱贫贝,與原生組件中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如何來處理原生的事件通知呢俺驶?在原生我們封裝的JavaScript組件代碼中沒有任何的處理事件通知的方法,那么接下來我們就去處理原生組件的事件通知棍辕。
- 首先我們需要將封裝的JavaScript端的組件代碼(RCTCustomButton.js)進(jìn)行改動(dòng)暮现,將其封裝成React組件
- 在Android原生視圖組件代碼(RCTCustomButton.java文件)中重寫
addEventEmitters
方法,添加按鈕點(diǎn)擊事件監(jiān)聽通知- 在點(diǎn)擊事件的實(shí)現(xiàn)方法中調(diào)用
getJSModule(RCTEventEmitter.class).receiveEvent
方法楚昭,傳遞事件通知栖袋。
receiveEvent
方法參數(shù)說明:
第一個(gè)參數(shù)通過getId()
方法將原生組件和React組件關(guān)聯(lián)在一起
第二個(gè)參數(shù)是事件映射到JavaScript中的Key值
第三個(gè)參數(shù)是事件傳遞到JavaScript端的數(shù)據(jù)- 在Android原生視圖組件代碼(RCTCustomButton.java文件)中覆寫
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)聽抚太,關(guān)聯(lián)視圖組件并傳遞事件通知及其數(shù)據(jù)
private static final String EVENT_NATIVE_ONCLICK_NAME = "onNativeClick";
// 重寫addEventEmitters方法塘幅,傳遞點(diǎn)擊事件
@Override
protected void addEventEmitters(final ThemedReactContext reactContext, final Button button) {
super.addEventEmitters(reactContext, button);
// 添加按鈕點(diǎn)擊事件監(jiān)聽
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
);
}
});
}
注意:如果不重寫addEventEmitters
方法,在createViewInstance
方法在添加事件監(jiān)聽也是可以的尿贫。但是優(yōu)先級低于addEventEmitters
方法电媳。
- 在Android原生視圖組件代碼(RCTCustomButton.java文件)中覆寫
getExportedCustomBubblingEventTypeConstants
方法將事件通知映射到JavaScript端
// 覆寫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
對應(yīng)原生組件事件Key值匾乓,EVENT_JS_ONCLICK_NAME
對應(yīng)JS端事件的Key值,其他的直接復(fù)制粘貼又谋,尤其是字符串“phasedRegistrationNames”
和“bubbled”
不要更改拼缝,不然會導(dǎo)致映射失敗,點(diǎn)擊按鈕發(fā)現(xiàn)沒有反應(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");
完成上述步驟后剩愧,我們就可以通過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);
}}
/>