React Native在iOS界早就炒的火熱了,隨着2015年末Android端推出後,一套代碼能運行於雙平臺上,真正擁有了Hybrid框架的全部優點。再加上Native的優秀性能,讓愈來愈多的公司在實際項目中一探究竟。58同城APP發佈模塊年代久遠,一直計劃進行重構以適應日益苛刻的用戶體驗,這個需求與咱們在React Native上一探究竟的意願一碰撞,就產生了React Native在58APP的開發實踐。html
本文重點介紹的是實踐過程當中的技術架構和Native組建層以及熱更新平臺的基本狀況,以期能對React Native的從零到深刻有一個總體的把握。react
React Native是一項全新的技術,不一樣公司使用有不一樣的體驗,好壞衆說紛紜。基於此,必須根據自身的狀況進行摸底調研。58APP的調研過程從2015年6月就開始了,那時候android還沒推出,僅調研了iOS的相關狀況。真正的全面調研展開是在2016年3月開始的,由寧老闆親自點將並監督進度。整個過程持續到5月初結束。下面分三個階段介紹一下58APP調研的具體歷程。android
React Native確切的說從2015年開始在國內火起來的。牆外開花,牆內結果,國外技術研發,國內炒的火熱。阿里天貓在這一方面走的比較靠前,但這時候Android部分尚未推出,僅有iOS。應慶春分配的任務,我負責了這方面的調研。ios
當時是拿二手車的列表頁進行的試驗,主要測試用RN實現的列表頁和用Native實現的列表頁在性能上的差異,當時得出的調研結論以下:git
(1)集成React Native須要從iOS7.0開始,在7.0如下會因私有API問題在審覈過程當中被拒;github
(2)性能方面,經過對ListView的針對性分析,在數據量不大的狀況(50條左右),內存和CPU的差異在iphone4s以上的設備上能夠接受;當數據量比較多,好比試驗過程當中的150條,內存比較大,在低端設備(4s/5c)上隨着業務的擴展,性能會有瓶頸。web
(3)開發學習成本上,上手會比較快。但在開發的過程當中遇到一些複雜的業務邏輯,得基於現有的框架擴展組件;還有在崩潰的收集上會比較麻煩,只能定位到OC層的代碼,對於JS的運行時崩潰,目前的崩潰收集系統還沒法採集。redis
固然,ReactNative的理念是比較好的,既能擁有native的良好用戶體驗,又能具有web的快速發佈和迭代的功能。若是android後續能很好推出,還能實現跨平臺的一處編寫,多處運行的效果。不管集成與否,後續要持續關注,保持前沿技術的敏感性。對應ListView性能問題,RN官方一直沒有一個很好的解決方案,咱們最近也在作一些調研和組件的從新封裝,指望能從根本上解決這個問題。算法
在2015年末,React Native就推出了Android版本,而後就有不少公司在開始嘗試了。春節流量高峯一過,上面就在籌劃RN上開發嘗試的事了。大致方向是以APP中的發佈模塊作爲試點,而後咱們調研的技術偏向於發佈模塊的相關功能實現。調研由寧老闆點將,iOS 筆者負責,Android 萌陽負責,JS那端天翔負責,成立了調研三人組,每週彙報進度。3月份的調研主要面向的是RN基礎調研,摘取了其中的一些調研細節:react-native
(1)Android/iOS 如何將RN集成到當前項目中?
(2)如何用RN提供的原生組件實現發佈界面?
(3)寫完的JS如何打包給Native使用?
(4)因爲是集成到已有項目,如何處理項目中的統一導航和RN提供的導航?
(5)發佈表單中圖片區域如何處理?Native封裝的組件粒度如何?
(6)發佈頁面的UI是用ScrollView控制仍是ListView控制?
3月份的調研,在RN的應用層面作到了一個心中有數,爲後期的技術工做開展奠基了一個很好的基礎。至於基礎調研過程當中的問題,限於篇幅問題,就不一一展開敘述了,有興趣的同窗能夠私下交流。
熱更新調研是整個調研最最關鍵的一環,由於官方並無熱更新的成熟方案。整個4月份一直在進行熱更新的調研,直到5月8號結束。熱更新調研主要涉及的主題爲:
(1)熱更新Native端的流程?如何控制熱更新包的大小及內置的資源大小?
(2)Server端熱更新diff文件存儲方案及更新方案?
(3)Native端如何獲取文件的更新?
(4)異常回滾機制?
(5)是基於二進制算法的diff仍是基於文件算法的diff?
熱更新中涉及的細節真的不少,上面只是列出其中的一些。咱們的調研過程,也是內部一遍遍技術評審/修改/再評審的過程。在下一章節會對這裏提到的主要問題進行分析和解釋。
5月份PM已經陸續把需求整理完成了,而後成立了項目組,加入了發佈業務的FE及Server。項目代號爲「水滴」,無線FE同窗的創意。水滴,源自於三體,多維空間武器,經過量子糾纏進行超遠距離通信和控制。React Native如同水滴,對JS-Native通信和控制。另外,寓意水滴石穿,堅持不懈,終能成功!
首先從總體上了解一下基於RN的APP開發架構。架構共分爲五個部分:Native組件/API層、JS中間層、JS業務層、視圖載體頁、熱更新平臺。JS業務層、JS中間層、Native組件/API層三者運行於視圖載體頁中,且JS業務層和JS中間層的代碼更新是經過熱更新平臺更新到用戶手機應用中的。Native組件/API層是整個裝置的基石,JS業務層經過JS中間層調用Native組件與API。
Native組件/API層與JS中間層是無狀態,能夠被複用的,它們被不一樣業務調用和組裝,能造成不一樣的業務功能。在這裏,一切業務都是基於組件的,任何業務的造成,都是調用Native組件及API來的。尤爲是引入了JS中間層,不只抹平了在不一樣平臺(iOS/Android)上調用組件的差別性,還解耦了JS業務層與Native組件層。若是沒有JS中間層,Native一個組件或者API的變更,都須要通知全部的業務方去進行修改,在業務到達必定量的狀況下,這種改動不只費時費力還具備風險,會影響線上功能。引入了JS中間層以後,Native組件及API的變更,都在JS中間層進行處理,JS業務層毫無感知。
Native組件/API層是在整個架構的最底層,也是整個裝置的基礎。
在這一層,除了React Native自己提供的原生組件外,咱們還對沒有覆蓋到的組件進行了封裝。React Native提供的組件有Image、ListView、Picker、Text、TextInput、ScrollView等,具體可從React Native官方網站(http://facebook.github.io/react-native/docs/getting-started.html)上查詢。本發明擴展的組件有:支付、語音、彈窗、單選選擇器、多選無聯動選擇器、登陸等。
在React Native中,除了組件,還有API。官方提供的API有ClipBoard、AsyncStorage、AppRegistry、Alert等,更多完備的API可從ReactNative官方網站上查詢。本發明擴展的API有:跳轉、定位、埋點、初始化參數等。
這些擴展的組件和API使得用React Native,來實現本地化的業務成爲了可能,也是本發明重要的組成部分。固然隨着業務的逐步擴大,還會不斷豐富組件/API庫,以適應業務的特殊性和多樣性。具體自定義組件狀況以下圖:
以彈窗dialog組件爲例,native與js交互的協議爲:
JS中間層是很是關鍵的一層,是爲上文中擴展的Native組件/API來服務的。JS中間層如上文所述,不只能抹平在不一樣平臺上調用Native組件/API的差別,還解耦了JS業務層與Native組件/API層。
上圖所示的是在自定義彈窗組件(Dialog)中的代碼片斷,從代碼的95行到102行,所作的是處理iOS/Android兩個平臺上彈窗界面肯定按鈕放置的位置不一樣而作的差別化處理。相似這些平臺差別化的內容在實際開發中會有不少,若是這些差別都有業務方去作,不只代碼可複用性差,並且耗時耗力,每個新接入方都要從新開發調試。
至於JS業務層與Native組件/API層之間的耦合關係,能夠試想,若是沒有中間層的封裝,以上文的dialog組件爲例,在業務層中,將會執行下圖中所示的調用,其中WBCustomDialogManager是Native的組件標識別,還有show函數的相關參數。這些與native相關的內容若是發生變化,則全部與這個組件相關的業務都要更改。而引入了中間層以後,業務調用方,將再也不關注這些細節,中間層在其中作了解藕。即便native發生了變更,也會最大限度下降業務層的變更。
JS業務層主要專一了業務的實現,包括視圖的渲染,組件的串聯,UI樣式的設置,Server API接口的調用與數據的處理。JS業務層在改裝置中是最終代碼的落地,視圖載體頁加載的視圖以及熱更新系統更新的代碼都是直接針對JS業務層的,只是這時業務層引用了JS中間層的代碼來實現對Native組件的調用。
視圖載體頁在這裏扮演了很重要的角色,是全部業務的一個統一載體。以58同城APP爲例,裏面有大類頁/列表頁/詳情頁/發佈等不一樣形態的各類業務。通用的作法每個業務一個載體頁。由於載體頁是Native代碼寫的,這使得當須要擴展一個業務線的時候,必須依賴發版。而統一了載體頁後,只須要經過熱更新平臺將JS代碼更新到APP本地便可實現。
視圖載體頁單一載體功能的實現,很關鍵的部分在於跳轉到載體頁跳轉協議的設計。因爲跳轉協議與具體的業務關聯較大,咱們的跳轉協議中有一個重要的參數pagetype,在這裏咱們將pagetype設置爲RN,而不是list(列表頁)/detail(詳情頁)等與業務相關的類型。其中bundleid指向的是下文熱更新平臺將JS業務層及其引用的代碼編譯link好的jsbundle文件。
跳轉協議在不一樣的APP中,實現思路不一樣,有不少APP採用的url形式來實現,但具體思路與上文描述的JSON形式相同。
熱更新平臺是整個框架的核心。熱更新平臺的主要功能是將JS業務層及其引用的代碼編譯link好的jsbundle下載到native app中。在此過程當中,須要控制更新文件的大小以及失敗狀況的處理。
熱更新平臺涉及JSBundle資源管理系統(Server)、JSBundle數據接口層(Server)、JSBundle Native更新及管理層(Native)。JSBundle資源管理系統負責將相關JS業務層代碼編譯link成JSBundle文件,並將相關更新寫到一個數據緩存中心(例如redis或者memcached)。當Native經過JSBundle數據接口層提供的接口獲取對應的JSBundle的信息時,數據接口層將從數據緩存中心查詢數據並返回給native端。Native端爲了提升用戶體驗,會對JSBundle進行緩存,用戶訪問相關頁面的時候,先展現緩存,再訪問接口,看是否有更新。
熱更新這塊,在這裏我從另一個角度來闡述,即咱們爲何不用現有市面上的方案,而要本身搞一套。下面的三個章節來慢慢敘述。
熱更新如今公開的兩個方案是微軟的CodePush和React Native中文網中的react-native-pushy。這兩種方案實現思路其實差很少,以react-native-pushy爲例,咱們發現它有如下缺點:
(1) 內置資源體積過大,致使整個應用包的大小過大,致使過多佔用用戶手機容量以及下載應用耗時超長。
在APP提交給應用商店審覈時,會將RN集成編譯後的jsbundle打進內置資源裏面去,而一個完整的jsbundle在區分平臺(iOS/Android)以及js壓縮的前提下,體積有600K左右。若是隨着業務的快速擴展,假設有100個jsbundle的內置資源,那麼大小就會到達60M。而應用商店的app大小,以58APP爲例,大小才87M。內置資源過大,致使整個應用包的體積過大,一是佔用用戶手機容量,另外一個每次下載應用耗時超長。這在必定程度上是很難接受的。
(2) 計算增量的基準文件不惟一,平均合併的diff個數過多,增長了服務器處理增量的複雜度和下降了app端合併diff性能。
react-native-pushy在利用bsdiff算法計算增量時,是相鄰兩個版本文件的diff。如今假設用戶本地app文件版本是1.0,而服務器最新文件版本是4.0,則服務器須要返回app3個diff(1.0至2.0一個diff,2.0至3.0一個diff,3.0至4.0一個diff)。因此,因爲bsdiff算法計算增量的基準文件不惟一,致使平均需合併的diff個數過多。這不只增長了服務器處理增量的複雜度,還下降了app端合併diff的性能,合併時間多長,阻塞用戶操做。
(3) 將整個app的全部JSBundle文件打包進行更新,不區分業務。致使若是一個業務的JSBundle有問題,會影響其餘業務JSBundle的正常運行。
react-native-pushy作的是一個通用的熱更新平臺,一個app有一個key,文件更新以此key爲標識,全部文件在一個zip包裏面,不區分業務。當前稍微大的互聯網公司,都是以業務線劃分職能的,在技術架構上,各業務線業務應該作到相互不干擾。react-native-pushy這種不區分業務的更新模式,會致使若是一個業務的JSBundle有問題,會影響其餘業務JSBundle的正常更新,形成業務的相互干擾。
基於對上面的分析,咱們得出了熱更新須要解決的三個問題:
(1) 既要控制更新包的大小,又要控制內資資源的大小;
(2) 下降服務器處理增量複雜度和提升APP端合併diff性能;
(3) Diff更新以JSBundle文件爲單位,業務Diff之間相互不干擾。
基於上面的三個問題,咱們有以下的解決方案:
3.2.1 JSBundle拆分及公共部分生成
在介紹diff生成及合併算法以前,先介紹一下一個關鍵性要點。即咱們重點關注React Native中JSBundle的內容的特殊性,發現打包編譯後的一個JSBundle能夠拆成一個穩定的公共部分加上差別部分。如上圖所示,針對一個入口文件pageIndex.jsbundle, 能夠拆分紅穩定的commonPart.jsbundle以及差別部分diffPart.jsbundle。其中,commonPart.jsbundle與具體業務無關,是React Native中一些公用的js庫。
commonPart.jsbundle生成的方法爲(以iOS爲例,Android的原理相同):
(1)新建一個blank.ios.js文件,在文件中僅需引入react及react-native,注意不要包含任何業務代碼,具體代碼以下截圖:
(2)經過curl命令將blank.ios.js文件編譯成common.ios.jsbundle。筆者在本地的執行命令爲:
curl'http://localhost:8081/blank.ios.bundle?minify=true&dev=false' -ocommon.ios.jsbundle。獲得的common.ios.jsbundle結果以下圖所示:
須要補充的是,由於commonPart.jsbundle依賴Native代碼,因此commonPart.jsbundle的更新是跟着APP發版走的。
基於上文對jsbundle的拆分,咱們選擇了google-diff-match-patch算法生成diff 及合併diff。在計算diff時,以commonPart.jsbundle爲基準,計算當前版本的pageIndex.jsbundle與commonPart.jsbundle之間的文本差別,而後APP端拿到文本差別描述後,再利用google-diff-match-patch算法將文本差別合併到本地的commonPart.jsbundle中去。
生成diff調用google-diff-match-patch的API爲(iOS端爲例,其餘端可找到對應API):
合併diff調用google-diff-match-patch的API爲(iOS端爲例,其餘端可找到對應API):
3.2.3 熱更新流程
熱更新流程如上圖所示。圖中載體頁是指native加載React Native代碼的一個載體。RN是React Native的縮寫。下面對上述流程進行敘述:
(1)進入載體頁,判斷是否有緩存。
進入載體頁以後,會先判斷是否有RN緩存,若是有緩存,則直接進行下一步。若是沒有緩存,則去服務器下載對應的RN資源(RN資源指的RN代碼文件)。
(2)展現RN頁面
根據RN資源,載體頁渲染出對應的頁面。
(3)後臺請求當前頁面最新信息
在展現完RN頁面後,新起子線程在後臺向服務器請求當前頁面的最新信息數據。
(4)根據最新信息進行更新操做
根據上一步服務器返回的數據,進行分支操做:若是是強制更新,則彈窗提示用戶須要強制刷新當前頁面才能繼續操做;若是是通常的非強制更新,則程序在後臺更新數據,用戶下次進入此頁面後更新生效;若是沒有更新,則不作任何操做,流程結束。
針對上述流程有一個補充:在APP啓動的時候向服務器請求預加載數據,提早對一些重要的RN資源進行加載,這樣在上述流程的第一步就能夠直接利用RN緩存快速進入頁面,用戶體驗會更好。
基於上述方案,可解決上述提到的三個問題:
(1)在內置資源的時候,只需內置一分commonPart.jsbundle和相應入口頁面對應的diffPart.jsbundle。在一般狀況下,commonPart.jsbundle佔整個jsbundle近2/3的大小,這相比基於react-native-pushy而內置的資源節省了近2/3的大小。另外,針對業務迭代過程的更新包,都只是業務代碼的更新,包的大小也獲得了很好的控制。
(2)服務器計算diff包的時候,因爲只需對commonPart.jsbundle進行比較,因此計算增量的複雜度相比react-native-pushy降到了最低。APP端在合成diff的時候,只須要將一個common.jsbundle與一個diff.jsbundle進行合併便可,合併性能相比react-native-pushy平均要合併多個,獲得了很大的提升。
(3)本方案計算的更新,以某一個入口頁面對於的pageIndex.jsbundle爲單位來操做,是以具體業務爲單位的。每個pageIndex.jsbundle對應的更新都是相互獨立的,即便一個業務更新出錯,也不會影響到其餘業務的更新。
通過兩個多月的研發,Native端,FE中間層,FE業務層,業務Server,熱更新平臺全部功能所有上線了,多虧PM上線前燒了高香,整個過程是很曲折的,但結果是美好的。但老闆說了,RN這個東西曆來沒上過,萬一上線以後出了重大問題怎麼辦?經過發版解決週期太長,速度太慢。因而乎只能經過Server端來控制了。
經過Server控制線上出現問題的風險,咱們稱爲降級策略。即用RN以前,58APP使用的Hybrid框架來作的發佈頁面。若是線上的RN出了問題,經過Server端控制跳轉協議,讓跳轉到web的發佈頁面上。等待RN問題解決了,再行切換。
產品上線後,產品層面最關注的就是加載速度了。針對發佈功能來講,從Hybrid切換到React Native,爲的就是加載速度。因爲RN有熱更新,基本上用戶是在90%的狀況下進入有緩存的界面的。下圖是由QA組王琪同窗提供的雙平臺在加載時間上的性能測試結果(有緩存的狀況下)。
上圖中的Web頁面加載時間是在網絡情況良好的狀況下的數據。從圖中能夠看出,RN頁面基本上是秒進,這讓從點擊發布按鈕到展現發佈頁面期間的用戶流失基本降到了最低。
通過項目組成員的辛苦努力,React Native在58APP上算是邁出了第一步,官方SDK如今是一個星期一個版本的更新節奏,後期開發中確定還會有好多坑,躍坑的過程確定很精彩!
http://mp.weixin.qq.com/s?__biz=MzI2NzI4MTEwNA==&mid=2247483942&idx=1&sn=a273257814626bd902c3d04d0e7f8d4f&chksm=ea807599ddf7fc8ffc9ba840a8f64e2e285151c837eeae57ef32528e73af8f073a19c0a60007&scene=4#wechat_redirect