一核无、引言
之前的文章客戶端Hybrid架構(gòu)設(shè)計(jì)之——WebView方案的實(shí)現(xiàn),已經(jīng)介紹了如何用WebView來(lái)實(shí)現(xiàn)Hybrid架構(gòu)隅熙。當(dāng)時(shí)提到了這種方案的幾個(gè)缺陷:
- H5頁(yè)面的部分用戶體驗(yàn)不如native
- H5也需適配各平臺(tái)機(jī)型
- 原生端基礎(chǔ)框架搭建費(fèi)時(shí)費(fèi)力哲戚,有時(shí)一些特殊功能需要ios、Android荤堪、H5三方聯(lián)合開(kāi)發(fā),增加了溝通協(xié)調(diào)成本
所以在這套架構(gòu)基本開(kāi)發(fā)完善之后枢赔,項(xiàng)目組馬不停蹄的開(kāi)始研究相對(duì)更先進(jìn)逞力、更優(yōu)的解決方案——ReactNative/Weex。他們相比WebView方案的區(qū)別就在于糠爬,js代碼最終會(huì)被翻譯成原生代碼,呈現(xiàn)在用戶面前的也是原生組件举庶,所以性能执隧、體驗(yàn)也是無(wú)限趨近于原生級(jí)(之所以只能是趨近,因?yàn)檫€是有js和原生通信所產(chǎn)生的性能損耗)户侥。
經(jīng)過(guò)近半年的調(diào)研镀琉、學(xué)習(xí)、實(shí)戰(zhàn)蕊唐,項(xiàng)目最新版本的主要頁(yè)面已經(jīng)全部由ReactNative來(lái)實(shí)現(xiàn)屋摔,既然有了些成果,這次就來(lái)跟大家做個(gè)簡(jiǎn)單的分享替梨。本文為了方便钓试,主要還是從Android的角度出發(fā),iOS的思路也是一樣的副瀑。
二弓熏、選擇RN還是Weex
結(jié)果很明顯,我們選擇了RN糠睡,但不得不說(shuō)挽鞠,從一開(kāi)始,燙爺一直是更傾向于Weex的狈孔,因?yàn)楫?dāng)時(shí)覺(jué)得Weex相比RN有以下幾點(diǎn)優(yōu)勢(shì):
- Weex開(kāi)發(fā)者自身就是RN早期使用者信认,他們遇到的很多RN問(wèn)題,都在Weex中設(shè)法解決了
- Weex號(hào)稱“一次編寫均抽,多端運(yùn)行”嫁赏,比RN的“Learn Once Write Anywhere”強(qiáng)不少
- Weex對(duì)新人非常友好,上手簡(jiǎn)單到忽,這一點(diǎn)能得到很高的印象分
- Weex使用的Vue相比于ReactNative使用的React橄教,更加輕量級(jí)清寇,對(duì)于無(wú)前端經(jīng)驗(yàn)的客戶端開(kāi)發(fā)人員來(lái)說(shuō)更友好
- Weex是中國(guó)人自己的框架,支持國(guó)貨护蝶,人人有責(zé)华烟。
簡(jiǎn)單說(shuō),Weex更新更性感更中國(guó)持灰。那么大家要問(wèn)了盔夜,Weex那么棒,最后怎么還是選了RN堤魁?理由也不復(fù)雜:
- RN比Weex成熟太多了喂链,業(yè)界已經(jīng)有不少成功案例,而Weex在當(dāng)時(shí)只有幾個(gè)小demo妥泉,如果項(xiàng)目開(kāi)發(fā)中遇到框架本身繞不過(guò)去的坑椭微,很容易兩眼抓瞎
- RN雖然不標(biāo)榜“一次編寫,多端運(yùn)行”盲链,但是大多數(shù)的組件蝇率,包括社區(qū)提供的第三方,還是可以在iOS刽沾、Android兩個(gè)平臺(tái)通用本慕,不能通用的也可以通過(guò)js中間層去屏蔽對(duì)業(yè)務(wù)代碼的影響,畢竟這些都是一勞永逸的活
- RN的文檔小爛侧漓,但是Weex幾乎沒(méi)有文檔
- Weex是中國(guó)人自己的框架锅尘,還他喵的是阿里的框架(大霧)
技術(shù)選型,可能還是要穩(wěn)妥一點(diǎn)布蔗,不能一味追求新潮藤违,畢竟支撐業(yè)務(wù)才是第一位的。
三何鸡、ReactNative實(shí)現(xiàn)核心模塊簡(jiǎn)介
RN方案其實(shí)和WebView方案非常相似纺弊,實(shí)現(xiàn)起來(lái)也并沒(méi)有更麻煩,主要還是以下幾個(gè)模塊
- RN框架
RN框架本身已經(jīng)是一個(gè)相當(dāng)成熟的框架了骡男,前期的配置看一些官方文檔已經(jīng)足夠淆游,可以較方便的把RN項(xiàng)目跑起來(lái)。 - 路由模塊
其實(shí)一個(gè)純RN應(yīng)用只要一個(gè)Activity就足夠了隔盛,但大多數(shù)項(xiàng)目都有歷史包袱犹菱,比如燙爺?shù)捻?xiàng)目,又有原生頁(yè)面吮炕,又有H5頁(yè)面腊脱,現(xiàn)在又多了RN頁(yè)面,怎么在這些頁(yè)面之間自由跳轉(zhuǎn)龙亲,就得依賴路由模塊了陕凹,這個(gè)模塊的介紹同樣可以參考之前的文章客戶端Hybrid架構(gòu)設(shè)計(jì)之——WebView方案的實(shí)現(xiàn)悍抑。 - 更新模塊
RN的一大好處是可以不用發(fā)客戶端版本,動(dòng)態(tài)更新杜耙,所以更新模塊尤為關(guān)鍵搜骡。 - 統(tǒng)計(jì)模塊
由于市面上大多數(shù)統(tǒng)計(jì)sdk不會(huì)統(tǒng)計(jì)RN數(shù)據(jù),所以我們很有必要自己實(shí)現(xiàn)相應(yīng)的統(tǒng)計(jì)模塊佑女,不然我們對(duì)RN頁(yè)面性能到底如何记靡,完全不能做到門清,那后期優(yōu)化就會(huì)比較的茫然和無(wú)的放矢团驱。
同時(shí)還有一些需要額外學(xué)習(xí)的前端知識(shí)
- ES6語(yǔ)法和React
這是基礎(chǔ)中的基礎(chǔ)摸吠,寫RN代碼必須的,建議可以看看阮一峰相關(guān)的教程嚎花。 - Flexbox布局
在ReactNative中我們使用flexbox規(guī)則來(lái)指定某個(gè)組件的子元素的布局寸痢,這個(gè)不難,看看官方文檔就足夠了紊选。 - Redux轿腺、saga等配合React使用的框架
這次折騰,一大感慨就是前端社區(qū)確實(shí)比客戶端社區(qū)熱鬧好多丛楚,各種各樣各種功能的前端框架層出不窮。不過(guò)燙爺建議憔辫,一開(kāi)始只要使用React已經(jīng)足夠了趣些,只有當(dāng)你的應(yīng)用足夠復(fù)雜以后,才有必要去使用Redux等第三方框架贰您,一開(kāi)始千萬(wàn)不要貪多嚼不爛坏平,那樣很容易由于學(xué)習(xí)曲線過(guò)于陡峭,而打擊積極性锦亦。
四舶替、原生改造
-
Bundle路徑修改
RN會(huì)把所有的RN代碼打包成一個(gè)Bundle,框架讀取這個(gè)Bundle就可以加載相應(yīng)的RN頁(yè)面了杠园」说桑框架默認(rèn)是讀取預(yù)置在本地比如Assets里的bundle文件,如果要修改路徑抛蚁,就需要修改相關(guān)的RN代碼陈醒。拿Android舉例,就需要寫個(gè)類繼承ReactNativeHost瞧甩,并修改getJSBundleFile()方法钉跷,并且為了能應(yīng)用這個(gè)自定義的Host,還需要修改或者覆蓋對(duì)應(yīng)的一系列文件肚逸,如ReactActivityDelegate爷辙、ReactActivity等彬坏。
-
Activity和原生模塊關(guān)聯(lián)
有些RN無(wú)法完成的功能,需要靠自定義原生模塊來(lái)實(shí)現(xiàn)膝晾,但是由于RN框架沒(méi)考慮多Activity的情況栓始,那樣在原生和RN之間的交互就可能引發(fā)混亂。舉個(gè)例子玷犹,RN頁(yè)面需要通過(guò)接口請(qǐng)求結(jié)果來(lái)通過(guò)原生模塊設(shè)置title混滔,但是在結(jié)果返回之前,我們可能已經(jīng)跳到一個(gè)新Activity了歹颓,這個(gè)時(shí)候就不應(yīng)該由新的Activity去設(shè)置title坯屿,而應(yīng)該找到之前的Activity去做這件事。所以我們要做的就是把Activity和RN頁(yè)面巍扛,以及在這個(gè)頁(yè)面上發(fā)出的原生模塊方法做一個(gè)綁定领跛。
具體的做法是每個(gè)RNActivity都使用自己實(shí)例的hashcode作為自己的id,在創(chuàng)建RN頁(yè)面時(shí)把這個(gè)id傳給RN撤奸,而RN頁(yè)面在調(diào)用原生模塊方法時(shí)吠昭,則把這個(gè)id作為一個(gè)參數(shù)再傳回來(lái)。同時(shí)我們維護(hù)一個(gè)RNActivityPool胧瓜,用RNActivity的id作key矢棚,來(lái)保存RNActivity,這個(gè)時(shí)候在調(diào)用原生模塊方法時(shí)府喳,就可以通過(guò)id來(lái)找到需要對(duì)這個(gè)方法負(fù)責(zé)的RNActivity了蒲肋。
-
Launch頁(yè)面和預(yù)加載
無(wú)論是RN框架的啟動(dòng)還是RNBundle的加載、RN頁(yè)面的實(shí)際渲染钝满,都需要一定時(shí)間兜粘,而當(dāng)這些因素疊加在一起,也即啟動(dòng)第一個(gè)RN頁(yè)面時(shí)弯蚜,白屏?xí)r間會(huì)長(zhǎng)的有點(diǎn)讓人難以接受(5s左右甚至更長(zhǎng))孔轴。解決的辦法是Launch頁(yè)面完全用原生做,并且在Launch頁(yè)面預(yù)加載bundle碎捺。一般我們的Launch頁(yè)面都會(huì)做一些別的初始化工作路鹰,或者廣告加載等,這些時(shí)間足夠完成RN的預(yù)加載了收厨,這樣一來(lái)打開(kāi)第一個(gè)RN頁(yè)面就和后面打開(kāi)其他RN頁(yè)面沒(méi)有明顯的差距了悍引。預(yù)加載的方法也有現(xiàn)成的
ReactInstanceManager reactInstanceManager =
((ReactApplication) getApplication()).getReactNativeHost().getReactInstanceManager();
if (!reactInstanceManager.hasStartedCreatingInitialContext()) { // 這句必須加,不然容易重復(fù)create帽氓,造成RN拋出異常
reactInstanceManager.createReactContextInBackground();
}
五趣斤、更新模塊
首先,當(dāng)然可以使用Microsoft/code-push這類第三方更新組件黎休,但是為了更好的把控這一核心功能浓领,包括未來(lái)對(duì)多Bundle的處理優(yōu)化等玉凯,有余力的同學(xué),最好還是自己實(shí)現(xiàn)更新模塊联贩。
1漫仆、差分更新技術(shù)
更新模塊中的一個(gè)核心技術(shù)點(diǎn)就是差分更新技術(shù),差分更新技術(shù)主要依賴文件的開(kāi)源二進(jìn)制比較工具bsdiff泪幌。我們用apk更新的例子來(lái)簡(jiǎn)單介紹下差分更新技術(shù):
自從 Android 4.1 開(kāi)始盲厌,Google Play 引入了應(yīng)用程序的增量更新功能,App使用該升級(jí)方式祸泪,可節(jié)省約2/3的流量÷鸷疲現(xiàn)在國(guó)內(nèi)主流的應(yīng)用市場(chǎng)也都支持應(yīng)用的增量更新了。
增量更新的原理没隘,就是將手機(jī)上已安裝apk與服務(wù)器端最新apk進(jìn)行二進(jìn)制對(duì)比懂扼,得到差分包,用戶更新程序時(shí)右蒲,只需要下載差分包阀湿,并在本地使用差分包與已安裝apk,合成新版apk瑰妄。
例如陷嘴,當(dāng)前手機(jī)中已安裝微博V1,大小為12.8MB间坐,現(xiàn)在微博發(fā)布了最新版V2罩旋,大小為15.4MB,我們對(duì)兩個(gè)版本的apk文件差分比對(duì)之后眶诈,發(fā)現(xiàn)差異只有3M,那么用戶就只需要要下載一個(gè)3M的差分包瓜饥,使用舊版apk與這個(gè)差分包逝撬,合成得到一個(gè)新版本apk,提醒用戶安裝即可乓土,不需要整包下載15.4M的微博V2版apk宪潮。
以上內(nèi)容摘自 cundong/SmartAppUpdates
如上所述,差分更新技術(shù)可以有效減小更新包的大小趣苏,讓更新更有效率狡相。
2、具體流程
服務(wù)器端
每次RN代碼打包時(shí)食磕,都自動(dòng)生成當(dāng)前最新版本的全量包尽棕,以及新版本和最近5個(gè)舊版本的差分包,保存在服務(wù)器端彬伦√舷ぃ客戶端發(fā)起更新檢查接口時(shí)上報(bào)自己當(dāng)前的RN版本號(hào)伊诵,如果版本差小于等于5,服務(wù)器端就同時(shí)下發(fā)差分包和全量包的下載地址,反之如果版本差大于5回官,則只下發(fā)全量包地址曹宴。-
客戶端
客戶端每次啟動(dòng)時(shí)同步做兩件事:
a. 發(fā)起更新檢查接口,詢問(wèn)服務(wù)端是否需要更新歉提,如需要笛坦,是差分更新還是全量更新。更新時(shí)苔巨,優(yōu)先進(jìn)行差分更新版扩,具體步驟為下載差分包——與舊包合成——驗(yàn)證合成包的MD5值是否與服務(wù)器先前下發(fā)的一致,這些都完成后就可以把合成包保存好恋拷,等待下次重啟時(shí)應(yīng)用了资厉。這一過(guò)程中如果出現(xiàn)任何問(wèn)題,則放棄差分更新蔬顾,去下載全量包進(jìn)行全量更新迄埃。b.應(yīng)用當(dāng)前本地保存的最新的RN包,并刪除舊包券腔。如果既沒(méi)有新包阱驾,也沒(méi)有舊包,則從本地Assets中拉取打包時(shí)預(yù)置的包舷胜。
注意娩践,之所以下載完新包不馬上應(yīng)用,而是要等到下次重啟時(shí)才生效烹骨,主要是擔(dān)心在用戶使用過(guò)程中突然重新加載RN頁(yè)面有可能改變頁(yè)面UI甚至是業(yè)務(wù)流程翻伺,這會(huì)導(dǎo)致用戶體驗(yàn)不佳或者操作失敗。
六沮焕、統(tǒng)計(jì)模塊
高級(jí)程序員應(yīng)該樹(shù)立一個(gè)意識(shí)吨岭,開(kāi)發(fā)并不只是開(kāi)發(fā)完功能就結(jié)束了,事實(shí)上功能開(kāi)發(fā)可能只占了整體工作量的一半峦树,設(shè)計(jì)辣辫、測(cè)試、統(tǒng)計(jì)魁巩、監(jiān)控等則占了另外一半急灭。你開(kāi)發(fā)的系統(tǒng)不應(yīng)該僅僅滿足于跑起來(lái),還應(yīng)該有一套成熟的統(tǒng)計(jì)系統(tǒng)去分析系統(tǒng)運(yùn)轉(zhuǎn)狀態(tài)谷遂,有一套監(jiān)控系統(tǒng)在出現(xiàn)故障時(shí)能自動(dòng)采取措施或至少發(fā)出預(yù)警葬馋。由于RN是一個(gè)新框架,無(wú)論是框架本身,還是我們的應(yīng)用技巧都還不夠成熟点楼,所以非常有必要去對(duì)RN的性能扫尖、錯(cuò)誤等進(jìn)行統(tǒng)計(jì),并以此作為未來(lái)優(yōu)化的根據(jù)掠廓。
-
性能統(tǒng)計(jì)
性能統(tǒng)計(jì)主要是統(tǒng)計(jì)RN頁(yè)面的白屏?xí)r間换怖,具體方法是把一個(gè)頁(yè)面創(chuàng)建時(shí)各個(gè)生命周期的時(shí)間戳記錄下來(lái),再統(tǒng)一上傳到服務(wù)器做分析蟀瞧。生命周期從前往后依次是onCreate(原生)沉颂、componentWillMount、componentDidMount悦污、componentWillUpdate铸屉、componentDidUpdate可以簡(jiǎn)單的認(rèn)為
RN初始化時(shí)間 = componentWillMount - onCreate
靜態(tài)UI加載時(shí)間 = componentDidMount - onCreate
服務(wù)器數(shù)據(jù)返回頁(yè)面刷新時(shí)間 = componentDidUpdate - onCreate(ps:因?yàn)橹灰淖僺tate就會(huì)觸發(fā)componentWillUpdate和componentDidUpdate,所以需要做個(gè)標(biāo)志位切端,只記錄關(guān)鍵接口第一次返回后觸發(fā)的componentWillUpdate和componentDidUpdate)
因?yàn)榻^大多數(shù)頁(yè)面都需要調(diào)用服務(wù)器接口獲取數(shù)據(jù)彻坛,之后再刷新頁(yè)面,一般也只有在此時(shí)用戶才能看到有意義的信息踏枣,所以我們更關(guān)注第三個(gè)指標(biāo)昌屉,也可以直接把第三個(gè)時(shí)間當(dāng)做是廣義的白屏?xí)r間。
以下是燙爺?shù)捻?xiàng)目收集到的數(shù)據(jù)茵瀑,基本是未經(jīng)過(guò)什么特別優(yōu)化的RN表現(xiàn)间驮,可以看出白屏?xí)r間基本在1s以內(nèi),明顯還是比Hybrid的表現(xiàn)好不少的马昨,而且比較讓人意外的是竞帽,Android平臺(tái)的性能并沒(méi)有比iOS差多少,這一點(diǎn)比WebView方案強(qiáng)上很多鸿捧。
WechatIMG2.jpeg 埋點(diǎn)統(tǒng)計(jì)
RN的埋點(diǎn)比較簡(jiǎn)單的方法是把RN自帶的幾個(gè)可點(diǎn)擊組件再封裝一遍屹篓,包括TouchableHighlight、TouchableNativeFeedback匙奴、TouchableOpacity堆巧、TouchableWithoutFeedback,在其中添加統(tǒng)計(jì)功能饥脑,這樣開(kāi)發(fā)業(yè)務(wù)時(shí)只要用封裝好的組件,再添加對(duì)應(yīng)的統(tǒng)計(jì)id就可以了懦冰。
七灶轰、其他
-
屏幕適配
RN框架默認(rèn)的長(zhǎng)度單位是dp,但是在某些場(chǎng)景用dp并不能很好的適配所有屏幕大小刷钢,比如有些屏幕寬320dp笋颤,有些寬360dp,這就給我們適配造成了麻煩。這里推薦一個(gè)三方庫(kù)vitalets/react-native-extended-stylesheet伴澄,這個(gè)庫(kù)有不少功能赋除,其中一個(gè)就是可以適配iOS和Android的不同屏幕。比如我們可以在框架中設(shè)定屏幕寬度為750px非凌,設(shè)計(jì)圖全部按750px寬來(lái)出举农,之后開(kāi)發(fā)設(shè)置具體組件的長(zhǎng)寬時(shí),就可以完全按照設(shè)計(jì)圖上的尺寸來(lái)敞嗡,隨后框架會(huì)根據(jù)設(shè)備的長(zhǎng)寬去做等比縮放以此達(dá)到適配效果颁糟,原理和安卓原生的UI適配框架hongyangAndroid/AndroidAutoLayout很像。
其實(shí)關(guān)于ReactNative的內(nèi)容還有很多喉悴,這篇文章就先總結(jié)到這里棱貌,相信把上面提到的那些做好,再結(jié)合下官網(wǎng)文檔箕肃,已經(jīng)足夠做出一個(gè)靠譜的商業(yè)級(jí)app了婚脱。等未來(lái)燙爺研究的再深入些,再找些別的干貨來(lái)給大家作匯報(bào)勺像,我們到時(shí)再會(huì)障贸!