摘要:以上就是通信交互整個流程,但是在實際業務開發中我們發現,RN提供的基礎組件已經不能滿足我們的業務開發,部分需要依賴native原生功能來實現比如: 模塊間的跳轉、分享等,這就需要我們與native底層約定一些交互方法來滿足各種各樣的業務場景,基於此前端封裝了一箇中間交互的協議層。1. 首先來說現在業內對於React Native的項目更新還是個很大的問題,58車商通現在使用的版本是0.53,如果我們想升級RN那必須發版,並且基礎庫以及各個業務模塊可能都會受到影響。

導語

本文從RN的簡介到在車商通落地,從宏觀設計到深層次分析其通信原理,再到熱更新的進階設計三個方面,闡述了React Native在車商通中的設計以及流程

背景

58車商通

58車商 通是爲車商打造發佈及收車賣車、CRM管理、商機線索、庫存管理、全網同步、營銷推廣功能等全方位SAAS服務的移動端APP。

主要功能包括:

1. 發佈車源(核心功能)幫助車商快速發佈車源,和主App 58同城起到相輔相成的作用。 需求變化比較頻繁,開始採用原生開發,但是無法滿足需求的頻繁變化,於是2017年改版爲Hybrid。 後續考慮改版爲RN     

2. 庫存管理(核心功能)幫助車商高效的管理自己的車輛,包括已經發布的,已經售出的,已經下架的,等等,以及定價,預警等相關功能。 需求變化相對穩定。

3. 客戶管理(核心功能)幫助車商管理自己的客戶,區分出客戶的購買意向程度,以及後續溝通等等。 變化頻率相對穩定

4. 營銷推廣(核心功能)幫助車商推廣車輛,包括置頂,刷新服務等,變化頻率相對較高

總結: 58車商通是58集團二手車部門的一個重要的App產物,幫助車商方便管理自己的車源,隨時發佈,同步其他市場。 而 RN 模塊第一次嘗試放在了車商通的每日任務模塊。

爲什麼會選中每日任務模塊

每日任務模塊: 用戶每日登陸之後可以通過每日的任務來賺取積分。 用於推廣等功能等。

那我們爲什麼要用每日任務模塊來做呢。

首先, 每日任務模塊體量較輕,入手起來相對比較簡單

其次, 線上有成熟的h5,如果出現嚴重問題的話,我們可以及時切換到線上h5進行補救

然後, 每日任務模塊雖然量輕,但是可以覆蓋協議的大部分功能。

最後就是這個模塊需求變化相對來說比較快,也可以檢驗熱更新模塊。

接下來我們來聊聊RN。

引言

開發已經經歷了幾個階段,從Native App 到 WebApp大火,再到蘋果公司禁Web,又發展到了Hybrid的Web與原生共生。 再到React Native,這種利用Js 轉成原生的取中方案,本文將就58車商通介紹下RN在其中的應用場景,以及發展階段。

RN特點

Learn Once,Write AnyWhere.

如下圖: 我們可以清楚的看到RN是構建在React和JSX的基礎上的。

使用RN的優點及不足

一個新的挑戰: 如何將開發成本和用戶體檢做好更好的平衡呢?

上面已經說了簡單說了一些RN開發的背景,接下來簡單介紹下RN的特性及優缺點

RN特性:

a) 提供了原生控件支持

使用RN可以使用底層原生控件,iOS可以使用UITabBar、   

UINavigationController等標準的iOS平臺組件; 在Android平臺我們可以使用   

Drawer控件; 這樣,就讓我們的App從使用上和視覺上擁有像原生App一樣的驗

b) 異步執行

所有的JavaScript邏輯與原生平臺之間的所有操作都採用異步執行模式,原生模

塊使用額外線程

c) 觸屏處理

RN引入了一個類似於iOS上Responder Chain響應鏈事件處理機制的響應體系,  

並基於此爲開發者提供了諸如TouchableHighlight等更高級的組件,實現了高性 

能的圖層點擊與接觸處理

但是,說到底RN畢竟也是前端基於JSCore通過Runtime機制或者反射機制來和Native端進行交互,所以RN還是有缺點存在的

RN缺點以及不足:

1. 首先來說現在業內對於React Native的項目更新還是個很大的問題,58車商通現在使用的版本是0.53,如果我們想升級RN那必須發版,並且基礎庫以及各個業務模塊可能都會受到影響。

2. React Native 的列表性能較差(不是滑動效果,是內存佔用方面),如果cell 很多,那容易崩潰,原因是RN的list控件不像Native 端有重用機制,不過現在業內借鑑native的重用機制的思路也有解決方案,也是目前我們的一個優化方向

3. React Native 的調用基於JSCore 和 Runtime的消息轉發機制,所以調起Native的組件,仍然是有一定的延遲,這個也是RN的一個瓶頸或者說上限,這也是爲什麼大家會說RN的效率是非常接近原生,但是遠遠高於WebApp的原因。

4. React Native 和Native 端是異步調用,所以有些操作必須要屏蔽用戶操作行爲,不過這個問題目前帶來的問題影響很小 

其實,綜上來說,React Native 的開發總體上還是利遠大於弊,而且,熱更新對於App的Native開發,尤其是 iOS開發來說,價值很大,所以我們選擇落地RN項目,並實際運用到車商通中來。

58車商通RN模塊整體技術架構

車商通RN整體分爲三個部分:

- 客戶端: 提供部分基礎組件、提供交互協議、支持UI渲染展示頁面

- 服務端: 提供業務接口、提供項目bundle下載地址

- 熱更新平臺: 提供項目bundle文件、支持更新、回滾

58車商通客戶端設計和開發

客戶端整體框架如下:

1. 入口組件

每個應用程序都有一個對應的入口文件,前端頁面的開發項目目錄如下:

index.js是Android和iOS渲染前端UI的統一入口

import { AppRegistry } from "react-native";

import App from "./src/index"; //業務口

AppRegistry.registerComponent("入口名字", () => App);

把當前APP的前端UI對象註冊到AppRegistry組件中;

- AppRegistry 是運行所有 React Native 應用程序的 JS 入口點

- 應用程序入口組件需要通過 AppRegistry.registerComponent 來註冊它們自身

- 當註冊完應用程序組件後,Native就會加載jsbundle文件並觸發 AppRegistry.runApplication運行應用

2. RN通信機制

這部分內容,其實網上的資料比較多,而我們結合源碼,和已經一些已經公開的知識點,簡單跟大家分享一下。 後續如果大家感興趣,可以在單拿出一篇文章來分析討論。 下面我們來簡單看下RN從JS 端開始調起原生方法的原理(以OC爲例):

OC生成一張模塊配置表,包含所有模塊和模塊裏的方法,根據特定的標識宏(RCT_EXPORT_MODULE()),將可以暴露的方法暴露給JS。

OC-JS交互流程: (注: 此圖是網上的示意圖,但是表達的意思很明確,所以借用一下)

1. js調用OC模塊暴露出來的方法

2. 把調用方法分解爲ModuleName、MethodName、arguments,再丟給 MessageQueue處理

3. 把js的callback函數緩存在MessageQueue的一個成員變量裏面,同時生成一 個CallbackID來代表callback; 在通過保存在MessageQueue的模塊配置表 把ModuleName、MethodName轉成ModuleID、MethodID

4. 把ModuleID、MethodID、CallbackID和其他參數傳給OC JavaScriptCore)

5. OC接到消息,通過模塊配置表拿到對於的模塊和方法

6. RCTModuleMethod對js傳過來的參數進行處理

7. OC模塊方法執行完,執行block回調

8. 調用第6步中RCTModuleMethod生成的block

9. block帶着CallbackID和block傳過來的參數去掉用js裏的MessageQueue方法 invokeCallbackAndReturnFlushedQueue

10. MessageQueue通過CallbackID找到相應的js的callback方法

11. 調用callback方法,並把OC帶過來的參數一起傳過去完成回調

以上就是通信交互整個流程,但是在實際業務開發中我們發現,RN提供的基礎組件已經不能滿足我們的業務開發,部分需要依賴native原生功能來實現比如: 模塊間的跳轉、分享等,這就需要我們與native底層約定一些交互方法來滿足各種各樣的業務場景,基於此前端封裝了一箇中間交互的協議層。

3. 58車商通RN交互協議設計

協議層是native和前端對NativeModules進行了一些約定的封裝和處理,方便業務使用。

目前前端協議層通過一下幾種類型類集中封裝的:

RN跳轉類:

- 調起Native組件類 如: loading、toast等

- 調起native功能類 如: 分享、埋點、定位等

協議約定:

1. native封裝模塊宏CSTRNComponent到NativeModules下

2. 在模塊宏CSTRNComponent下聲明函數宏CSTRNHandler

3. 函數固定傳參兩個,第一個是協議交互的所有參數param(jsonString類型),第

二個固定傳入callback函數

4. 參數param格式

param = { action, params };

也是固定兩個參數

action:唯一確定調起的native組件,都是以CST開頭的 如:action = "CSTLoadPageRNWeb"表示RN跳轉H5

params:協議交互參數,每個協議都有不同的參數,具體協議的參數和native約定 如:

跳轉H5協議傳參

let params = {

url, //h5鏈接

jumpParameter, //參數

isDestoryBeforePage, //當前頁面是否銷燬關閉

title, //頁面titl

...other

};

5. 回調函數

CSTRNHandler的第二個參數就是協議的回調函數,傳入function類型

固定接收兩個參數

(error, event) => {

第一個參數error是一個錯誤對象(沒有發生錯誤的時候爲 null

第二個參數event是native返回給前端的具體回調數據

(jsonString類型)

}

6. 前端封裝

根據以上約定規則,下面來看一下前端具體的協議封裝,以跳轉H5頁面爲例

const nativeBridge = NativeModules.CSTRNComponent.CSTRNHandler;

function loadPage(

{ url, isDestoryBeforePage = 0, jumpParameter, title, ...other },

disappearCallBack = (error, nativeData) => {}, //回調函數

action = "CSTLoadPageRNWeb"

) {

let params = {

url, //h5鏈接

jumpParameter, //參數

isDestoryBeforePage, //當前頁面是否銷燬關閉

title, //頁面title

...other

};

const error = checker(

arguments,

[

{

url: "s|r",

jumpParameter: "o",

isDestoryBeforePage: "n",

title: "s"

},"f","s"

],"loadPage"

);

// 線上環境報錯不調起 Native 的 API

if (error === "error") {

return error;

}

let paramToNative = { action, params };

nativeBridge(JSON.stringify(paramToNative), disappearCallBack);

4.58車商通-RN UI頁面開發

在上面我們大概瞭解了RN APP中的啓動流程和交互方式,那麼如何應用要我們的日常開發中能,下面來介紹一下前端的UI的開發。

1、本地開發流程

爲保證RN版本的匹配,和一些代碼規範的統一,在開發自己項目時需要克隆RN種子工程。 在開發過程中需要使用到本地調試,下面我們看看iOS端調試。

2、本地調試

a) iOS模擬器啓動頁面:

b) 啓動Chrome瀏覽器調試:

command+D彈出模擬器工具類 選擇選項Debug Js Remotely

c) 瀏覽器自動打開鏈接:http://localhost:8081/debugger-ui/

這時能在瀏覽器上查看所有前端js代碼了

d) 斷點調試

打開目的js文件,找到要調試的函數,直接打斷點,當執行該函數時就直接斷點攔截了。

58車商通-RN落地服務端開發設計

服務端在整個RNAPP中承擔的角色,就是爲APP提供基本的數據接口服務和提供RN項目資源信息和下載地址;

此處就講一下RN項目下載更新流程。

服務端接口返回數據模型:

{

"respData": {

"h5Url": "",

"business": {

"version": "90",

"remoteUrl": "https://j1.58cdn.com.cn/escstatic/rn/apptest/10021/android/10021_90_android_business.tgz"

},

"resource": {

"version": "90",

"remoteUrl": "https://j1.58cdn.com.cn/escstatic/rn/apptest/10021/android/10021_90_android_assets.tgz"

},

"bundleId": "10021",

"unpacking": true,

"downNow": false

},

"respCode": 0

}

服務端流程如下:

58車商通-熱更新平臺設計

1. 熱更新流程

在傳統的web開發中,我們修改完js之後在瀏覽器上就能直接看到效果,JavaScript本身就是一門動態語言,並不需要編譯,瀏覽器每次刷新都拉取新的js文件; 針對web應用最簡單也最有效的優化就是緩存,當js沒有更新,瀏覽器就不需要下載新的js文件;

在RN實現動態更新也是同樣的思路,RN中前端JS代碼最終都會打包成jsbundle文件,我們在需求更新時,在應用中從遠程下載這個文件,並重新加載,就可以完成動態更新同時無需通過App Store重新發布;

APP RN資源更新流程:

1、 APP啓動時:

2、 啓動項目時:

所有前面的更新流程都會依賴於前端的RN資源,那麼下面我們來看一下如何把一個項目在平臺上錄入、編譯打包和上線的。

2. 平臺資源錄入

在項目開發完成,提交到公司Git代碼倉庫,就可以使用RN資源管理平臺錄入資源了;

平臺功能如下:

項目錄入:

在填寫完信息後,平臺會根據填寫的git地址,把當前項目代碼下載到服務器,並npm install安裝當前項目需要的所有依賴; 這個過程因爲需要安裝項目依賴,所以時間比較長,項目初始化完成就可以對項目編譯打包了;

信息錄入時,會爲每個項目分配一個唯一的ID,也是客戶端區分項目的唯一標識。

3. 58車商通-RN打包編譯流程

RN項目編譯打包流程:

打包產物jsbundle

打包之後JSBundle文件的結構,基本分爲3部分:

-  頭部: 全局定義,主要是define,require等全局模塊的定義;

-  中間: 模塊定義,RN框架和業務的各個模塊定義;

-  尾部: 引擎初始化和入口函數執行;

在RN打包過程中,解析依賴關係,爲每個模塊添加一個id:

1、需求

在實際的RN業務開發中,我們會涉及到很多個業務,這些業務基本上也不會耦合,這時我們就需要創建多個RN項目,每個項目編譯打包成獨立的bundle; 對於RN APP來說,即使只有一個helloworld頁面,在使用官方命令react-native bundle打出來的jsbundle文件大約爲530KB以上,RN依賴模塊本身就佔了99%以上; 如果更新的話,需要從網絡上拉取整個包下載時間長,還會海鷗飛用戶流量,每次進入RN頁面還都要執行RN基礎模塊的定義; 在RN項目開發中基礎庫react和react-native是不變的,我們可以抽離這兩個依賴打成common.bundle內置於APP中;

2、目標

- 抽離react和react-native打包成common.bundle

- 減小線上下發業務bundle體積,減少下載時間、節省用戶流量

- 可預加載common.bundle,提升打開頁面速度

3、分析

- 通過分析bundle結構和依賴查找,最終可以通過標記法進行分包

- 打包bundle時,根據entryFile進行深度遍歷依賴分析,模塊id不斷遞增,即越早引用的模塊,id越小

- 在分析依賴時標記哪些模塊屬於common.bundle,哪些模塊屬於業務bundle

- 打包時先引入base.js保證common.bundle的模塊id都在前面,先收集common.bundle的模塊

- 在遍歷進行依賴收集,輸出業務bundle

//base.js

import React, { Component } from "react";

import {} from "react-native";

4. 拆分打包流程

編譯打包之後本地項目打包結果如下:

iOS端:

Android端:   

5. 小結

優點:

- 一次性打包輸出common.bundle和業務bundle,效率高

- 用戶只需下載業務bundle,減少流量消耗和下載時間

缺點:

- 直接引用react-native作爲基礎,common中可能會引入一些用不到的模塊

總體上利大於弊,開發中也不可預知需要用到react-native哪些模塊,直接打包一個全集也未嘗不可。

6. 項目提測、上線

項目提測流程:

項目上線流程:

7. 項目回滾

在日常開發中,縱然有多輪測試,也避免不了發佈到線上不會存在問題,我們傳統的解決方案就是定位問題,找出原因,解決完之後重新發布上線; 如果是一個流量很大的需求,同時又出現了線上不容易解決的問題,此時線上就會出現長時間的功能無法使用的情況; 這是我們就可以考慮到回滾,先把項目回滾到一個可用的版本,然後再來解決自己的問題。

RN項目回滾流程如下:

8. 展望

理論上我們還可以指定規則,解耦業務,根據不同的業務模塊,劃分出更多的bundle,每個bundle的模塊id按照某個值開始,避免重複,類似android插件化處理資源id策略,按需加載業務bundle。

採坑

俗話說工欲善其事,必現踩其坑,下面我們分享一下我們在實踐過程中遇到的坑:

1. 統一封裝RN Fetch請求時android底層報錯 

作爲前端開發人員,網絡請求工具對大家來說肯定不陌生。 iOS的AFNetworking,Android的okHttp等。 但是對於RN來說,我們最常用到的就是js原生的Fetch請求了。

React Native提供了和web標準一致的Fetch API,用於滿足開發者訪問網絡的需求。

錯誤原因:  

在封裝RN fetch請求時header進行了統一設置設置如下:

在實際請求時android底層拋異常 如下:

查看RN Android底層源碼看到 RN的fetch請求在Android底層Content-Type只支持multipart/form-data類型。

但是iOS底層支持application/json、text/plain、application/x-www-form-urlencoded類型; 不支持multipart/form-data。

解決方案:

在封裝是需要分端去指定當前請求頭,android端headers中Content-Type只能是multipart/form-data

2. Android端Fetch POST請求無參數時,報異常

錯誤原因:

前端統一封裝POST請求如下:

如果POST請求無需傳參 此時FormData如下:

FormData {_parts: Array(0)}

Android底層會報錯,錯誤如下:  

解決方案:  

前端兼容POST參數,如果POST請求無需傳參 也就是params爲空時,前端拼接一個空的part,part的key、value值都置爲空。

3. RN頁面數據注入問題

3.1、頁面props注入

iOS端:

通過RCTRootView的初始化函數你可以將任意屬性傳遞給React Native應用。 參數initialProperties必須是NSDictionary的一個實例。 這一字典參數會在內部被轉化爲一個可供JS組件調用的JSON對象。

android端:

android底層目前無法實現參數注入的形式。

在實際業務開發中也無法使用此方面來實現RN頁面的數據注入。

3.2、使用消息監聽的方式

iOS端:

前端監聽:

OC端需要自定義實現消息監聽模塊,在前端首次調用消息監聽時OC端去註冊監聽事件,此時不會執行前端監聽回調,需要前端再次調用監聽事件。

android端:

前端監聽:

android端消息監聽使用的是RN封裝的DeviceEventEmitter消息監聽事件,可以在android端直接通過sendEvent方法發送廣播消息,前端監聽接收,實現消息的收發。

消息監聽的方式整體的是可行的方案,但是android端和iOS端消息監聽的實現差異太大,無法做到兩端統一,此方案也不是可選的最優方案。

3.3、事件交互獲取數據

最終我們的實現方案是前端和native端定義雙方交互事件,通過native事件通信的方式去獲取RN頁面想要的數據。

4. iOS 端內存不釋

在測試過程中我們發現,iOS端在數次退出進入RN模塊後會崩潰,糾其原因是

bridge沒有釋放,需要我們釋放一下

總結

本文總結了RN在58車商通的應用場景以及整體設計流程,還包括熱更新的幾個階段設計,從業務和技術兩個方面進行了闡述。 後續會持續的更新RN系列。 如果後續同學們想研究下RN的底層實現,我們可以在發一篇文章共大家參考。

經過大家的共同努力,我們終於在車商通3.7.3期順利上線了兩個RN模塊,但是我們也還有很多優化的空間。 後續我們也會持續跟進。 目前思路是從以下幾個方面來繼續優化

1. 前端工作,優化列表控件。 目前的列表性能還不是特別好,還需要下一步繼續優化

2. 熱更新繼續深入優化,最終做到增量更新(任重而道遠)。

3. 優化首屏加載白屏的問題,需要native 端與前端一起來做。

4. native 內部代碼結構還需要進一步調整,重構代碼。

由於我們的RN 項目是先在公司的主站RN項目開源前落地並開發完成的,目前階段,我們還沒有做更深一步的差量化更新(以補丁包的形式下發更新),所以,深度上講我們目前沒有涉及更深層次。 如今,公司主站App,更加成熟的RN體系已經開源,我們可以借鑑或者應用更加成熟的RN體系到應用的到項目中,以提高App的開發效率,縮短更新流程,降低更新風險

參考文章

1. ReactNative中文網

2. ReactNative GitHub源碼

3. Fetch API

作者簡介

桑銳/58同城/ iOS高級開發工程師

龔成輝 /58同城/   前端高級開發工程師

閱讀推薦

1.  58二手車在移動平臺上的AI技術探索

2. 深度|數據智能在二手車業務場景中的探索與沉澱——關於用戶流量的預測與識別

3.  機器學習在58二手車估價系統實踐

相關文章