我的部落格原文位址
在React Native開發過程中,有時候我們可能需要通路平台的API,但React Native還沒有相應的實作,或者是React Native還不支援一些原生的屬性,我們需要調用原生代碼來實作,或者是我們需要複用一些原來的Java代碼,這個時候我們就需要建立一個原生子產品來自己實作對我們需要功能的封裝。
可以參考官方文檔或中文文檔。
開發子產品
實作子產品
下面我們就通過實作一個自定義子產品,來熟悉編寫原生子產品需要用的一些知識。該子產品主要實作調用一些Android原生的功能,比如彈
Toast
,啟動
Activity
等。
我們首先來建立一個原生子產品。一個原生子產品是一個繼承了
ReactContextBaseJavaModule
的Java類,它有一個必須實作的方法
getName()
,它傳回一個字元串名字,在JS中我們就使用這個名字調用這個子產品;還有構造函數
NativeModule
。
然後在這個類裡面實作我們需要實作的方法:
public class MyNativeModule extends ReactContextBaseJavaModule {
private final static String MODULE_NAME = "MyNativeModule";
private static final String TestEvent = "TestEvent";
private ReactApplicationContext mContext;
public MyNativeModule(ReactApplicationContext reactContext) {
super(reactContext);
mContext = reactContext;
}
@Override
public String getName() {
return MODULE_NAME;
}
@Nullable
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("SHORT", Toast.LENGTH_SHORT);
constants.put("LONG", Toast.LENGTH_LONG);
constants.put("NATIVE_MODULE_NAME", MODULE_NAME);
constants.put(TestEvent, TestEvent);
return constants;
}
@ReactMethod
public void startActivity(){
Intent intent = new Intent(mContext,SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
@ReactMethod
public void showToast(String msg, int duration){
Toast.makeText(mContext, msg, duration).show();
}
}
React Native調用的方法需要使用@ReactMethod注解。
方法的傳回類型必須為void。
參數類型
原生Java資料類型和JS資料類型的映射關系:
Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
詳情參考:ReadableMap和ReadableArray
導出常量
可以實作
getContants
方法導出需要給JavaScript使用的常量。
@Nullable
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("SHORT", Toast.LENGTH_SHORT);
constants.put("LONG", Toast.LENGTH_LONG);
constants.put("NATIVE_MODULE_NAME", MODULE_NAME);
constants.put(TestEvent, TestEvent);
return constants;
}
注冊子產品
然後我還要注冊這個子產品,通過實作
ReactPackage
接口來實作:
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<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
添加子產品
在
Application
的
getPackages()
方法中添加子產品:
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(), new MyReactPackage()
);
}
或者是在
Activity
的
onCreate
中:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReactRootView = new ReactRootView(this);
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModuleName("index.android")
.addPackage(new MainReactPackage())
.addPackage(new MyReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
mReactRootView.startReactApplication(mReactInstanceManager, "HelloWorld", null);
setContentView(mReactRootView);
}
封裝子產品
為了使JavaScript端通路起來更為友善,通常我們都會把原生子產品封裝成一個JavaScript子產品。在
index.android.js
檔案的同一目錄下面建立一個MyNativeModule.js。
'use strict';
import { NativeModules } from 'react-native';
// 這裡的MyNativeModule必須對應
// public String getName()中傳回的字元串
export default NativeModules.MyNativeModule;
調用子產品
現在,在别處的JavaScript代碼中可以這樣調用你的方法:
import MyNativeModule from './MyNativeModule';
class HelloWorld extends React.Component {
startActivity(){
console.log("MODULE NAME: ",MyNativeModule.NATIVE_MODULE_NAME);
MyNativeModule.startActivity();
}
showToast(){
console.log("MODULE NAME: ",MyNativeModule.NATIVE_MODULE_NAME);
MyNativeModule.showToast("From JS", MyNativeModule.LONG);
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this.startActivity}>
<Text style={styles.hello}>start Activity</Text>
</TouchableOpacity>
</View>
)
}
}
其他特性
React Native的跨語言通路是異步進行的,是以想要給JavaScript傳回一個值的唯一辦法是使用回調函數或者發送事件。
回調函數
原生子產品還支援一種特殊的參數——回調函數。它提供了一個函數來把傳回值傳回給JS。
@ReactMethod
public void testCallback(int para1, int para2, Callback resultCallback){
int result = para1 + para2;
resultCallback.invoke(result);
}
可以在JS中調用:
testCallback(){
MyNativeModule.testCallback(,,(result) => {
console.log("result: ",result); //'result: ',
});
}
原生子產品通常隻應調用回調函數一次。但是,它可以儲存callback并在将來調用。
callback并非在對應的原生函數傳回後立即被執行——注意跨語言通訊是異步的,這個執行過程會通過消息循環來進行。
發送事件到JavaScript
原生子產品可以在沒有被調用的情況下往JavaScript發送事件通知。最簡單的辦法就是通過
RCTDeviceEventEmitter
,這可以通過
ReactContext
來獲得對應的引用:
public void sendEvent(){
WritableMap params = Arguments.createMap();
params.putString("module", "MyNativeModule");
mContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(TestEvent, params);
}
在JS中這樣調用:
import { DeviceEventEmitter } from 'react-native';
......
componentWillMount() {
console.log("componentWillMount");
//接收事件
DeviceEventEmitter.addListener(MyNativeModule.TestEvent, info => {
console.log(info);
});
}
Promise
如果對ES6的
Promise
對象不太熟悉的話,可以點這裡進行了解。
原生子產品還可以使用
promise
來簡化代碼,搭配ES2016(ES7)标準的
async/await
文法則效果更佳。如果橋接原生方法的最後一個參數是一個
Promise
,則對應的JS方法就會傳回一個
Promise
對象。
Promise
是異步程式設計的一種解決方案,比傳統的解決方案(回調函數和事件)更合理和更強大。
@ReactMethod
public void testPromise(Boolean isResolve, Promise promise) {
if(isResolve) {
promise.resolve(isResolve.toString());
}
else {
promise.reject(isResolve.toString());
}
}
在JS中調用:
testPromise(){
MyNativeModule.testPromise(true)
.then(result => {
console.log("result1 is ", result);
})
.catch(result => {
console.log("result2 is ", result);
});
}
這裡可以用
then
方法分别指定
Resolved
狀态和
Reject
狀态的回調函數。第一個回調函數是
Promise
對象的狀态變為
Resolved
時調用,第二個回調函數是
Promise
對象的狀态變為
Reject
時調用。其中,第二個函數是可選的,不一定要提供。
catch
方法用于指定發生錯誤時的回調函數。
結果:
'result1 is ', 'true'
從startActivityForResult中擷取結果
參考官方文檔
監聽生命周期
監聽activity的生命周期事件(比如
onResume
,
onPause
等等),子產品必須實作
LifecycleEventListener
,然後需要在構造函數中注冊一個監聽函數:
public MyNativeModule(ReactApplicationContext reactContext) {
super(reactContext);
mContext = reactContext;
//添加監聽
reactContext.addLifecycleEventListener(this);
}
實作
LifecycleEventListener
的幾個接口:
@Override
public void onHostResume() {
Log.e(MODULE_NAME, "onHostResume");
}
@Override
public void onHostPause() {
Log.e(MODULE_NAME, "onHostPause");
}
@Override
public void onHostDestroy() {
Log.e(MODULE_NAME, "onHostDestroy");
}
然後就可以監聽ReactNative應用的生命周期了。