全民K歌跨端體系建設(shè)
“作者: Edwiin,原文鏈接:https://xie.infoq.cn/article/e284b3a19e7937dc209a3d345
大廠技術(shù) 高級(jí)前端 Node進(jìn)階
點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)
回復(fù)1,加入高級(jí)Node交流群
1. 背景
1.1 移動(dòng)端技術(shù)演進(jìn)
跨端技術(shù)的本質(zhì)是實(shí)現(xiàn)代碼復(fù)用,減少開發(fā)者在多平臺(tái)上的適配工作量,移動(dòng)互聯(lián)網(wǎng)發(fā)展至今,跨端技術(shù)經(jīng)歷了許多階段,大體上可以分成如下四類:
-
最早是通過 H5 來(lái)實(shí)現(xiàn)跨瀏覽器的頁(yè)面渲染; -
移動(dòng)互聯(lián)網(wǎng)發(fā)展,催生了越來(lái)越多調(diào)用到原生能力的場(chǎng)景,于是步入了 Hybrid 技術(shù)階段; -
緊接著為了解決渲染性能問題,又催生了像 RN/Weex 這類的 Native 容器方案; -
再往下一步走,就是近年來(lái)聊得比較火熱的 Flutter,以及相關(guān)的一些自繪制能力的渲染技術(shù);
從整個(gè)變遷過程中我們會(huì)發(fā)現(xiàn),跨端技術(shù)的演進(jìn)實(shí)際上是以 H5 代表的效率、動(dòng)態(tài)性逐步遷往 Native 代表的性能體驗(yàn)的過程,并在整個(gè)過程中不斷尋找兩者間的平衡點(diǎn)。
1.2 主流方案對(duì)比
接著我們?cè)賮?lái)看看目前主流的 Native 容器與自繪引擎方案的對(duì)比。
Native 容器方案
市面上 Native 容器方案最具代表的框架當(dāng)屬 Weex 和 RN 兩類,對(duì)于這類框架而言,原理都大同小異,
他們其實(shí)由三部分組成,分別是 JS,Native 以及中間的 Bridge 層,JS 層通常是我們熟知的 Vue / React 框架,他其實(shí)會(huì)把需要生成的節(jié)點(diǎn)信息通過 Bridge 層序列化后轉(zhuǎn)發(fā)到 Native 層進(jìn)行解析,并最終渲染為 Native 的組件,這是為什么 Native 容器方案性能比 H5 要好的一個(gè)重要的原因。
自繪制引擎
而對(duì)于自繪引擎方案,目前最具話題與代表性的是 Google 的 Flutter 框架,它重寫了一套跨平臺(tái)的 UI 框架,包括 UI 組件,
渲染引擎以及開發(fā)語(yǔ)言,渲染引擎基于 Skia 圖形庫(kù),而依賴系統(tǒng)部分就僅有圖形繪制相關(guān)的接口,可以最大程度上保證跨平臺(tái)的一致性,同時(shí)也意味著不需要再像 RN 那樣經(jīng)過一層 Bridge 進(jìn)行 JS 和 Native 的通信數(shù)據(jù)轉(zhuǎn)發(fā),渲染效率更高一籌,另外其上次基于 Dart 語(yǔ)言開發(fā),在執(zhí)行效率上也比 JS 要高效得多。
小結(jié)
兩種類型的框架就目前而言沒有誰(shuí)優(yōu)誰(shuí)劣,對(duì)于 Native 容器方案來(lái)說(shuō),他更多的是代表了研發(fā)效率與動(dòng)態(tài)性;對(duì)于 Flutter 這類框架而言,它雖然實(shí)現(xiàn)了高性能,但是卻缺少一套較為完備的動(dòng)態(tài)化方案。
PS: 這里需要提一嘴,RN 目前正在把 JsBridge 的架構(gòu)重構(gòu)為 JSI,通過 JSI 架構(gòu)可以省去 JsBridge 的異步序列化操作,節(jié)省了成本,提高了效率,目前還在研發(fā)當(dāng)中。
1.3 技術(shù)選型
對(duì)于該使用哪種跨端技術(shù)作為產(chǎn)品的接入方案,在選型上我們應(yīng)該考量到以下幾點(diǎn):
-
從產(chǎn)品角度,需要考慮迭代情況以及使用場(chǎng)景,比方說(shuō)像直播這種類型就更適合用 Native 而不是任何一種跨端技術(shù)實(shí)現(xiàn); -
從接入成本,需要看看接入這項(xiàng)技術(shù)所帶來(lái)的成本,比如團(tuán)隊(duì)的技術(shù)儲(chǔ)備,是否有相關(guān)人力,比如客戶端來(lái)支持,遇到問題依靠現(xiàn)有人力和生態(tài)是否能 hold 住; -
從研發(fā)效率,怎么實(shí)現(xiàn)最大化的代碼復(fù)用,以及建設(shè)相關(guān)的開發(fā)調(diào)試工具鏈,提高開發(fā)效率,是應(yīng)該考慮的問題之一; -
從性能體驗(yàn),跨端技術(shù)通常是通過犧牲部分體驗(yàn)換來(lái)效率提升,所以體驗(yàn)比起 Native 還是要差一些,這也是情理之中,但如何通過建設(shè)配套工具和針對(duì)性專項(xiàng)優(yōu)化也需要重點(diǎn)考慮進(jìn)來(lái)。
1.4 K 歌現(xiàn)狀
對(duì) K 歌來(lái)說(shuō),通過一段時(shí)間的調(diào)研,實(shí)踐和風(fēng)險(xiǎn)考慮,最終主要采用了騰訊內(nèi)部自研的類 RN 框架 - Hippy,目前在站內(nèi)已開發(fā)超過 200+的業(yè)務(wù),覆蓋了像點(diǎn)歌臺(tái),任務(wù)頁(yè)這類一級(jí)入口,同時(shí)包含 Poplayer 和游戲化的場(chǎng)景,日均 PV 直達(dá) 2.9 億,覆蓋了 41%+ 的主業(yè)務(wù)場(chǎng)景。作為 Hippy 最早的接入團(tuán)隊(duì)之一,在廣泛應(yīng)用的同時(shí),我們還深挖了框架技術(shù)痛點(diǎn),并深度參與到開源共建中。
同時(shí)我們也有投入使用 Flutter 技術(shù)棧,目前主要是在應(yīng)用在創(chuàng)新項(xiàng)目當(dāng)中。
2. K 歌跨端體系建設(shè)
2.1 體系建設(shè)
跨端技術(shù)的實(shí)踐往往需要一系列的配套建設(shè),以及針對(duì)性的性能優(yōu)化,下面是 K 歌在跨端上的體系建設(shè),主要分為四個(gè)部分:
-
開發(fā)支持,這里主要有開發(fā)調(diào)試使用到的工具鏈,組件文檔,以及與客戶端約定好的標(biāo)準(zhǔn)規(guī)范等; -
發(fā)布部署,包括 UI 自動(dòng)化測(cè)試,Bundle 的包管理以及加載策略; -
質(zhì)量監(jiān)控,主要是一些質(zhì)量指標(biāo)維度的制定以及配套的監(jiān)控系統(tǒng); -
性能優(yōu)化,包括在具體實(shí)踐過程中遇到的性能問題,以及解決方案,包括 Bundle 拆分,包內(nèi)置,秒開率及穩(wěn)定性的優(yōu)化等;
2.2 開發(fā)調(diào)試
為了提升開發(fā)聯(lián)調(diào)效率,除了框架本身提供的開發(fā)調(diào)試手段外,我們還沉淀了自己的一些工具和文檔,在新增一個(gè)接口協(xié)議后并提交后,觸發(fā)鉤子構(gòu)建,自動(dòng)生成接口的文檔及調(diào)試工具。
同時(shí)為了方便開發(fā)過程中日志信息的查看,我們也開發(fā)了一個(gè)類 RN 版的 vConsole 工具,可對(duì)輸出及網(wǎng)絡(luò)流水進(jìn)行攔截。
2.3 接口規(guī)范化
在與客戶端同學(xué)接口聯(lián)調(diào)時(shí),經(jīng)常出現(xiàn)三端沒有對(duì)齊的情況,比如輸入輸出不一致的問題。由此提出了接口標(biāo)準(zhǔn)化的方案,通過在一個(gè)接口模板中約定好接口的規(guī)范,包括接口名,具體的輸入和輸出,再自動(dòng)生成三端的接口模板,并在各端完成邏輯補(bǔ)齊、代碼提交后自動(dòng)打包推送到各自倉(cāng)庫(kù),大大提高了聯(lián)調(diào)的效率,目前該方案已應(yīng)用到 k 歌國(guó)內(nèi)版,極速版以及 Flutter 的項(xiàng)目中。
3. K 歌跨端實(shí)踐優(yōu)化
3.1 能力擴(kuò)展
首先是能力擴(kuò)展部分,原框架提供的能力往往不能滿足和覆蓋我們復(fù)雜的業(yè)務(wù)場(chǎng)景,為此我們根據(jù)業(yè)務(wù)需要和優(yōu)先級(jí)梳理了一套符合 W3C 規(guī)范的 WebApi,依靠框架提供的插件能力擴(kuò)展了所需要的功能,如觸摸事件,Performance 等原生接口能力,又如 Canvas, Audio 等原生組件和還有漸變色,魔法色等樣式能力支持。
其次 App 內(nèi)將常駐一個(gè) Master 的無(wú)界面實(shí)例,這實(shí)際上是一個(gè)后臺(tái)的業(yè)務(wù),會(huì)在 App 啟動(dòng)后自動(dòng)加載,主要是服務(wù)于兩類任務(wù),一種是在 App 啟動(dòng)時(shí)做一些初始化的操作,同時(shí)會(huì)啟動(dòng)一個(gè)輪詢?nèi)蝿?wù),定時(shí)去拉取業(yè)務(wù)包的配置信息,實(shí)現(xiàn)業(yè)務(wù)包的下載管理,這個(gè)在文章后面也會(huì)提及;另外是實(shí)例間的通訊,在歌房和直播間的場(chǎng)景下,可能同時(shí)掛載多個(gè) Hippy 業(yè)務(wù),而業(yè)務(wù)之間往往有通信的需求,針對(duì)這種場(chǎng)景,我們通過在 Native 實(shí)現(xiàn)一個(gè)中轉(zhuǎn)站對(duì)業(yè)務(wù)間的數(shù)據(jù)進(jìn)行實(shí)時(shí)轉(zhuǎn)發(fā),以此完成業(yè)務(wù)實(shí)例間通訊的需求。
另外團(tuán)隊(duì)與內(nèi)部設(shè)計(jì)團(tuán)隊(duì)約定了組件的設(shè)計(jì)規(guī)范,并拉通客戶端同學(xué),最終沉淀出三端通用的 UI 組件庫(kù),包含多種通用組件與業(yè)務(wù)組件,目前已在業(yè)務(wù)中廣泛接入與使用,通過規(guī)范化,極大提升了組件復(fù)用性,同時(shí)也降低業(yè)務(wù)的開發(fā)成本。
3.2 性能優(yōu)化
下面將從以下幾個(gè)維度對(duì)頁(yè)面性能進(jìn)行分析與優(yōu)化。
3.2.1 加載速度優(yōu)化
首先我們來(lái)看看加載速度方面的優(yōu)化,在這之前可以先了解一個(gè) Hippy 業(yè)務(wù)的加載鏈路:一個(gè)業(yè)務(wù)在用戶點(diǎn)擊入口時(shí),先會(huì)經(jīng)過 JS 引擎的初始化,接著是 JsBundle 文件的加載,在文件加載后客戶端會(huì)發(fā)送一個(gè) loadInstance 的事件通知前端完成業(yè)務(wù)注冊(cè),與此同時(shí)會(huì)創(chuàng)建一個(gè) view 節(jié)點(diǎn)就緒,隨后前端會(huì)通過一系列的異步請(qǐng)求完成整個(gè)頁(yè)面的渲染。
在這個(gè)鏈路里面我們把引擎初始化到第一個(gè) view 節(jié)點(diǎn)創(chuàng)建結(jié)束的這段時(shí)間成為首幀耗時(shí),而此后直到頁(yè)面渲染完成的時(shí)間段稱之為異步加載耗時(shí)。由此可見,加載耗時(shí)也可以拆分成三部分,分別是引擎初始化耗時(shí),JSBundle 耗時(shí)和請(qǐng)求的耗時(shí),針對(duì)這三個(gè)細(xì)化的指標(biāo),也有相應(yīng)的優(yōu)化方案,分別是引擎的復(fù)用,包拆分、裁剪和預(yù)請(qǐng)求等優(yōu)化。
引擎復(fù)用
先看看引擎復(fù)用部分,在未做優(yōu)化之前,一個(gè)業(yè)務(wù) bundle 的加載需要開啟一個(gè) JS 的引擎,而一個(gè) JS 引擎初始化時(shí)間大概是在 500ms 左右;而在實(shí)際應(yīng)用中,可能存在多業(yè)務(wù)并行的場(chǎng)景,隨著業(yè)務(wù)加載數(shù)量的增加,我們需要開啟越來(lái)越多的 JS 引擎,由于每次初始化引擎都需要耗費(fèi)耗時(shí),這樣會(huì)直接導(dǎo)致頁(yè)面耗時(shí)增加,此外也占用了不少的客戶端的 CPU 和內(nèi)存等資源。
對(duì)于這個(gè)問題,我們首先想到的是用引擎池的方案,客戶端會(huì)預(yù)先創(chuàng)建兩個(gè)引擎作為備用,業(yè)務(wù)開始加載時(shí),會(huì)優(yōu)先使用緩存池中的已經(jīng)創(chuàng)建好的引擎,當(dāng) 2 個(gè)引擎不滿足場(chǎng)景時(shí)才開始創(chuàng)建下一個(gè)引擎。這種方案可以一定程度上解決引擎初始化耗時(shí)問題,但還不夠極致。我們?cè)谙朊總€(gè)業(yè)務(wù)新起一個(gè)引擎的目的究竟是什么?其實(shí)只是為了做業(yè)務(wù)的隔離,因?yàn)槲覀兪褂昧?V8 作為 JS 的引擎,在 V8 中其實(shí)是可以通過單引擎多 context 方式進(jìn)行業(yè)務(wù)隔離,所以我們?cè)谧钚碌臉I(yè)務(wù)實(shí)踐中也采用了這樣的方式來(lái)實(shí)現(xiàn)引擎的復(fù)用,從而減少初始化的耗時(shí)。
Bundle 包優(yōu)化
接著看看 bundle 包優(yōu)化,這里其實(shí)可以從兩方面著手,分別是體積優(yōu)化與下載耗時(shí)優(yōu)化,具體使用到的措施有如下幾點(diǎn):
-
JS 拆包,采用了業(yè)界比較通用的做法,將前端的 JS 包拆分成 Base bundle 和業(yè)務(wù) bundle 兩部分,并把 base bunble 內(nèi)置于客戶端中,以此減少包體積與下載時(shí)長(zhǎng); -
業(yè)務(wù)優(yōu)化,不少業(yè)務(wù)中使用了很多 Base64 的圖片,會(huì)對(duì)業(yè)務(wù)包的體積造成一定影響,這里可以只保留必要的小圖 Base64 圖片,其他均采用外鏈方式,同時(shí)可以通過業(yè)務(wù)代碼重構(gòu),Tree-Shaking 等方式減少一些冗余代碼; -
Chunk 包加載,原框架不支持 Chunk 包加載,通過在 Native 層實(shí)現(xiàn) Chunk 包的緩存與加載,并將非首屏/核心資源抽離成 Chunk 包異步加載,從而實(shí)現(xiàn)首屏 Bundle 體積的減少; -
增量更新,在構(gòu)建階段通過與上次編譯結(jié)果的對(duì)比,實(shí)現(xiàn)了差分包的拆解,使得在業(yè)務(wù)包下載任務(wù)中只需要下載差分包,并在后臺(tái)合成,以此減少 Bundle 包下載耗時(shí)。
預(yù)請(qǐng)求優(yōu)化
再往下是預(yù)請(qǐng)求部分,上面我們提到一個(gè)業(yè)務(wù)中異步請(qǐng)求的耗時(shí)也是影響首屏渲染速度的一個(gè)重要因素,對(duì)此我們采用了預(yù)請(qǐng)求的優(yōu)化手段。
回顧及細(xì)化之前的加載鏈路,在前端業(yè)務(wù)響應(yīng)之前其實(shí)會(huì)經(jīng)過引擎初始化和業(yè)務(wù) bundle 的下載及加載的過程,在完成這兩個(gè)過程之后才能發(fā)起接口請(qǐng)求進(jìn)行頁(yè)面的渲染,如果我們把請(qǐng)求提前至這幾個(gè)過程之前,或者說(shuō)跟他們的加載時(shí)機(jī)并行跑,則會(huì)節(jié)省掉很多的耗時(shí)。事實(shí)上我們的預(yù)請(qǐng)求優(yōu)化就是這么做的,在加載業(yè)務(wù)之前,引擎通過業(yè)務(wù) url 上開關(guān)參數(shù)判斷出需要發(fā)起預(yù)請(qǐng)求,于是會(huì)讀取配置并發(fā)起一個(gè)請(qǐng)求,當(dāng)前端業(yè)務(wù)被喚起后,通過客戶端提供的接口我們可以獲取緩存好的請(qǐng)求結(jié)果,從而減少請(qǐng)求的耗時(shí)。
關(guān)于預(yù)請(qǐng)求的配置我們通過一個(gè) json 配置保存起來(lái),并與業(yè)務(wù) bundle 一起打包到 Zip 包里,里面主要約定了具體請(qǐng)求的命令字,請(qǐng)求參數(shù),以及請(qǐng)求 Id 等信息,同時(shí)為了在請(qǐng)求參數(shù)中傳送一些動(dòng)態(tài)的信息,比如登錄態(tài)中的 uid,url 上的參數(shù)信息等,我們也與客戶端同學(xué)約定了一些全局的變量定義以及獲取動(dòng)態(tài)值的語(yǔ)法等。
3.2.2 卡頓優(yōu)化
Native 分析工具
對(duì)于 K 歌所使用到的 Hippy 框架,或者是 RN/Weex 這類的跨端框架,其實(shí)最終還是會(huì)渲染成 Native 的原生組件,所以我們也可以借助客戶端的相關(guān)工具排查卡頓性能,比如以 Android 為例,可以通過【設(shè)置】【開發(fā)者調(diào)試選項(xiàng)】中的【GPU 條形模式分析】得到頁(yè)面的 FPS 繪制效率,iOS 則可以使用 xcode 的 instruments 工具排查卡頓性能。
左上圖為安卓的 GPU 分析案例,可以看到截圖最下方是一條綠線,代表的是 16ms 的閾值,超過了這個(gè)界限則表示當(dāng)前幀繪制的時(shí)間出現(xiàn)了延遲,卡頓的現(xiàn)象,而圖中具體的顏色值所代表的含義在右邊這張圖中我們也可以找到,如果以該案例來(lái)看,所處第二個(gè)顏色,也就是 Layout Measure 的處理耗時(shí)太多,表示當(dāng)前觸發(fā) onLayout 和 onMeasure 的回調(diào)次數(shù)太多,由此可以推斷出頁(yè)面層級(jí)可能比較復(fù)雜。
有了這個(gè)結(jié)論后,再往下就可以通過過度繪制檢查工具來(lái)排查頁(yè)面的層級(jí),如圖中所示,綠色部分表示層級(jí)簡(jiǎn)單,紅色則表示嵌套層級(jí)比較深,那么在后續(xù)就可以做針對(duì)性的優(yōu)化了。
前端通信分析
對(duì)于 Hippy 這類跨端框架而言,前端最終傳遞給客戶端的是抽象的節(jié)點(diǎn)以及響應(yīng)的操作行為,當(dāng)客戶端通過 UIManager 模塊接收這些信息,對(duì)其進(jìn)行整合,并轉(zhuǎn)化為一系列的操作指令,最終渲染到 Native 中。
在前端部分,可以通過攔截 js2native 的通信數(shù)據(jù)來(lái)排查卡頓問題,比方說(shuō)在列表場(chǎng)景下,會(huì)存在同時(shí)傳遞多個(gè)節(jié)點(diǎn)信息,這樣會(huì)導(dǎo)致頻繁的 js2native 的通信,對(duì)幀率也會(huì)造成影響。
問題及解決方案
通過結(jié)合 Native 與 JS 的排查手段,可以推斷卡頓的問題可能有如下幾類問題產(chǎn)生,分別是:
-
層級(jí)復(fù)雜,處理任務(wù)過多; -
頻繁的 js2native 的通信; -
js2native 通信數(shù)據(jù)更大導(dǎo)致通信延遲;
針對(duì)這類問題常用的解決方案分別是:
-
減少頁(yè)面層級(jí),前端同學(xué)由于開發(fā)背景的原因,往往會(huì)忽略層級(jí)嵌套的問題,導(dǎo)致性能問題,這個(gè)需要在開發(fā)側(cè)注意,盡量減少組件層級(jí)的嵌套; -
對(duì)于頻繁 js2native 的通信,我們嘗試在前端 SDK 層面做處理,在一個(gè)微任務(wù)里對(duì)節(jié)點(diǎn)的操作進(jìn)行合并,再統(tǒng)一傳到 Native,這樣可減少 js2native 的操作數(shù)量; -
對(duì)于節(jié)點(diǎn)數(shù)據(jù)問題,通過模版化的思想將節(jié)點(diǎn)里面一些公共的屬性和數(shù)據(jù)進(jìn)行模版化的操作,并沉淀了高性能的列表組件。 -
另外在必要的時(shí)候還需要推進(jìn)客戶端的同學(xué)進(jìn)行具體問題分析,比如之前我們就遇到過列表里重復(fù)對(duì) Base64 圖片進(jìn)行解碼的問題,導(dǎo)致 FPS 數(shù)據(jù)下降,后面的解決方案是推進(jìn)客戶端進(jìn)行 Base64 圖片緩存,以此提高繪制效率。
3.2.3 成功率優(yōu)化
接著是成功率優(yōu)化,這里主要指的是業(yè)務(wù)的加載成功率,由于業(yè)務(wù)最終還是通過網(wǎng)絡(luò)分發(fā),在業(yè)務(wù)加載時(shí),為了減少業(yè)務(wù)包的網(wǎng)絡(luò)下載耗時(shí)或者由于網(wǎng)絡(luò)問題而導(dǎo)致下載失敗的問題,在這里團(tuán)隊(duì)結(jié)合了內(nèi)置包和緩存包兩種方式來(lái)保證業(yè)務(wù)的加載成功率。
內(nèi)置包
內(nèi)置包作為極端網(wǎng)絡(luò)情況下的保底方案,適用于 App 內(nèi)的一級(jí)入口頁(yè)面,如點(diǎn)歌臺(tái),歌房 Tab 等業(yè)務(wù),保證在失去網(wǎng)絡(luò)時(shí)仍能打開這類業(yè)務(wù),它的具體更新流程如下:通過在一個(gè)特定的倉(cāng)庫(kù)下維護(hù)了一份業(yè)務(wù)的版本配置文件,這里提供了手工錄入和定時(shí)拉取外網(wǎng)最新穩(wěn)定版本兩種方式來(lái)修改這份配置文件,而當(dāng)該配置文件完成修改,并通過 Review 后,將觸發(fā) WebHook 鉤子通知構(gòu)建系統(tǒng),此時(shí)構(gòu)建會(huì)去拉取配置文件中的業(yè)務(wù)版本信息,并下載下來(lái)放到客戶端指定的本地目錄中,再向客戶端倉(cāng)庫(kù)發(fā)起新的 MR,通過客戶端人員的 CR 后內(nèi)置包即可更新,并在下次發(fā)布 AppStore 或應(yīng)用市場(chǎng)時(shí)將最新的內(nèi)置包帶到外網(wǎng)中。
緩存包
接著再來(lái)看看緩存包部分,由于內(nèi)置包集成會(huì)增加客戶端的安裝包體積,并不適合所有的 Hippy 業(yè)務(wù),這時(shí)候緩存包就可以發(fā)揮作用了。同時(shí)由于業(yè)務(wù)頻繁迭代發(fā)布的特性,如何保證外網(wǎng)可以較實(shí)時(shí)地拉取,也需要一套更新機(jī)制,前面我們有提及 APP 啟動(dòng)后會(huì)加載一個(gè)無(wú)界面的實(shí)例,該實(shí)例會(huì)在加載完畢后啟動(dòng)一個(gè)輪詢?nèi)蝿?wù),時(shí)間間隔為 2min,在這個(gè)過程中將會(huì)實(shí)時(shí)地拉取外網(wǎng)的業(yè)務(wù)版本配置,并與本地的配置進(jìn)行比對(duì),如果發(fā)現(xiàn)版本有更新,將會(huì)下載最新的業(yè)務(wù)包,并更新到本地緩存中,提高緩存命中率。
小結(jié)
針對(duì)成功率優(yōu)化部分,我們通過結(jié)合內(nèi)置包和緩存包機(jī)制來(lái)完成,這里的優(yōu)先級(jí)是優(yōu)先使用緩存包,因?yàn)榈孟缺WC命中最新的版本,而當(dāng)緩存失效或者不存在緩存包時(shí)才會(huì)命中內(nèi)置包,只有當(dāng)內(nèi)置包不存在時(shí),才會(huì)實(shí)時(shí)去外網(wǎng)拉取并下載最新包,以此提升成功率,再結(jié)合外網(wǎng)緩存命中率和 Crash 告警,實(shí)時(shí)監(jiān)聽外網(wǎng)成功率變化。
3.2.4 優(yōu)化效果
另外我們還做了其他的一些優(yōu)化,比如在用戶體驗(yàn)部分,我們也采用了骨架屏的方式減少用戶肉眼等待時(shí)間;內(nèi)存部分,通過前端業(yè)務(wù)側(cè)和客戶端的圖片緩存,按容器尺寸渲染等方式減少了內(nèi)存消耗;針對(duì) CPU/GPU 優(yōu)化部分,我們與官方的團(tuán)隊(duì)保持密切的聯(lián)系,并通過推動(dòng)優(yōu)化 SDK 升級(jí)等方式解決。
通過結(jié)合加載速度,卡頓和成功率三個(gè)緯度的優(yōu)化,整體下來(lái)目前取得了不錯(cuò)的效果,其中成功率已達(dá) 3 個(gè) 9,主業(yè)務(wù) FPS 達(dá) 54+,這兩個(gè)指標(biāo)均與 Native 對(duì)齊,另外整體秒開率方面對(duì)比優(yōu)化前有了 5% 以上的提升,達(dá)到 85%+。
3.3 質(zhì)量監(jiān)控
3.3.1 自動(dòng)化測(cè)試
下一部分是質(zhì)量監(jiān)控部分,這里我們與測(cè)試團(tuán)隊(duì)一起打通了自動(dòng)化的測(cè)試流程,利用測(cè)試同學(xué)提供的測(cè)試機(jī)與埋點(diǎn)服務(wù),保障業(yè)務(wù)發(fā)布前的穩(wěn)定性,具體的流程如下:
-
首先是前端同學(xué)觸發(fā)一個(gè)業(yè)務(wù)的構(gòu)建,當(dāng)業(yè)務(wù)構(gòu)建成功后會(huì)同步至我們的虛擬或體驗(yàn)環(huán)境; -
當(dāng)業(yè)務(wù)包在環(huán)境同步結(jié)束之后,構(gòu)建系統(tǒng)會(huì)向測(cè)試同學(xué)所提供的服務(wù)觸發(fā)一個(gè)鉤子,并附帶環(huán)境,業(yè)務(wù)名與具體的版本號(hào)信息; -
測(cè)試服務(wù)收到這些信息之后,通過腳本啟動(dòng)真機(jī)群,并在上面運(yùn)行指定環(huán)境與版本的業(yè)務(wù),通過埋點(diǎn)信息記錄業(yè)務(wù)的性能數(shù)據(jù),包括首幀,秒開率等等指標(biāo); -
自動(dòng)化測(cè)試流程結(jié)束之后會(huì)回調(diào)給我們的構(gòu)建機(jī),構(gòu)建機(jī)發(fā)起微信/郵件的結(jié)果推送,并記錄保存該任務(wù)的測(cè)試結(jié)果; -
到業(yè)務(wù)的發(fā)布階段,通過上面保存的測(cè)試結(jié)果,可以對(duì)業(yè)務(wù)的發(fā)布進(jìn)行二次確認(rèn)或阻斷。
3.3.2 監(jiān)控系統(tǒng)
監(jiān)控指標(biāo)梳理
在做監(jiān)控之前,我們首先對(duì)監(jiān)控上報(bào)的指標(biāo)進(jìn)行了梳理和統(tǒng)一,大致可分為 5 大類,首先是性能緯度,這里包含了業(yè)務(wù)運(yùn)行時(shí)所占用的內(nèi)存,CPU,GPU,F(xiàn)PS,引擎耗時(shí)和首幀耗時(shí)等等;然后是業(yè)務(wù) bundle 緯度,包括了業(yè)務(wù)的下載時(shí)間,加載時(shí)間和包體積大小等;緊接著是接口部分,包括了客戶端和后臺(tái)接口的調(diào)用,具體指標(biāo)有調(diào)用/請(qǐng)求耗時(shí),返回碼,通信數(shù)據(jù)大小等;再往下是錯(cuò)誤緯度,包括 JS 異常,業(yè)務(wù)運(yùn)行時(shí)的 Native Crash 等;最后還有一些大圖檢測(cè)和進(jìn)程通信等等上報(bào)。
監(jiān)控系統(tǒng)
最后是監(jiān)控系統(tǒng)部分,其實(shí)在之前很長(zhǎng)一段時(shí)間內(nèi)我們內(nèi)部有使用過多個(gè)不同的監(jiān)控系統(tǒng),這樣做的歷史背景是每個(gè)監(jiān)控系統(tǒng)的側(cè)重點(diǎn)有所不同,這就導(dǎo)致我們?cè)诒容^長(zhǎng)一段時(shí)間內(nèi)需要到各個(gè)平臺(tái)上篩選不同的上報(bào)數(shù)據(jù),這對(duì)問題的定位和排查造成了很大困擾和阻礙,在這之后我們下定決心把所有的上報(bào)口徑和渠道統(tǒng)一起來(lái),最終收歸落到同一個(gè)平臺(tái)上。
一個(gè)靠譜好用的監(jiān)控系統(tǒng)應(yīng)該支持實(shí)時(shí)性,多維度/指標(biāo)看板和自定義告警等能力,在經(jīng)過一段時(shí)間的試用和產(chǎn)品 PK,我們最終采用騰訊內(nèi)部自研的監(jiān)控系統(tǒng),同時(shí)也參與到內(nèi)部的開源共建中。
4. 最后
本篇文章首先是對(duì)跨端技術(shù)的演進(jìn)過程進(jìn)行了介紹,從整個(gè)變遷過程中我們會(huì)發(fā)現(xiàn),跨端技術(shù)的演進(jìn)實(shí)際上是以 H5 代表的效率、動(dòng)態(tài)性逐步遷往 Native 代表的性能體驗(yàn)的過程,并在整個(gè)過程中不斷尋找兩者間的平衡點(diǎn)。在跨端建設(shè)的實(shí)踐過程中,往往需要建設(shè)一系列配套建設(shè)和針對(duì)性性能優(yōu)化,這里也介紹了我們?cè)谶@方面的一些實(shí)踐。
最后引用阿里圣司之前在分享中的話,“性能體驗(yàn)、研發(fā)效率與穩(wěn)定性是前端三駕馬車,圍繞這些大方向很多技術(shù)規(guī)劃都是類似的,但落到執(zhí)行結(jié)果上千差萬(wàn)別;頂層設(shè)計(jì)之下,對(duì)每個(gè)細(xì)節(jié)的執(zhí)行與積累存在差異,而這些差異聚合在一起決定了成敗”。
我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。

“分享、點(diǎn)贊、在看” 支持一波??
