1. 程式人生 > >ReactNative與原生Android通訊互動

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);
    });

至此,三種通訊方式已經介紹完了。

按照慣例,上原始碼。