有時候App需要通路平台API,但React Native可能還沒有相應的子產品包裝;或者你需要複用一些Java代碼,而不是用Javascript重新實作一遍;又或者你需要實作某些高性能的、多線程的代碼,譬如圖檔處理、資料庫、或者各種進階擴充等等。
使用步驟
在RN中使用系統原生子產品需要如下步驟:
- 建立一個原生子產品。一個原生子產品是一個繼承了ReactContextBaseJavaModule的Java類,派生實作getName 傳回一個字元串名字,這個名字在JavaScript端标記這個子產品
- 一個可選的方法getContants傳回了需要導出給JavaScript使用的常量。它并不一定需要實作,但在定義一些可以被JavaScript同步通路到的預定義的值時非常有用
- 導出一個方法給JavaScript使用,Java方法需要使用注解@ReactMethod。方法的傳回類型必須為void
- 注冊子產品。我們需要在應用的Package類的createNativeModules方法中添加這個子產品
##編寫子產品##
下面,我以官方的demo為栗子,來顯示一個在js中調用android原生toast的demo,首先需要編寫一個類繼承自ReactContextBaseJavaModule。
package com.secondproject;
import android.widget.Toast;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import java.util.HashMap;
public class MyToastModule extends ReactContextBaseJavaModule {
private static final String DURATION_SHORT_KEY = "SHORT";
private static final String DURATION_LONG_KEY = "LONG";
public MyToastModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* getName方法。這個函數用于傳回一個字元串名字,就是js中的子產品名
*/
@Override
public String getName() {
return "MyToast";
}
/**
* 傳回了需要導出給JavaScript使用的常量
*/
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
return constants;
}
/**
* 導出給js使用的方法,需要使用注解@ReactMethod。方法的傳回類型必須為void
*/
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
}
}
##注冊子產品##
上面已經編寫好了導出給js使用的子產品,下面還需要注冊目前子產品,如果子產品沒有被注冊,它也無法在JavaScript中被通路到。注冊子產品份兩步:
- 編寫類實作ReactPackage接口
-
在目前應用的MainActivity.java中添加目前子產品
我們先來看下系統的ReactPackage接口:
node_modules\react-native\ReactAndroid\src\main\java\com\facebook\react\ReactPackage.java
package com.facebook.react;
import java.util.List;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
public interface ReactPackage {
List<NativeModule> createNativeModules(ReactApplicationContext reactContext);
List<Class<? extends JavaScriptModule>> createJSModules();
List<ViewManager> createViewManagers(ReactApplicationContext reactContext);
}
可以看到這裡有三個方法需要實作,其中,目前最關心的就是createNativeModules方法的實作。
- MyReactPackage.java
package com.secondproject;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
public class MyReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
// 這裡的MyToastModule是之前他添加的module
modules.add(new MyToastModule(reactContext));
return modules;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
// TODO Auto-generated method stub
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
// TODO Auto-generated method stub
return Collections.emptyList();
}
}
-
在MainActivity中添加目前子產品
可以看到在MainActivity的onCreate方法中,有這樣一段代碼:
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModuleName("index.android")
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
在目前代碼裡已經添加了一個package,.addPackage(new MainReactPackage()),這裡将自己之前定義的MyReactPackage 同樣加進去即可:
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();
##在javascript中使用原生子產品##
使用也很簡單,隻需要這樣一句代碼就可以了:
// 這裡的MyToast就是在MyToastModule的getName中傳回的module名稱
React.NativeModules.MyToast.show('調用系統的toast啦', ToastAndroid.SHORT);
_onPress: function(duration,content) {
React.NativeModules.MyToast.show('調用系統的toast啦', ToastAndroid.SHORT);
},
render: function() {
return (
<View style={{flexDirection: 'column'}}>
<Text onPress={()=> this._onPress(ToastAndroid.SHORT,'this is short')} style={styles.button}>點選調用原生API</Text>
</View>
);
}
現在效果如下:
##将原生子產品封裝##
為了在js裡使用友善,可以将原生子產品封裝成一個JavaScript子產品,在index.android.js同目錄下建立一個toast.js檔案:
toast.js
'use strict';
var { NativeModules } = require('react-native');
// 這裡的MyToast就是在MyToastModule 的getName傳回的子產品名
module.exports = NativeModules.MyToast;
此時使用起來就很友善了,如下:
// 引入目前的子產品,注意toast是檔案名
var MyToast = require('./toast');
// 這裡的MyToast是toast.js檔案通過module.exports給我們的
MyToast.show('調用系統的tdsaf', ToastAndroid.SHORT);
##參數類型##
下面的參數類型在@ReactMethod注明的方法中,會被直接映射到它們對應的JavaScript類型
Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
回調函數
有時候,在js中調用android中自定義子產品中的方法,需要有傳回值,此時就需要使用到RN為我們封裝好的一個Callback接口。
package com.facebook.react.bridge;
public interface Callback {
public void invoke(Object... args);
}
可以看到,通過我們在java中調用invoke方法,将結果傳回給js的,傳回的參數類型和個數都是不限的。
###建立CallbackModule###
package com.secondproject;
import android.widget.Toast;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import java.util.Map;
import java.util.HashMap;
public class CallbackModule extends ReactContextBaseJavaModule {
public CallbackModule(ReactApplicationContext reactContext) {
super(reactContext);
}
// 傳回js中使用目前子產品的名稱
@Override
public String getName() {
return "MyCallback";
}
@ReactMethod
public void getAddResult(int number1, int number2, Callback callback) {
try {
int result = number1 + number2;
//通過invoke方法将結果傳遞給js
callback.invoke("結果是:",result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
###注冊CallbackModule 子產品###
記得在之前的MyReactPackage中注冊目前子產品:
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new MyToastModule(reactContext));
modules.add(new CallbackModule(reactContext));
return modules;
}
###建立callback.js###
- 在與index.android.js同目錄下建立callback.js,用來導出目前CallbackModule 子產品。
'use strict';
var { NativeModules } = require('react-native');
module.exports = NativeModules.MyCallback;
###使用MyCallback###
MyCallback的使用和MyToast相同,這裡我将兩者結合起來使用,代碼如下:
var MyToast = require('./toast');
var MyCallback = require('./callback');
MyCallback.getAddResult(3,2,(code,result)=>{
console.log("callback",code,result);
MyToast.show(code + result,ToastAndroid.SHORT);
});
此時效果如下:
##發送事件到JavaScript##
另外,原生的代碼也可以在沒有被調用的情況下向js發送事件通知。通過RCTDeviceEventEmitter來實作。這裡依然以剛才的CallbakModule為例:
@ReactMethod
public void getAddResult(int number1, int number2, Callback callback) {
try {
WritableMap params = Arguments.createMap();
//通過WritableMap傳遞一個參數給js,也可以傳遞多個
params.putString("first","i am first");
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("toastMe", params);//對應的javascript層的事件名為toastMe,注冊該事件即可進行回調
int result = number1 + number2;
callback.invoke("結果是:",result);
} catch (Exception e) {
e.printStackTrace();
}
}
###在componentDidMount方法中注冊事件###
componentDidMount:function(){
//使用DeviceEventEmitter注冊事件
DeviceEventEmitter.addListener('toastMe',(e)=>{
MyToast.show(e.first,ToastAndroid.SHORT);
});
},
這裡注冊的事件後,一旦調用getAddResult方法,就會回到toastMe方法。此時效果如下:
ok,今天就到這裡了,由于代碼較大,這裡我将用到的代碼打包上傳,
歡 迎 關 注 我 的 公 衆 号 “編 程 大 全”
專注技術分享,包括Java,python,AI人工智能,Android分享,不定期更新學習視訊