拉勾算法突擊特訓(xùn)營(yíng)3期-明月何皎皎
Que App跨端技術(shù)架構(gòu)實(shí)踐
1.麻雀的整體介紹
拉勾算法突擊特訓(xùn)營(yíng)3期
拉勾算法突擊特訓(xùn)營(yíng)3期https://www.zxit666.com/4981/
1.1.簡(jiǎn)介
首先,我們來(lái)介紹一下語(yǔ)言Que的整體情況。Language Que是螞蟻集團(tuán)推出的筆記和文檔知識(shí)庫(kù)管理與協(xié)作工具。目前,全集團(tuán)約有10萬(wàn)員工也在日常使用這一工具,并對(duì)外提供服務(wù)。
下圖是基于電子的語(yǔ)言Que推出的Mac和Windows桌面,以及處理移動(dòng)端工作場(chǎng)景的小程序。此外,從去年開(kāi)始,我們開(kāi)始開(kāi)發(fā)移動(dòng)應(yīng)用程序,并于今年2月成功推出。右圖為Android/iOS應(yīng)用截圖。
1.2.語(yǔ)言雀的整體技術(shù)選擇
接下來(lái)我就介紹一下麻雀的整體技術(shù)選型。Que的內(nèi)部名稱是Skylark,它的整個(gè)項(xiàng)目位于一個(gè)大型的代碼庫(kù)中。這個(gè)代碼庫(kù)中包含了server、desktop和mobile的所有代碼,如下圖所示:
其中服務(wù)器使用Node.js,Egg.js,也有部分服務(wù)是基于Java/Kotlin開(kāi)發(fā)的,但這部分目前比較少。PC端主要使用React技術(shù)棧,桌面端使用React和電子,小程序使用React和H5離線包。正如你所看到的,整個(gè)技術(shù)是基于JavaScript或TypeScript的。
語(yǔ)言Que整個(gè)技術(shù)團(tuán)隊(duì)沒(méi)有前端和后端或者客戶端之分,大家都是全棧式的做產(chǎn)品研發(fā)。一個(gè)團(tuán)隊(duì)成員可能必須完成需求的服務(wù)器端部分和前端代碼開(kāi)發(fā)。在此基礎(chǔ)上,我們?cè)O(shè)計(jì)開(kāi)發(fā)了手機(jī)App,接下來(lái)會(huì)詳細(xì)介紹。
2.語(yǔ)言鳥(niǎo)App架構(gòu)的演繹與設(shè)計(jì)
2.1.阿里云mPaaS
Que App的架構(gòu)是如何設(shè)計(jì)的?首先,整個(gè)集團(tuán)已經(jīng)有了一個(gè)相對(duì)穩(wěn)定的移動(dòng)終端框架——MPAAS,這是移動(dòng)終端開(kāi)發(fā)的基礎(chǔ)框架,提供了豐富的移動(dòng)終端基礎(chǔ)能力,如配置切換服務(wù)、推送服務(wù)、基于長(zhǎng)連接的同步服務(wù)、H5容器&離線包、移動(dòng)分析、移動(dòng)網(wǎng)關(guān)等。現(xiàn)在它也在阿里云上對(duì)外提供服務(wù),很多三方公司都基于這個(gè)框架開(kāi)發(fā)了自己的app:
2.2.移動(dòng)建筑理念
在此技術(shù)品牌的基礎(chǔ)上,我們?cè)O(shè)計(jì)了移動(dòng)終端的架構(gòu)。首先選擇阿里云上的mPaaS作為底層框架基礎(chǔ),然后客戶端的原生框架層盡量做到輕量級(jí),與業(yè)務(wù)解耦:
接下來(lái)對(duì)于渲染層,我們考慮動(dòng)態(tài)或者混合方案,比如根據(jù)文檔閱讀頁(yè)面或者編輯器的業(yè)務(wù)特點(diǎn)選擇特定的框架。最后要考慮同構(gòu)開(kāi)發(fā),因?yàn)橐呀?jīng)有很多多端的業(yè)務(wù)模塊在運(yùn)行。如果可以實(shí)現(xiàn)同構(gòu)開(kāi)發(fā),那么這些代碼或者經(jīng)驗(yàn)就可以重用。
2.3.移動(dòng)解決方案比較
下圖是各種開(kāi)發(fā)框架在移動(dòng)端的對(duì)比。相信你在很多場(chǎng)合都見(jiàn)過(guò)。其中,基于H5混合(H5或離線包)的方案性能稍差。純Native雖然性能很高,但是開(kāi)發(fā)成本也相應(yīng)增加,上手難度大?,F(xiàn)在一些公司也有自己的內(nèi)部混合開(kāi)發(fā)方案。
2.4.語(yǔ)言雀的選擇
那么如何選擇麻雀呢?首先,我們希望麻雀的體驗(yàn)流暢,能夠接近原生App的各種體驗(yàn);其次,希望能寫(xiě)一次,兩邊跑,兼顧開(kāi)發(fā)效率;然后,可以有Web R&D的經(jīng)驗(yàn),讓團(tuán)隊(duì)的Web技術(shù)棧學(xué)生快速參與開(kāi)發(fā);最后,希望可以同構(gòu)復(fù)用,讓之前的經(jīng)驗(yàn)和模塊可以直接復(fù)用。
綜合考慮各種因素和實(shí)際業(yè)務(wù)需求,我們最終選擇ReactNative作為業(yè)務(wù)層開(kāi)發(fā)框架。
2.5.語(yǔ)言鳥(niǎo)App架構(gòu)圖
接下來(lái)我給大家介紹一下語(yǔ)鳥(niǎo)App的整體架構(gòu),如下圖所示。底層是mPaaS的基礎(chǔ)設(shè)施,如前所述,它包含一些常見(jiàn)的基本組件。中間層是原生基礎(chǔ)能力層,提供基礎(chǔ)賬戶管理、統(tǒng)一資源管理、動(dòng)態(tài)能力、各種橋梁等服務(wù)。
接下來(lái),我們可以看到兩個(gè)向上的箭頭。RCTBridge負(fù)責(zé)通過(guò)React Native橋接口將基本服務(wù)暴露給React NativeJSBridge通過(guò)H5 JSBridge向H5同步公開(kāi)這些服務(wù)。然后,ReactNative和H5之間的雙向通信通過(guò)RCTEvent進(jìn)行。
反應(yīng)層主要實(shí)現(xiàn)頁(yè)面生命周期、業(yè)務(wù)跳轉(zhuǎn)、頁(yè)面展現(xiàn)等。H5主要用于承載需要高更新速度的業(yè)務(wù)。上層是CI/CD、DevTools、環(huán)境切換、單元測(cè)試等。,全面支持語(yǔ)鳥(niǎo)上層的具體業(yè)務(wù)。
2.6.三層架構(gòu)
如圖,可以看出整體是三層架構(gòu)。在原生層,我們使用Kotlin/Swift來(lái)實(shí)現(xiàn)。反應(yīng)層通過(guò)TypeScript/同構(gòu)Web實(shí)現(xiàn)。該層主要用于實(shí)現(xiàn)對(duì)流暢度和體驗(yàn)要求較高的列表或頁(yè)面。H5層主要結(jié)合Sparrow的具體業(yè)務(wù)特點(diǎn)使用:比如你想用ReactNative實(shí)現(xiàn)一個(gè)富文本編輯器或者閱讀器,那么它的成本是很高的,這種情況用H5實(shí)現(xiàn);對(duì)于需要高頁(yè)面加載速度的H5頁(yè)面,我們使用離線包來(lái)加速頁(yè)面資源。
2.7.常規(guī)JSBridge設(shè)計(jì)
接下來(lái),我們來(lái)介紹一下一般的JS橋設(shè)計(jì)如下:
在本地層,RN和H5共享一個(gè)JS橋?qū)崿F(xiàn)。一些底層的通用服務(wù)功能通過(guò)兩個(gè)橋公開(kāi),H5和ReactNative可以直接調(diào)用這兩個(gè)橋。上圖右側(cè)顯示了H5和反應(yīng)呼叫橋的代碼。其實(shí)除了第一行不一樣,后面的橋調(diào)用和結(jié)果處理都是一致的。
2.8.反應(yīng)本地H5雙向消息流
在業(yè)務(wù)開(kāi)發(fā)中,不可避免地會(huì)遇到反應(yīng)性頁(yè)面和開(kāi)放的H5頁(yè)面之間的交互和通信。我們開(kāi)發(fā)了雙向消息流機(jī)制,可以在兩邊使用postMessage/onMessage來(lái)發(fā)送和接收消息。
比如進(jìn)入富文本編輯頁(yè)面后,底部有一個(gè)ReactNative視圖,在這個(gè)視圖上嵌入了WebView/WKWebView來(lái)顯示編輯頁(yè)面。這涉及到H5和ReactNative之間的雙向消息流:
如上圖所示,藍(lán)色箭頭是H5呼叫反應(yīng)式,紅色箭頭是反應(yīng)式呼叫H5。雙方通過(guò)postMessage/onMessage相互通信,支持回調(diào)處理。
3.跨端同構(gòu)實(shí)踐
如前所述,在選擇Yuque App的技術(shù)時(shí)考慮到了同構(gòu)開(kāi)發(fā)。那么,語(yǔ)言雀App中涉及到哪些場(chǎng)景的同構(gòu)呢?在具體的業(yè)務(wù)研發(fā)中,我們遇到了幾種典型的同構(gòu)場(chǎng)景。接下來(lái)簡(jiǎn)單介紹一下同構(gòu)的實(shí)踐經(jīng)驗(yàn)。
3.1.基于視圖模型的同構(gòu)列表頁(yè)面
如下圖所示,我們看到一個(gè)包含一些列表的網(wǎng)頁(yè),每個(gè)列表項(xiàng)包含一個(gè)操作選項(xiàng)條目。用戶點(diǎn)擊操作菜單后,可以對(duì)列表進(jìn)行排序、過(guò)濾或刪除。小程序和移動(dòng)應(yīng)用也是如此。這三個(gè)頁(yè)面的共同點(diǎn)是都是一個(gè)列表頁(yè)面,需要同時(shí)請(qǐng)求網(wǎng)絡(luò)加載數(shù)據(jù)。此外,它們還可能包括分頁(yè)操作等。當(dāng)然這里沒(méi)有特別復(fù)雜的交互。點(diǎn)擊操作菜單進(jìn)行刪除等操作。知道了這些共性,我們就很容易發(fā)展出同構(gòu)。
具體的列表頁(yè)面如何基于ViewModel進(jìn)行同構(gòu)開(kāi)發(fā)?如下圖所示,我們拿出通用的ViewModel,做加載管理,數(shù)據(jù)請(qǐng)求,用戶操作管理等。在里面。網(wǎng)絡(luò)請(qǐng)求管理可以通過(guò)recentListAll進(jìn)行請(qǐng)求,并分別通過(guò)狀態(tài)和處理程序公開(kāi)數(shù)據(jù)和特定操作。這最終會(huì)返回狀態(tài)集和一個(gè)動(dòng)作列表:
在使用上層的時(shí)候,比如我們使用這個(gè)ViewModel渲染最終頁(yè)面的時(shí)候,只需要在每個(gè)平臺(tái)上重寫(xiě)UI渲染部分,返回到對(duì)應(yīng)平臺(tái)的View/div。常見(jiàn)的邏輯密集型和請(qǐng)求密集型代碼都是在相同的結(jié)構(gòu)中開(kāi)發(fā)的。
3.2.請(qǐng)求請(qǐng)求同構(gòu)
互聯(lián)網(wǎng)服務(wù)研發(fā)中的網(wǎng)絡(luò)請(qǐng)求在接口層面是同構(gòu)的,可以保證三端代碼和開(kāi)發(fā)模式的一致性。具體同構(gòu)模式如下:
同構(gòu)定義一個(gè)通用接口文件,其中fetch/get/post/...并使用其他方法發(fā)送請(qǐng)求;
在每個(gè)平臺(tái)上實(shí)現(xiàn)特定的fetch接口。比如Web端使用瀏覽器的fetch接口,applet端可能調(diào)用AlipayJSBridge,ReactNative端調(diào)用LarkRCTBridge;
在構(gòu)建階段,request.js通過(guò)alias在各種平臺(tái)上重寫(xiě)。
3.3.該消息在三個(gè)端點(diǎn)是同構(gòu)的。
接下來(lái)我們介紹另一個(gè)同構(gòu)場(chǎng)景:消息通知三端同構(gòu)。例如,我們?cè)赪eb端有這樣一個(gè)消息列表。只要?jiǎng)e人關(guān)注我或者評(píng)論我的文檔,就會(huì)產(chǎn)生一條消息。小程序和app端的UI都差不多。另一個(gè)場(chǎng)景是App消息推送。當(dāng)其他人操作我的文檔時(shí),它應(yīng)該顯示在移動(dòng)設(shè)備系統(tǒng)的通知欄中:
那么這種情況下我們?cè)趺醋鐾瑯?gòu)開(kāi)發(fā)呢?現(xiàn)在有70多種信息。如果您想為所有三個(gè)終端編寫(xiě)一個(gè)實(shí)現(xiàn),您可能需要編寫(xiě)230多次。這顯然是不現(xiàn)實(shí)的。在這一點(diǎn)上,可以基于同構(gòu)方案來(lái)完成。同構(gòu)部分包括各種消息的同構(gòu)生成器生成器,通過(guò)模板通知上下文構(gòu)建三端渲染結(jié)果:服務(wù)器獲取消息字符串進(jìn)行顯示,在H5或Web端渲染native div、span等元素,在ReactNative端渲染View、text等視圖。
比如下圖所示的上下文上有buildActor、buildSubject、buildLink來(lái)渲染用戶頭像或鏈接等。接下來(lái),服務(wù)器、applet和ReactNative可以提供三種上下文實(shí)現(xiàn):
上面介紹的一些典型的同構(gòu)場(chǎng)景,很好的驗(yàn)證了App開(kāi)發(fā)中架構(gòu)設(shè)計(jì)階段提出的同構(gòu)思想。在實(shí)際的R&D中,模塊和代碼的重用將大大提高R&D的效率。
4.子應(yīng)用程序設(shè)計(jì)
在實(shí)際的業(yè)務(wù)R&D中,一些業(yè)務(wù)模塊對(duì)更新的及時(shí)性有很強(qiáng)的要求。此外,技術(shù)方也希望一些業(yè)務(wù)模塊可以獨(dú)立維護(hù)和交付,可以按需加載,減輕主應(yīng)用負(fù)荷。在此基礎(chǔ)上,我們?cè)O(shè)計(jì)了一個(gè)子應(yīng)用方案來(lái)滿足這個(gè)場(chǎng)景。
基于反應(yīng)式的動(dòng)態(tài)加載特性,設(shè)計(jì)了一個(gè)子應(yīng)用架構(gòu)。使App中更新率高的場(chǎng)景動(dòng)態(tài)加載子應(yīng)用。如下圖所示,RN側(cè)的主應(yīng)用代碼和子應(yīng)用代碼可以分開(kāi)維護(hù),子應(yīng)用可以用app打包預(yù)置,也可以通過(guò)CDN動(dòng)態(tài)分發(fā)加載,更符合金絲雀的業(yè)務(wù)場(chǎng)景。
5.性能、穩(wěn)定性和交付
5.1.性能調(diào)整
在實(shí)際開(kāi)發(fā)中,我們遇到了一些性能問(wèn)題。首先是App啟動(dòng)速度的優(yōu)化。App啟動(dòng)時(shí),一般需要顯示閃屏頁(yè)面、隱私協(xié)議授權(quán)頁(yè)面、權(quán)限授予等。我們將閃屏頁(yè)面和主活動(dòng)合二為一,同時(shí)利用管道方案自定義啟動(dòng)細(xì)節(jié),最終優(yōu)化了啟動(dòng)速度。
二是筆記本編輯器啟動(dòng)速度的優(yōu)化。如前所述,編輯器是由H5制作的,但如果用戶受到啟發(fā),想做一些記錄,如果編輯器啟動(dòng)緩慢,那么用戶就很惱火。為此我們?yōu)樾〖隽艘粋€(gè)單獨(dú)的編輯器,可以通過(guò)離線打包預(yù)置到客戶端,也可以通過(guò)動(dòng)態(tài)分發(fā)更新,第一時(shí)間快速拉起。
此外,我們還優(yōu)化了WebView的加載,并針對(duì)一些通用的JS和CSS預(yù)加速了一些資源包。結(jié)合WebView預(yù)創(chuàng)建+回收方案,大大提高了App中H5頁(yè)面的打開(kāi)速度。
5.2.WebView預(yù)創(chuàng)建+回收
在Que App中,文檔閱讀頁(yè)面、編輯頁(yè)面等復(fù)雜頁(yè)面均由H5實(shí)現(xiàn)。用戶一直反饋終端中的打開(kāi)速度非常慢。經(jīng)過(guò)我們的實(shí)測(cè),在一個(gè)很差的安卓機(jī)型上打開(kāi)一個(gè)文檔大概需要5s!結(jié)合業(yè)務(wù)特點(diǎn),我們采用了WebView預(yù)創(chuàng)建+回收的方案,大大提升了H5的開(kāi)放性能。
如下圖所示,App啟動(dòng)后,啟動(dòng)WebView預(yù)創(chuàng)建池,預(yù)創(chuàng)建離屏H5骨架屏——這個(gè)骨架屏是一個(gè)單頁(yè)應(yīng)用,包括最后經(jīng)常使用的頁(yè)面,比如閱讀頁(yè)面、新頁(yè)面、個(gè)人頁(yè)面等。當(dāng)用戶打開(kāi)相應(yīng)頁(yè)面時(shí),直接將其從池中取出,識(shí)別出打開(kāi)場(chǎng)景后,切換替換路線。由于頁(yè)面所需的資源已經(jīng)預(yù)加載,在打開(kāi)階段,只需要拉取相應(yīng)的業(yè)務(wù)數(shù)據(jù)進(jìn)行渲染,大大提高了頁(yè)面的打開(kāi)速度。
在方案推出之前,我們將主要場(chǎng)景的FCP耗時(shí)嵌入點(diǎn)作為優(yōu)化基準(zhǔn)。上線后安卓平均耗時(shí)下降67%左右,iOS平均耗時(shí)下降70%左右,整體效果還可以。但是也有一些場(chǎng)景是骨架屏用不上的。例如,用戶在進(jìn)入后立即訪問(wèn)H5頁(yè)面。如果此時(shí)骨架屏還沒(méi)準(zhǔn)備好,體驗(yàn)會(huì)降級(jí)到優(yōu)化前。我們以后會(huì)繼續(xù)想辦法優(yōu)化這一塊。
5.3.穩(wěn)定性監(jiān)控
通過(guò)mPaaS埋點(diǎn)SDK統(tǒng)計(jì)埋點(diǎn)信息,通過(guò)mPaaS監(jiān)控后臺(tái)可以看到一些基礎(chǔ)數(shù)據(jù),同時(shí)這些埋點(diǎn)數(shù)據(jù)流會(huì)每日廣播到內(nèi)部監(jiān)控市場(chǎng)和釘釘集團(tuán),也可以通過(guò)麻雀的實(shí)時(shí)報(bào)告通道進(jìn)行ErroeBoundary/JSException實(shí)時(shí)廣播,如下圖:
5.4.R&D和交付效率
在三層架構(gòu)的實(shí)踐下,Que App可以保證R&D效率和交付效率。我們每周都會(huì)發(fā)布一個(gè)測(cè)試版,有可以快速滾動(dòng)測(cè)試版的小功能,測(cè)試版用戶可以給出積極的反饋。同時(shí),我們會(huì)每?jī)芍馨l(fā)布一個(gè)小版本,每個(gè)月發(fā)布一個(gè)大版本。
目前整個(gè)架構(gòu)的原生端比UI更注重服務(wù):Android和iOS原生實(shí)現(xiàn)的模塊基本都是面向服務(wù)的代碼,通過(guò)JSBridge暴露給上層。反應(yīng)方強(qiáng)調(diào)UI勝于服務(wù):它只是一個(gè)簡(jiǎn)單的渲染層。原生服務(wù)層的接口和能力穩(wěn)定后,變化不大,隨著業(yè)務(wù)的快速滾動(dòng),上層可以通過(guò)反應(yīng)式開(kāi)發(fā)和交付。
5.5.交付質(zhì)量
作為CI/CD的一部分,我們的QA同學(xué)提供了非常全面的自動(dòng)化測(cè)試。他們會(huì)每天進(jìn)行自動(dòng)化測(cè)試巡視,用最新的包進(jìn)行滾動(dòng)測(cè)試驗(yàn)證,同時(shí)出具報(bào)告。如果有問(wèn)題,他們會(huì)第一時(shí)間通知開(kāi)發(fā)同學(xué)修復(fù)。以下是自動(dòng)化測(cè)試報(bào)告的截圖:
6.總結(jié)和跟進(jìn)計(jì)劃
在日常開(kāi)發(fā)中,可能需要一些原生組件。常見(jiàn)的情況是,社區(qū)沒(méi)有它們,或者社區(qū)組件的功能與業(yè)務(wù)需求不匹配。比如語(yǔ)言Que App的底層基礎(chǔ)服務(wù)和UI組件都是Native提供的,語(yǔ)言Que現(xiàn)在提供99 Native JSBridge。還有一些UI組件需要Native提供雙端實(shí)現(xiàn),比如NebulaWebView、ImagePreviewer、PullToRefreshView等。: