語雀 App 跨端技術(shù)架構(gòu)實踐
?????♀? 編者按:本文作者是螞蟻集團前端工程師牧秦,介紹了語雀 App 在落地過程中的一些方案推演及架構(gòu)設(shè)計,以及跨端場景下的一些同構(gòu)開發(fā)實踐。

1. 語雀整體介紹
1.1. 簡單介紹
首先介紹一下語雀的整體情況,語雀是螞蟻集團推出的一款筆記與文檔知識庫的管理 & 協(xié)同工具,目前螞蟻集團和阿里集團內(nèi)部的員工大約有 10 萬多人日常也在使用這個工具,同時也在對外提供服務(wù)。
如下圖所示為語雀基于 Electron 推出的 Mac 和 Windows 桌面端,還有應(yīng)對移動端工作場景的小程序。另外,從去年開始我們著手開發(fā)了移動端 App,并于今年 2 月份順利發(fā)布上線。圖中右側(cè)展示的是 Android/iOS的 App 截圖。

1.2. 語雀整體技術(shù)選型
接下來我將介紹一下語雀的整體技術(shù)選型。語雀在內(nèi)部名稱為 Skylark,它的整個項目位于一個大型的 Codebase 中。其中的服務(wù)端、桌面端、移動端等所有代碼包含在這個 Codebase 中,如下所示:

其中服務(wù)端采用 Node.js、Egg.js,還有一些服務(wù)是基于 Java/Kotlin 開發(fā)的,但是這部分目前相對比較少。PC 端主要采用 React 技術(shù)棧,桌面端采用 React 和 Electron,小程序采用 React 以及 H5 離線包。大家可以發(fā)現(xiàn),整個技術(shù)是基于 JavaScript 或者TypeScript來做的。
整個語雀技術(shù)團隊沒有區(qū)分前后端或客戶端,大家都是全棧式地做產(chǎn)品研發(fā)。一個團隊成員可能既要完成一個需求的服務(wù)端部分,同時也要完成前端的代碼研發(fā)。在此基礎(chǔ)上我們進行了移動端 App 的設(shè)計和開發(fā),這部分內(nèi)容我們接下來將詳細介紹。
2. 語雀 App 架構(gòu)推演 & 設(shè)計
2.1. 阿里云 mPaaS
語雀 App 的架構(gòu)是如何設(shè)計的呢?首先整個集團已經(jīng)有一套比較穩(wěn)定的移動端框架 —— mPaaS,該框架是基礎(chǔ)的移動端開發(fā)框架,提供了豐富的移動端基礎(chǔ)能力,如:配置開關(guān)服務(wù)、Push服務(wù)、基于長連接的 Sync 服務(wù)、H5 容器&離線包、移動分析和移動網(wǎng)關(guān)等基礎(chǔ)服務(wù)。它現(xiàn)在在阿里云上也對外提供服務(wù),也有很多三方公司都基于這個框架來開發(fā)自己的 App:

2.2. 移動端架構(gòu)思路
在這樣的技術(shù)品牌打底的基礎(chǔ)上,我們進行了移動端的架構(gòu)設(shè)計。首先選用阿里云上 mPaaS 作為底層的框架基礎(chǔ),然后客戶端的 Native 框架層盡可能輕量、與業(yè)務(wù)解耦:

接下來針對渲染層,我們考慮動態(tài)或者Hybrid方案,比如要根據(jù)文檔閱讀頁或編輯器的業(yè)務(wù)特性來選用具體的框架。最后還要考慮同構(gòu)開發(fā),因為現(xiàn)在已經(jīng)有很多的多端業(yè)務(wù)模塊在運行中了,如果能夠?qū)崿F(xiàn)同構(gòu)開發(fā),那么這些代碼或者經(jīng)驗都可以被復(fù)用。
2.3. 移動端方案對比
下圖為移動端的各種開發(fā)框架對比,相信大家已經(jīng)在很多場合都看過了。其中基于 H5 Hybrid 的方案( H5 或者離線包),其性能稍微差一些。純 Native 的性能雖然很高,但是開發(fā)成本也相應(yīng)增加,上手也比較困難?,F(xiàn)在一些公司也有自己內(nèi)部的 Hybrid 開發(fā)方案。

2.4. 語雀的選擇
那么語雀是怎么選擇呢?首先我們希望語雀的體驗是比較流暢的,能夠貼近 Native App 的各種體驗;其次希望能做到一次編寫,雙端運行,兼顧開發(fā)效率;然后就是能夠兼具 Web 的研發(fā)體驗,使得團隊的 Web 技術(shù)棧的同學(xué)能夠快速參與進來開發(fā);最后是期望能夠同構(gòu)復(fù)用,這樣之前沉淀的經(jīng)驗、模塊可以直接復(fù)用。
綜合各種因素,結(jié)合實際的業(yè)務(wù)需求考慮,我們最終選擇了?ReactNative?作為業(yè)務(wù)層開發(fā)框架。
2.5. 語雀 App 架構(gòu)圖
接下來我為大家介紹一下語雀 App 的整體架構(gòu),如下圖所示。最底層是 mPaaS 的基礎(chǔ)設(shè)施,前面已經(jīng)提過了,其中包含了一些通用基礎(chǔ)組件。中間層是 Native 基礎(chǔ)能力層,這一層提供了基礎(chǔ)的賬號管理、統(tǒng)一資源管理、動態(tài)化能力、各種 Bridge 以及其他服務(wù)。

接下來我們可以看到兩個向上的箭頭,RCTBridge 負責(zé)把基礎(chǔ)服務(wù)通過 ReactNative Bridge 接口暴露給 ReactNative;JSBridge 則是將這些服務(wù)通過 H5 JSBridge 同步暴露給 H5。然后 ReactNative 和 H5 之間通過 RCTEvent 進行雙向通信。
ReactNative 層主要實現(xiàn)頁面生命周期、業(yè)務(wù)跳轉(zhuǎn)、頁面呈現(xiàn)等;H5 主要用來承載對更新速度等要求比較高的業(yè)務(wù)。再上層是 CI/CD 以及 DevTools、環(huán)境切換、單元測試等,充分支撐語雀的上層具體業(yè)務(wù)。
2.6. 三層架構(gòu)

如圖所示可以看出整體是一個三層架構(gòu)。在 Native 層我們采用 Kotlin/Swift 來實現(xiàn)。ReactNative 層采用 TypeScript/同構(gòu) Web 方式來實現(xiàn),這一層主要用來實現(xiàn)列表或者對流暢性、體驗要求比較高的頁面。而 H5 層主要結(jié)合語雀的具體業(yè)務(wù)特性來使用:比如要用ReactNative來實現(xiàn)一個富文本編輯器或者閱讀器,那么它的代價是非常高的,這種情況就使用 H5 來實現(xiàn);對頁面加載速度要求比較高的 H5 頁面,我們使用離線包來做頁面資源加速。
2.7. 通用 JSBridge 設(shè)計
接下來介紹一下通用的 JS bridge 設(shè)計,如下所示:

在 Native 層,RN 和 H5 共用一份 JS Bridge 實現(xiàn)。底層的一些通用服務(wù)能力,分別通過兩個 Bridge 暴露上去,可以讓 H5 和 ReactNative 直接調(diào)用。上圖右側(cè)展示了 H5 和 ReactNative 調(diào)用 Bridge 的代碼,其實除了第一行不一樣之外,后面的 Bridge 調(diào)用和結(jié)果處理都是一致的。
2.8. ReactNative ? H5 雙向消息流動
在業(yè)務(wù)開發(fā)中,不可避免會遇到 ReactNative 頁面和打開的 H5 頁面之間的交互和通信。我們研發(fā)了雙向消息流動機制,可以在兩側(cè)均使用 postMessage/onMessage 來收發(fā)消息。
比如在進入語雀富文本編輯頁面后,底部是一個 ReactNative View,上面嵌入了 WebView/WKWebView 用于展示編輯頁。這就涉及到了 H5 和 ReactNative 的雙向消息流動:

如上圖所示,藍色的箭頭是 H5 調(diào)用 ReactNative,紅色箭頭是 ReactNative 調(diào)用 H5,雙方通過 postMessage/onMessage 互通消息,并且支持 callback 處理回調(diào)。
3. 跨端同構(gòu)實踐
前面有提到,語雀 App 技術(shù)選型的時候就考慮到同構(gòu)開發(fā)。那么在語雀 App 中,有什么場景是涉及到同構(gòu)的呢?我們在具體的業(yè)務(wù)研發(fā)中,遇到了幾種典型的同構(gòu)場景,接下來簡單介紹一下同構(gòu)的實踐經(jīng)驗。
3.1. 基于 ViewModel 同構(gòu)列表頁
如下圖所示,我們看到一個Web 頁面,其中包含了一些列表,每個列表項包含操作選項入口,用戶點擊操作菜單后,可以對列表進行排序、篩選或者刪除等操作。小程序和移動端App 也是這樣,這三個頁面的共性就是它們都是一個列表頁,同時需要請求網(wǎng)絡(luò)加載數(shù)據(jù),另外可能還包括分頁操作等。當然這里沒有特別復(fù)雜的交互,點擊操作菜單可以進行刪除等操作。我們知道了這些共性,就可以方便的進行同構(gòu)開發(fā)。

具體列表頁怎么基于 ViewModel 進行同構(gòu)開發(fā)呢?如下圖,我們抽出通用的 ViewModel,在其中做 loading 管理、數(shù)據(jù)請求、用戶操作管理等。網(wǎng)絡(luò)請求管理可以通過 recentListAll 請求,通過 state 和 handlers 分別暴露數(shù)據(jù)和具體操作。這樣最后返回的是狀態(tài)集和一個操作列表:


在上層使用的時候,比如說我們使用這樣的 ViewModel 渲染的最終頁面時候,在各個平臺上只需重寫 UI 渲染部分,返回對應(yīng)平臺的 View/div 即可。通用的重邏輯、重請求的代碼都是同構(gòu)開發(fā)的。
3.2. Request 請求同構(gòu)
語雀業(yè)務(wù)研發(fā)中的網(wǎng)絡(luò)請求在接口層面是同構(gòu)的,這樣可以保證三端代碼和開發(fā)模式的一致性。具體同構(gòu)方式如下:
在 isomorphic 中定義通用的接口文件,其中使用 fetch/get/post/... 等方法發(fā)送請求;
在各個平臺上,實現(xiàn)具體的 fetch 接口。如:Web 側(cè)使用瀏覽器的 fetch 接口,小程序側(cè)可能調(diào)用 AlipayJSBridge,ReactNative 側(cè)則調(diào)用 LarkRCTBridge;
在構(gòu)建階段,通過 alias 將 request.js 在各個平臺進行重寫。

3.3. 消息三端同構(gòu)
接下來介紹一下另一個同構(gòu)場景:消息通知三端同構(gòu)。比如我們在 Web 側(cè)有這樣一個消息列表,只要別人關(guān)注了我或者評論了我的文檔,就會產(chǎn)生一個消息。小程序和 App 側(cè)的 UI 也是類似的。還有一個場景就是 App 消息推送,別人對我的文檔進行操作時,要在移動端設(shè)備系統(tǒng)通知欄展示出來:

那么在這種情況下我們該怎么做同構(gòu)開發(fā)呢?語雀現(xiàn)在有 70 多種消息,如果要為三端都寫一份實現(xiàn)的話,可能要寫 230 多次。這顯然是不現(xiàn)實的。此時就可以基于同構(gòu)的方案來做。同構(gòu)部分包括各種消息的同構(gòu) Builder 生成器,分別通過模板 Notification Context 構(gòu)建三端渲染結(jié)果:服務(wù)端得到消息字符串用于展示,在 H5 或者 Web 端渲染出原生的 div、span 等元素,在 ReactNative 側(cè)渲染出 View、Text 等 View。

舉一個例子,比如在下圖所示的 Context 上有一個 buildActor、buildSubject 和 buildLink 來渲染用戶頭像或者鏈接等。接下來服務(wù)端、小程序和 ReactNative 提供三個 Context 實現(xiàn)就可以了:

上面介紹的一些典型的同構(gòu)場景,在 App 開發(fā)中很好的驗證了架構(gòu)設(shè)計階段提出的同構(gòu)思路。在實際研發(fā)中,進行模塊、代碼的復(fù)用,對研發(fā)效率有很大提升。
4. 子應(yīng)用設(shè)計
在實際的業(yè)務(wù)研發(fā)中,有些業(yè)務(wù)模塊對更新時效性有較強的要求,另外技術(shù)側(cè)也希望一些業(yè)務(wù)模塊能夠做到獨立維護、獨立交付,同時能夠按需加載,降低主應(yīng)用負荷。在此基礎(chǔ)上,我們進行了子應(yīng)用的方案設(shè)計來滿足這種場景。
基于 ReactNative 的動態(tài)加載特性,我們設(shè)計了子應(yīng)用架構(gòu)。使得 App 中對更新率要求較高的場景,進行子應(yīng)用化動態(tài)加載。如下圖,RN 側(cè)主應(yīng)用代碼和子應(yīng)用代碼可以分別維護,子應(yīng)用可以隨 App 打包預(yù)置,也可以通過 CDN 進行動態(tài)下發(fā)加載,較好的滿足了語雀的業(yè)務(wù)場景。

5. 性能 & 穩(wěn)定性 & 交付
5.1. 性能調(diào)優(yōu)
我們在實際開發(fā)中,遇到了一些性能問題。首先是 App 啟動速度優(yōu)化,App 啟動時一般需要展示閃屏頁、隱私協(xié)議授權(quán)頁、權(quán)限授予等,我們將閃屏頁和主 Activity 合二為一,同時采用 Pipeline 方案定制啟動細節(jié),最終啟動速度得到了較好的優(yōu)化。
第二個就是小記編輯器啟動速度優(yōu)化。前面說到編輯器是利用 H5 來做的,但是如果用戶有了靈感,想要做一些記錄,如果編輯器啟動慢,那么用戶是很惱火的。為此,我們對小記做了一個單獨的編輯器,通過離線包將其預(yù)置到客戶端,或者通過動態(tài)下發(fā)的方式更新,能夠第一時間快速拉起。
另外,我們還進行了 WebView 的加載優(yōu)化,對一些通用 JS 和 CSS 做了一些資源包的預(yù)加速。結(jié)合 WebView 預(yù)創(chuàng)建 + 循環(huán)復(fù)用的方案,對 App 端內(nèi) H5 頁面的打開速度,進行了較大幅度的提升。

5.2. WebView 預(yù)創(chuàng)建 + 循環(huán)復(fù)用
語雀 App 中,像文檔閱讀頁、編輯頁等復(fù)雜頁面均是 H5 實現(xiàn)。用戶一直反饋在端內(nèi)打開速度很慢,經(jīng)過我們實測,在 Android 較差的機型上,打開一篇文檔耗時有時達到 5s 左右!我們結(jié)合業(yè)務(wù)特點,采用了 WebView 預(yù)創(chuàng)建 + 循環(huán)復(fù)用的方案,對 H5 的打開性能有了較大幅度的提升。
如下圖所示,App 啟動后,啟動 WebView 預(yù)創(chuàng)建池,預(yù)創(chuàng)建出離屏 H5 骨架屏 —— 該骨架屏是一個單頁應(yīng)用,包含了閱讀頁、新建頁、個人頁等端內(nèi)高頻使用的頁面。在用戶打開相應(yīng)頁面時,直接從池中取出,識別出打開場景后,進行 replace 路由切換即可。由于頁面所需的資源已經(jīng)預(yù)先加載好,在打開階段,只需拉取對應(yīng)的業(yè)務(wù)數(shù)據(jù)渲染即可,大大提升了頁面的打開速度。

在方案上線前,我們對主要的場景做了 FCP 耗時埋點作為優(yōu)化基準,上線后 Android 平均耗時整體降低?67%?左右,iOS 平均耗時整體降低?70%?左右,整體效果還可以。不過有一些場景無法用到骨架屏,比如用戶進入后立即訪問 H5 頁面,如果這時候骨架屏還沒有 ready,那么體驗會降級到未優(yōu)化前,這塊我們后面會繼續(xù)想辦法進行優(yōu)化。
5.3. 穩(wěn)定性監(jiān)控
通過 mPaaS 埋點 SDK 統(tǒng)計埋點信息,我們通過 mPaaS 監(jiān)控后臺能夠看到一些基礎(chǔ)數(shù)據(jù),同時會把這些埋點數(shù)據(jù)流到內(nèi)部監(jiān)控大盤、釘釘群日常播報,也可以通過語雀實時上報通道進行ErroeBoundary/JSException實時播報,具體如下所示:
5.4. 研發(fā) & 交付效率
語雀 App 在三層架構(gòu)在實踐下,較好的保證了的研發(fā)效率和交付效率。我們每周會發(fā)布一個內(nèi)測版本,小功能能夠快速滾動內(nèi)測發(fā)布,內(nèi)測用戶也能夠積極反饋。同時,我們每兩周會發(fā)布一個小版本,每個月可以發(fā)布一個大版本。
現(xiàn)在整個架構(gòu)的 Native 側(cè)是重服務(wù)輕UI:Android和 iOS Native 實現(xiàn)的模塊,基本上全是服務(wù)型的代碼,通過JSBridge 暴露給上層。ReactNative 側(cè)則是重 UI 輕服務(wù):它只是做一個單純的渲染層,Native 服務(wù)層接口 & 能力穩(wěn)定之后,很少變動,上層就可以用 ReactNative 隨著業(yè)務(wù)快速滾動開發(fā)交付。
5.5. 交付質(zhì)量
交付質(zhì)量這塊,作為 CI/CD 的一部分,我們的 QA 同學(xué)提供了非常充分的自動化測試,每天會進行自動化測試巡檢,用最新的包進行滾動測試驗證,同時出具報告,有問題的情況會第一時間通知開發(fā)同學(xué)修復(fù)。如下圖是自動化測試報告的截圖:
6. 總結(jié) & 后續(xù)計劃
日常開發(fā)中,可能需要一些 Native 組件,常見的情況是,社區(qū)沒有或者社區(qū)組件能力不匹配業(yè)務(wù)需求。比如語雀 App 底層的基礎(chǔ)服務(wù)、UI 組件都是由 Native 提供的,語雀現(xiàn)在提供了 99 個Native JSBridge。還有一些 UI 組件需要 Native 提供雙端的實現(xiàn),比如 NebulaWebView、ImagePreviewer、PullToRefreshView 等。
最后做一個簡單的小結(jié),我們提出三層架構(gòu)的最主要思路有三個具體的思考點:一是結(jié)合業(yè)務(wù)的特點和技術(shù)儲備去設(shè)計整體架構(gòu);另外就是可以盡量復(fù)用手頭或者已有的組件或者社區(qū)組件;第三個是結(jié)合語雀現(xiàn)有的代碼情況和團隊整體研發(fā)匹配情況。
在整個過程中,我們提出的同構(gòu)設(shè)計思路和開發(fā)模式,在開發(fā)實踐中得以充分使用,可以說到了較好的驗證。后續(xù)我們將持續(xù)關(guān)注性能和體驗,比如將跟進社區(qū),升級 Hermes 引擎和 Fabric 架構(gòu),同時持續(xù)進行性能優(yōu)化。歡迎下載語雀 App 進行體驗?。ǖ刂罚篽ttps://www.yuque.com/download)