ReactNative與原生Android通訊互動
前言
之前對ReactNative有過研究,恰好近期公司想在節約成本的前提下,進一步提升App的使用體驗,相比起某些頁面嵌入H5頁面來說,RN的體驗更接近原生,加上RN發展這麼久生態已經比較龐大,於是決定將RN加入專案實踐中。
分析
為了實現React Native與原生App之間的通訊,FB實現了自己的一套互動機制,分別是:
- RCTDeviceEventEmitter 事件方式
- Callback 回撥方式
- Promise 非同步方式
三種方式各具不同優缺點:
1.RCTDeviceEventEmitter
優點:可任意時刻傳遞,Native主導控制。
2.Callback
優點:JS呼叫一次,Native返回。
缺點:CallBack為非同步操作,返回時機不確定
3.Promise
優點:JS呼叫一次,Native返回。
缺點:每次使用需要JS呼叫一次
原生模組
實現三種通訊方式前,我們首先去實現原生模組:
第一步:
一個原生模組是一個繼承了ReactContextBaseJavaModule
的 Java 類,它可以實現一些 JavaScript 所需的功能。
建立一個新的 Java 類並命名為TransMissonMoudle.java,其程式碼如下:
public class TransMissonMoudle extends ReactContextBaseJavaModule { private static final String REACT_CLASS = "TransMissonMoudle"; private ReactContext mReactContext; public TransMissonMoudle(ReactApplicationContext reactContext) { super(reactContext); this.mReactContext = reactContext; } @Override public String getName() { return REACT_CLASS; } }
ReactContextBaseJavaModule
要求派生類實現getName
方法。這個函式用於返回一個字串名字,這個名字在 JavaScript 端標記這個模組。這裡我們把這個模組叫做TransMissonMoudle
,這樣就可以在 JavaScript 中通過NativeModules.TransMissonMoudle
訪問到這個模組。
第二步:
註冊這個模組,我們需要在應用的 Package 類的createNativeModules
方法中新增這個模組。如果模組沒有被註冊,它也無法在 JavaScript 中被訪問到。
建立一個新的 Java 類並命名為TransMissonPackage.java,其程式碼如下:
public class TransMissonPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new TransMissonMoudle(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
第三步:
這個 package 需要在MainApplication.java
檔案的getPackages
方法中提供
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new TransMissonPackage()// <-- 新增這一行,類名替換成你的Package類的名字.
);
}
現在,在別處的 JavaScript 程式碼中可以這樣呼叫你的方法(本地方法被@ReactMethod才能直接呼叫),具體的方法我們後面實現:
import { NativeModules } from 'react-native';
NativeModules.TransMissonMoudle.方法
通訊互動
1.RCTDeviceEventEmitter
原生模組可以在沒有被呼叫的情況下往 JavaScript 傳送事件通知。最簡單的辦法就是通過RCTDeviceEventEmitter
,這可以通過ReactContext
來獲得對應的引用,像這樣:
/**
* 1.RCTDeviceEventEmitter方式
* @param reactContext
* @param eventName 事件名
* @param params 傳參
*/
public void sendTransMisson(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
@ReactMethod
public void contactWithRTC(String type) {
WritableMap writableMap = new WritableNativeMap();
switch (type){
case "getTime":
writableMap.putString("type",type);
writableMap.putString("age","10");
writableMap.putString("time", getTimeMillis());
break;
default:
break;
}
sendTransMisson(mReactContext, "rnTransMisson", writableMap);
}
JavaScript 模組可以通過使用DeviceEventEmitter
模組來監聽事件:
import { DeviceEventEmitter } from 'react-native';
componentWillMount() {
DeviceEventEmitter.addListener('rnTransMisson', (e: Event) => {
this.refs.toast.show("DeviceEventEmitter收到訊息:" + "\n" + "年齡:" + msg.age + "時間:" + msg.time, 2000);
});
}
NativeModules.TransMissonMoudle.contactWithRTC("getTime");
2.Callback 回撥方式
原生模組還支援一種特殊的引數——回撥函式。它提供了一個函式來把返回值傳回給 JavaScript。
原生模組通常只應呼叫回撥函式一次。但是,它可以儲存 callback 並在將來呼叫。
請務必注意 callback 並非在對應的原生函式返回後立即被執行——注意跨語言通訊是非同步的,這個執行過程會通過訊息迴圈來進行。
/**
* 2.CallBack方式
* @param type
* @param callback
*/
@ReactMethod
public void contactWithCallBack(String type, Callback callback) {
switch (type){
case "getTime":
callback.invoke(getTimeMillis());
break;
default:
break;
}
}
這個函式可以在 JavaScript 裡這樣使用:
NativeModules.TransMissonMoudle.contactWithCallBack("getTime",
(msg) => {
this.refs.toast.show("CallBack收到訊息:" + "\n" + msg, 2000);
}
);
3.Promise
原生模組還可以使用 promise 來簡化程式碼,搭配 ES2016(ES7)標準的async/await
語法則效果更佳。如果橋接原生方法的最後一個引數是一個Promise
,則對應的 JS 方法就會返回一個 Promise 物件。
我們把上面的程式碼用 promise 來代替回撥進行重構:
/**
* 3.Promise方式
* @param type
* @param promise
*/
@ReactMethod
public void contactWithPromise(String type, Promise promise) {
switch (type) {
case "getTime":
WritableMap writableMap=new WritableNativeMap();
writableMap.putString("age","30");
writableMap.putString("time",getTimeMillis());
promise.resolve(writableMap);
break;
default:
break;
}
}
現在 JavaScript 端的方法會返回一個 Promise。這樣你就可以在一個聲明瞭async
的非同步函式內使用await
關鍵字來呼叫,並等待其結果返回。(雖然這樣寫著看起來像同步操作,但實際仍然是非同步的,並不會阻塞執行來等待)。
NativeModules.TransMissonMoudle.contactWithPromise("getTime")
.then(msg=> {
this.refs.toast.show("Promise收到訊息:" + "\n" + "年齡:" + msg.age + "時間:" + msg.time, 2000);
}).catch(error=> {
console.log(error);
});
至此,三種通訊方式已經介紹完了。
按照慣例,上原始碼。