1. 程式人生 > >React Native與Android通訊互動

React Native與Android通訊互動

剛建立的React Native 微信公眾號,歡迎微信掃描關注訂閱號,每天定期會分享react native 技術文章,移動技術乾貨,精彩文章技術推送。同時可以掃描我的微信加入react-native技術交流微信群。歡迎各位大牛,React Native技術愛好者加入交流!


在前兩篇的內容中,和大家分享了Android原生整合RN,以及RN的增量熱更新。關於詳細的內容,點選如下具體瞭解:

本篇內容同樣和React Native 與 原生App有關,可以說更加深入了兩者之間的感情,為培養下一代做出準備:React Native與原生App的通訊互動。

Android系統為我們提供了webview來載入網頁,為了讓webview載入的網頁可以與App互動,系統提供了一套機制幫助我們更方便的實現通訊。同樣為了實現React Native與原生App之間的通訊,FB也實現了自己的一套互動機制。

(1)RCTDeviceEventEmitter 事件方式

(2)Callback 回撥方式

(3)Promise

(4)直傳常量資料(原生向RN)

三種方式各具不同優缺點

1. RCTDeviceEventEmitter

   優點:可任意時刻傳遞,Native主導控制。

2. Callback

   優點:JS呼叫,Native返回。

   缺點:CallBack為非同步操作,返回時機不確定

3. Promise

   優點:JS呼叫,Native返回。

   缺點:每次使用需要JS呼叫一次

瞭解了三者的通訊方式,怎麼能少了程式碼的描述!我們來看看程式碼如何實現。大致的實現步驟如下:

(1)定義Module類,繼承ReactContextBaseJavaModule

         在Module類中,我們定義互動的方法,例如RN呼叫Native的方法,Native呼叫RN的方法等。

(2)定義Package類,繼承ReactPackage

         實現Package的createNativeModules方法,將Module例項新增到集合。

(3)定義Application,繼承ReactApplication

         實現getPackages方法將Package例項新增到getPackages下的集合。

4. 直傳常量資料(原生向RN)

         跨域傳值,只能從原生端向RN端傳遞。RN端可通過 NativeModules.[module名].[引數名] 

的方式獲取

1.Module類中的核心程式碼

    /**
     * 在rn程式碼裡面是需要這個名字來呼叫該類的方法
     * @return
     */
    @Override
    public String getName() {
        return MODULE_NAME;
    }

 名稱可以自定義,對接時協商好即可。

    /**
     * RN呼叫Native的方法
     * @param phone
     */
    @ReactMethod
    public void rnCallNative(String phone) {

        // 跳轉到打電話介面
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:" + phone));
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 跳轉需要新增flag, 否則報錯
        mContext.startActivity(intent);
    }

  在module中定義一個方法,並用@ReactMethod 註解標註:表明該方法會被RN呼叫。即被RN呼叫的原生方法必須使用@ReactMethod註解標註。

  注意:RN層呼叫Native層進行介面跳轉時,需要設定FLAG_ACTIVITY_NEW_TASK標誌,否則會出現如下錯誤:

 

    /**
     * Native呼叫RN
     * @param msg
     */
    public void nativeCallRn(String msg) {
        mContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(EVENT_NAME,msg);
    }

  上面程式碼定義了原生方法,通過在Android層呼叫RN層。使用ReactContext的getJSModule方法,emit來發送訊息。同樣,emit的第一個引數要與RN層中addListener方法的第一個引數相同。

2.自定義Package的核心程式碼

/**
 * 通訊Package類
 * Created by Song on 2017/2/17.
 */
public class CommPackage implements ReactPackage {

    public CommModule mModule;

    /**
     * 建立Native Module
     * @param reactContext
     * @return
     */
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        mModule = new CommModule(reactContext);
        modules.add(mModule);
        return modules;
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

 在createNativeModules方法中,初始化集合,並將module例項新增進集合,返回集合例項。

3.Application核心程式碼

   private static final CommPackage mCommPackage = new CommPackage();
   /**
     * 獲取 reactPackage
     * @return
     */
    public static CommPackage getReactPackage() {
        return mCommPackage;
    }

在getPackages方法中,將Package例項新增到Arrays中即可完成註冊。以上就是Android層核心程式碼配置,繼續來看React Native層核心程式碼:

1.呼叫原生程式碼

   /**
    * 呼叫原生程式碼
    */
    skipNativeCall() {
       let phone = '18637070949';
       NativeModules.commModule.rnCallNative(phone);
    }

在React Native層,通過NativeModules呼叫commModule,繼而呼叫原生方法即可。注意:commModule要與Module類的getNames方法返回的名稱對應。

2. 接收原生呼叫

   /**
    * 接收原生呼叫
    */
   componentDidMount() {
       DeviceEventEmitter.addListener('nativeCallRn',(msg)=>{
            title = "React Native介面,收到資料:" + msg;
            ToastAndroid.show("傳送成功", ToastAndroid.SHORT);
       })
   }

通過DeviceEventEmitter註冊監聽,類似於Android中的監聽事件。第一個引數標識名稱,要與Module中emit的Event Name相同。第二個引數即為處理回掉。

3.介面程式碼

 render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome} >
            {title}
        </Text>
         <Text style={styles.welcome} onPress={this.skipNativeCall.bind(this)}>
            跳轉到撥號介面
         </Text>
        
         <Image source={require('./images/ic.png')} />
      </View>
    );
  }

在Text中註冊單擊事件,RN層呼叫原生程式碼,跳轉到撥號介面。

4.Android層呼叫RN的程式碼

    /**
     * 向RN傳送訊息
     * @param v
     */
    public void sendMsgToRN(View v) {
        Log.e("---","sendMsgToRN");
        MainApplication.getReactPackage().mModule.nativeCallRn("hello");
    }

呼叫Module中定義的nativeCallRn方法,繼而出發RN層程式碼。以上就是通過 RCTDeviceEventEmitter 模式進行通訊互動。可以很清晰的看出,互動都是以主動方式為主。

RN中剩下的兩種通訊方式,存在一個共同的特點:

從RN層呼叫Native層,Native層處理完成後,回撥RN層

直接看程式碼實現:

(1)Callback

同樣還是在Module類中定義互動方法:

    /**
     * Callback 方式
     * rn呼叫Native,並獲取返回值
     * @param msg
     * @param callback
     */
    @ReactMethod
    public void rnCallNativeFromCallback(String msg, Callback callback) {

        // 1.處理業務邏輯...
        String result = "處理結果:" + msg;
        // 2.回撥RN,即將處理結果返回給RN
        callback.invoke(result);
    }

RN中定義回撥:

   /**
    * Callback 通訊方式
    */
    callbackComm(msg) {
        NativeModules.commModule.rnCallNativeFromCallback(msg,(result) => {
             ToastAndroid.show("CallBack收到訊息:" + result, ToastAndroid.SHORT);
        })
    }

(2)Promise

Module類中定義互動方法:

    /**
     * Promise 方式
     * @param msg
     * @param promise
     */
    @ReactMethod
    public void rnCallNativeFromPromise(String msg, Promise promise) {

        Log.e("---","adasdasda");
        // 1.處理業務邏輯...
        String result = "處理結果:" + msg;
        // 2.回撥RN,即將處理結果返回給RN
        promise.resolve(result);
    }

 RN中定義回撥:

    /**
     * Promise 通訊方式
     */
    promiseComm(msg) {
        NativeModules.commModule.rnCallNativeFromPromise(msg).then(
            (result) =>{
                ToastAndroid.show("Promise收到訊息:" + result, ToastAndroid.SHORT)
            }
        ).catch((error) =>{console.log(error)});
    }

佈局中觸發單擊事件:

 <Text style={styles.welcome} onPress={this.skipNativeCall.bind(this)}>
            跳轉到撥號介面
         </Text>
         <Text style={styles.welcome} onPress={this.callbackComm.bind(this,'callback傳送啦')}>
            Callback通訊方式
         </Text>
         <Text style={styles.welcome} onPress={this.promiseComm.bind(this,'promise傳送啦')}>
            Promise通訊方式
         </Text>

(3)直傳常量資料(原生向RN)

自定義Module類中實現getConstants方法

    @Nullable
    @Override
    public Map<String, Object> getConstants() {
        return super.getConstants();
    }
以上是預設實現,getConstants方法中呼叫了super.getConstants(),跟蹤原始碼可以看到
  /**
   * @return a map of constants this module exports to JS. Supports JSON types.
   */
  public @Nullable Map<String, Object> getConstants() {
    return null;
  }
從原始碼註釋中可以看出,該方法是返回一個Map型別的常量,匯出到JS端(即RN)。支援JSON 型別。所以,我們只需要重寫方法,宣告Map集合,向其中新增常量後,返回即可。所以就有了如下程式碼
    @Nullable
    @Override
    public Map<String, Object> getConstants() {
        Map<String,Object> params = new HashMap<>();
        params.put("Constant","我是常量,傳遞給RN");
        return params;
    }
此時,在RN端,我們可以通過NativeModules來接收即可
componentWillMount() {
	let result = NativeModules.MyModule.Constant
}



最終效果:

  Android呼叫React Native:

 

  React Native呼叫Android:

 

  原始碼以上傳到github,希望大家star,follow支援哦~

  原始碼下載