React Native 與 Android 混編項目覆盤反思

最近偷懶了一段時間(折騰了下小程序、跨平臺開發框架Weex / React Native / Flutter),到現在接入RN混合開發也已經七八個月的時間了,今天針對RN在Android混編項目做個整體的覆盤反思。以下內容是之前總結,本次不再重複記錄。

  1. RN與Android原生的消息通信
  2. RN使用Android原生控件或自定義組件
  3. RN加載Gif圖片及手動修改React Native端口號
  4. 將RN項目打包成離線包
  5. React Native Linking與 Android原生頁面路由跳轉實現
  6. Android項目集成RN混淆打包問題

本次記錄不作爲使用指南記錄,因此不再描述項目初始化等相關方面,需要的可以參考上面鏈接,這裏主要站在我們業務選擇和使用RN的角度來整體上分析跨平臺RN開發的優缺點,及對React Native與 Android 混編項目簡單做下覆盤反思。

一、跨平臺RN開發的優缺點:

  1. 優點:

    • 提高研發和測試效率
    • 各終端邏輯保持統一

    之前Android、iOS、M同一需求需三端開發人員各自去實現,以各自對需求的理解、各自的實現方式去碼不同的代碼,各自的邏輯和展示也可能就各不相同,很難保證功能一致性,自然測試人員需要針對三端各自測試,邏輯bug產生相對比較零散,各端各不相同,驗證也相對比較耗時耗力,採用RN同一功能,同一套代碼邏輯方式,測試起來發現邏輯bug相對統一,一些平臺差異性兼容性bug除外,大大提高測試效率,同時各終端業務邏輯也相對統一,提高了研發效率。

  2. 缺點:
    因平臺差異性可能會存在一些兼容性問題,如果想在前期就能抹平各端差異性問題及解決各平臺兼容型問題,就需要RN開發人員熟悉Android、IOS、Web三端開發技術,對技術廣度稍微有點高。

二、React Native在Android混編項目中的頁面跳轉和方法調用


大致通過上面這張簡圖來描述下:

  1. 頁面跳轉(RN與Android原生)
    調查背景:在設計與RN交互時,並不是僅站在Android一端的角度去設計,而是考慮通用型,儘量用RN本身特性去抹平差異性,避免RN在代碼層面進行差異化處理,比如說頁面跳轉處理:

    • Android的頁面跳轉是通過Intent跳轉
    • RN是通過路由(M版也通過路由跳轉)

    如果保持各自特性,則兩者直接頁面互相跳轉就需要原生藉助JS暴露接口給RN來實現了,這樣的話RN就需要根據終端環境進行差異化處理,爲了避免RN在代碼層面進行差異化處理,儘量尋找統一性方案確保整個項目的統一,通過調查發現RN提供的Linking方式進行跳轉,那麼就有兩個問題,RN怎麼拿?原生怎麼傳?然後通過源碼發現RN分別針對Android和IOS進行了封裝映射,我們只需要把數據傳送到對應的位置即可,

    const LinkingManager =
        Platform.OS === 'android'
        ? NativeModules.IntentAndroid : NativeModules.LinkingManager;
    

    在Android中對應的是IntentAndroid,查看對應的源碼:

    /**
     * Return the URL the activity was started with
     *
    * @param promise a promise which is resolved with the initial URL
    */
     @ReactMethod
     public void getInitialURL(Promise promise) {
       try {
         Activity currentActivity = getCurrentActivity();
         String initialURL = null;
    
         if (currentActivity != null) {
           Intent intent = currentActivity.getIntent();
           String action = intent.getAction();
           Uri uri = intent.getData();
    
           if (Intent.ACTION_VIEW.equals(action) && uri != null) {
             initialURL = uri.toString();
           }
         }
    
         promise.resolve(initialURL);
       } catch (Exception e) {
         promise.reject(new JSApplicationIllegalArgumentException(
             "Could not get the initial URL : " + e.getMessage()));
       }
     }
    

    通過上面源碼也就瞭解了Android與RN如何根據Linking進行頁面跳轉,我先按照這種形式在Android上進行測試發現可行,當然也有bug,就是拿的時候可能爲空,調查發現RN與原生頁面裝載之前就調用這個方法導致拿不到上下文,只要創建rootview就會執行rn的生命週期,而此時rN與原生還沒有綁定,後來發現RN是能監聽到原生頁面的活動狀態,此時再去獲取路由即可。詳細內容可參考React Native Linking與 Android原生頁面路由跳轉實現

  2. 方法調用
    RN通信原理簡單地講就是,一方native(java)將其部分方法註冊成一個映射表,另一方(js)再在這個映射表中查找並調用相應的方法,而Bridge擔當兩者間橋接的角色。
    其實方法調用大致分爲2種情況:

    • Android主動向JS端傳遞事件、數據
    • JS端主動向Android詢問獲取事件、數據

    RN調用Android需要module名和方法名相同,而Android調用RN只需要方法名相同。
    (1)RCTDeviceEventEmitter 事件方式
    ​ 優點:可任意時刻傳遞,Native主導控制。
    (2)Callback 回調方式
    ​ 優點:JS調用,Native返回。
    ​ 缺點:CallBack爲異步操作,返回時機不確定
    (3)Promise
    ​ 優點:JS調用,Native返回。
    ​ 缺點:每次使用需要JS調用一次
    (4)直傳常量數據(原生向RN)
    ​ 跨域傳值,只能從原生端向RN端傳遞。RN端可通過 NativeModules.[module名].[參數名] 的方式獲取。

    注意:RN層調用Native層進行界面跳轉時,需要設置FLAG_ACTIVITY_NEW_TASK標誌。

例如:下面是RCTDeviceEventEmitter事件的簡單事例,稍後封裝下更方便與原生的通信交互。

public class EventEmitter {
    private static final String TAG = "EventEmitter";
    // 在ReactPackage中的createNativeModules()初始化,RNEventEmitter.setReactContext(reactContext);
    private static ReactApplicationContext mReactContext;

    public static void setReactContext(ReactApplicationContext mReactContext) {
        RNEventEmitter.mReactContext = mReactContext;
    }
    /**
     * 顯示RN中loading
     * @param data show:顯示,false:隱藏
     */
    public static void showLoading(boolean show) {
        nativeCallRn("showloading", show);
    }
    public static void nativeCallRn(String eventName, Object msg) {
        if (mReactContext == null) {
            Log.e(TAG, "ReactContext is null");
            return;
        }
      mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName,msg);
    }
}

RN中接收原生消息:

 /**
    * 接收原生調用
    */
   componentDidMount() {
       DeviceEventEmitter.addListener('showLoading',(msg)=>{
            ToastAndroid.show("發送成功"+msg, ToastAndroid.SHORT);
       })
     //通過DeviceEventEmitter註冊監聽,類似於Android中的監聽事件。第一個參數標識名稱,要與Module中emit的Event Name相同。第二個參數即爲處理回調時間。
   }

三、Andorid 與 RN 傳參數據類型映射關係:

Android React Native 備註
Boolean Bool
Integer Number
Double Number
Float Number
String String
Callback function
ReadableMap / WritableMap Object RN中的對象
ReadableArray / WritableArray Array

觀察着8種參數類型發現,其中有ReadableMapReadableArray類型,對應JavaScriptObjectArray。而在Java原生中,可以發現facebook定義了ReadableArrayReadableMap接口,一層一層找一下,找到了WritableArrayWritableMap接口,以及實現了他們的WritableNativeArrayWritableNativeMap,我嘗試利用WritableNativeArray push了幾個參數,成功的傳遞到了參數:

//Android
    @ReactMethod
    public void show(Callback callback) {
        WritableArray writableArray = new WritableNativeArray();
        writableArray.pushString("one");
        writableArray.pushString("two");
        writableArray.pushString("three");
        callback.invoke(writableArray);
    }
    
//React Native
    MyTest.show((result)=> {
                ToastAndroid.show("結果是:" + result[2], ToastAndroid.SHORT);
            }
        );

//Android
    @ReactMethod
    public void show(Callback callback) {
        WritableMap writableMap = new WritableNativeMap();
        writableMap.putString("1", "first");
        writableMap.putString("2", "second");
        writableMap.putString("3", "third");
        callback.invoke(writableMap);
    }
    
//React Native
    MyTest.show((result)=> {
                ToastAndroid.show("結果是:" + result["2"], ToastAndroid.SHORT);
            }
        );

備註: 如何更好地將RN與原生進行參數傳遞呢?雖說上面的映射關係可以讓我們去準確的傳遞參數,但是比如說在Android原生中ReadableMap / WritableMap對應着RN中的 Object, 但是我們原生裏面並不會全局使用ReadableMap / WritableMap來替換現有的Map或者HashMap吧,爲儘量避免RN的侵入型,我們需要Native Module層進行抽象封裝處理,將RN中的數據類型與Android原生的數據類型進行互轉,稍後會整理相關轉化工具類,方便Android與原生快速交互。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章