本文來自QCon全球軟件開發(fā)大會(huì)王勁鵬的技術(shù)分享,下文進(jìn)行了排版優(yōu)化和修訂。
1、引言
性能和體驗(yàn)在 iOS / Android 雙端場(chǎng)景下已經(jīng)是一個(gè)較為成熟的話題,但隨著鴻蒙 OS 的發(fā)展,端側(cè)開發(fā)者需要更多的關(guān)注多端場(chǎng)景的差異性。
本次分享的主題是小紅書在鴻蒙平臺(tái)上的工程實(shí)踐,主要聚焦于性能優(yōu)化和探索。(* PPT講稿原文下載:《小紅書鴻蒙OS下的性能優(yōu)化探索與實(shí)踐(PPT)[附件下載]》)
先介紹一下自己的背景。之前一直從事大前端領(lǐng)域的工作,主要專注于跨端和容器化方案。也曾手寫過一個(gè)跨端框架,名為 Doric,它可以對(duì)標(biāo) React Native、Vue Native 和 Flutter 等。Doric 框架在落地時(shí)表現(xiàn)良好,還支持了一些自研的 3D 引擎方案。除此之外,我還有播放器內(nèi)核研發(fā)經(jīng)驗(yàn),以及大前端常規(guī)體系建設(shè)和 CI/CD 流水線的工程經(jīng)驗(yàn)。未來,我將持續(xù)關(guān)注大前端的演進(jìn),尤其是鴻蒙這樣的多端和跨端平臺(tái)。
從 2023 年開始,鴻蒙的優(yōu)勢(shì)愈發(fā)明顯,已經(jīng)成為可與 iOS、安卓媲美的第三大移動(dòng)操作系統(tǒng)。從一些抖音視頻中也可以看出,鴻蒙在流暢性方面甚至在某些層面上超過了 iOS。
今天的分享內(nèi)容分為四個(gè)部分:
- 1)介紹整個(gè)歷程和背景;
- 2)介紹鴻蒙 OS 的相關(guān)能力和小紅書在該平臺(tái)上的優(yōu)化實(shí)踐;
- 3)通過鴻蒙 OS 提供的性能驗(yàn)證工具,展示小紅書在鴻蒙平臺(tái)上的性能優(yōu)化驗(yàn)證方法、優(yōu)化后的性能提升以及具體的收益和結(jié)果;
- 4)總結(jié)和展望。
2、內(nèi)容分享和整理
分享者:王勁鵬,內(nèi)容審校和編輯:Kitty。

王勁鵬:小紅書鴻蒙工程師。目前主要負(fù)責(zé)小紅書鴻蒙版的研發(fā)和工程建設(shè),曾從事過大前端架構(gòu)設(shè)計(jì)、研發(fā)效能等方向的工作,在終端架構(gòu)演進(jìn)、性能優(yōu)化以及跨端容器和動(dòng)態(tài)化等方面具備長(zhǎng)期實(shí)踐及深厚經(jīng)驗(yàn),持續(xù)關(guān)注大前端技術(shù)體系,鴻蒙以及多端的演進(jìn)。
3、版本歷程和開發(fā)背景
3.1 小紅書迭代歷程
從 2023 年年中開始,鴻蒙的“千帆計(jì)劃”正式啟動(dòng),并很快升級(jí)為“鴻飛計(jì)劃”。小紅書作為 7 家頭部合作商之一,率先支持了鴻蒙,并于 2023 年 11 月中旬上線了一個(gè)基礎(chǔ)版的 beta 版本 APP。這個(gè)版本主要包含筆記瀏覽和視頻筆記瀏覽兩大功能,以及一些簡(jiǎn)單的個(gè)人設(shè)置。當(dāng)時(shí),小紅書的動(dòng)作非常迅速,可以說是頭部應(yīng)用廠商中對(duì)華為支持最為積極的品牌之一。
在整個(gè)鴻飛計(jì)劃中,我們規(guī)劃了三個(gè)核心里程碑:除了 2023 年 11 月的 beta 版本外,還包括 2024 年 6 月的 HDC 版本和 2024 年 9 月的商用版本。HDC 版本主要是針對(duì)華為正式宣發(fā)鴻蒙 3(HarmonyOS Next)開發(fā)者測(cè)試的情況。在 HDC 版本中,我們上線了許多小紅書特有的存量功能,包括視頻拍攝、圖文拍攝以及多設(shè)備協(xié)同等創(chuàng)新特性。而到了 2024 年 9 月的商用版本交付時(shí),小紅書的核心功能已經(jīng)基本與主端對(duì)齊。考慮到鴻蒙的開發(fā)周期僅有一年,小紅書的鴻蒙 APP 在這一年中要對(duì)齊開發(fā)了十年甚至十幾年的安卓和 iOS 版本,難度和壓力都非常巨大。
到 2024 年 9 月,除了對(duì)齊雙端的所有功能外,我們還開發(fā)了許多其他功能,包括華為支持的創(chuàng)新特性,例如智能拖拽——用戶可以將圖片拖拽到中轉(zhuǎn)站或小藝等場(chǎng)景。此外,商用版本還支持了用戶呼聲較高的 HDR 或 Moonlight Photo 拍攝能力。
3.2 純血鴻蒙與安卓的區(qū)別
我從幾個(gè)維度來對(duì)比一下純血鴻蒙和安卓 OS 的主要區(qū)別。
內(nèi)核架構(gòu)純血鴻蒙的本質(zhì)是微內(nèi)核,而安卓是基于 Linux 宏內(nèi)核。微內(nèi)核只提供基礎(chǔ)的內(nèi)存和文件管理能力,驅(qū)動(dòng)和其他系統(tǒng)能力都在 OS 之外。這樣做的好處是系統(tǒng)穩(wěn)定性極高,即使應(yīng)用崩潰,也不會(huì)導(dǎo)致整個(gè)系統(tǒng)崩潰(system crash)。而在 Linux 宏內(nèi)核中,應(yīng)用的不當(dāng)行為可能會(huì)直接導(dǎo)致系統(tǒng)崩潰。
多設(shè)備適配鴻蒙目前支持多種設(shè)備類型:包括 Mate 60 Pro 這樣的直板手機(jī)、Mate X5 或非凡大師 XT 這樣的雙折疊和三折疊手機(jī)、平板電腦、車機(jī),甚至華為正在研發(fā)的鴻蒙 PC。鴻蒙真正實(shí)現(xiàn)了類似 iOS 的多端整合能力,通過一套代碼實(shí)現(xiàn)多端部署。其工程體系和架構(gòu)支持單 HAP(Harmony Ability Package)多 HSP(Harmony Service Package)模塊,指令集適配了 ARM64 等多種架構(gòu),開發(fā)者只需根據(jù)設(shè)備尺寸適配 UI 展示即可。例如,在 2024 年 9 月 的華為全場(chǎng)景設(shè)備發(fā)布會(huì)上,余承東展示了小紅書在從直板機(jī)到雙折疊、三折疊設(shè)備上的適配能力,完全實(shí)現(xiàn)了響應(yīng)式編程,不同設(shè)備形態(tài)下有不同的瀏覽體驗(yàn)。
開發(fā)工具和編程模型鴻蒙的開發(fā)工具和編程模型與安卓差異較大。鴻蒙更類似于 Flutter 的嵌套型容器布局,而不是安卓那種面向?qū)ο蟮拈_發(fā)方式。在語言層面,鴻蒙完全封裝了底層邏輯,采用類似前端 Flux 單向數(shù)據(jù)流模式,通過數(shù)據(jù)變更驅(qū)動(dòng) UI 刷新。這種模式類似于前端 Redux 或 MobX 框架中的 state 管理 。
從 2024 年 10 月 8 日公測(cè)開始,鴻蒙的應(yīng)用生態(tài)正在逐漸繁榮。不過,目前像微信這樣的應(yīng)用還處于搶先體驗(yàn)階段。相比之下,安卓的生態(tài)已經(jīng)相對(duì)成熟。鴻蒙的最終目標(biāo)是打造全場(chǎng)景智能設(shè)備生態(tài),涵蓋所有終端設(shè)備,以及基于 OpenHarmony 內(nèi)核開發(fā)的物聯(lián)網(wǎng)終端。它還支持多種芯片體系,例如瑞芯微 RK3568 等。
3.3 小紅書鴻蒙應(yīng)用架構(gòu)層級(jí)
小紅書經(jīng)過一年的迭代,其整體應(yīng)用架構(gòu)已經(jīng)基本成熟。目前,整體代碼量接近 200 萬行,達(dá)到了一個(gè)較高的復(fù)雜度。在一般成熟的 APP 架構(gòu)中,通常會(huì)包含一些基礎(chǔ)底層能力,例如網(wǎng)絡(luò)、磁盤存儲(chǔ)、埋點(diǎn)體系、APM(應(yīng)用性能管理)系統(tǒng),以及一些通用組件和能力。對(duì)于鴻蒙平臺(tái),小紅書還具備一些特殊的公共通用能力。
我們開發(fā)了一個(gè)“一多框架”,這是一個(gè)支持一套代碼多端部署的具體框架體系。通過這個(gè)框架,我們實(shí)現(xiàn)了多設(shè)備的斷點(diǎn)控制功能。用戶可以根據(jù)設(shè)備的尺寸和類型進(jìn)行適配,因?yàn)槿A為設(shè)備支持多端投屏。例如,用戶可以在手機(jī)上瀏覽小紅書,然后將內(nèi)容投屏到車機(jī)上。比如用戶購(gòu)買了一輛問界汽車,可以在車內(nèi)通過車機(jī)繼續(xù)瀏覽手機(jī)上的小紅書內(nèi)容,這種場(chǎng)景在駕駛時(shí)尤其有用。
除了底層框架,對(duì)于上層業(yè)務(wù),小紅書還有一套自研的組件庫方案,這套組件庫承載了上層業(yè)務(wù)的多種功能,包括圖文筆記、視頻筆記瀏覽,以及一些 Hybrid 容器能力。小紅書本質(zhì)上在跨端開發(fā)中仍然使用了 React Native(RN)和類 Web 技術(shù)。RN 引擎由華為內(nèi)部合作提供,采用了自研的 ohos 方案,用于解決 React Native 的 bundle 和 JS 加載以及渲染問題。此外,還包括產(chǎn)品定制層,這里涵蓋了所有相關(guān)的設(shè)備適配內(nèi)容。

3.4 性能優(yōu)化與實(shí)踐
目前,安卓和 iOS 在性能優(yōu)化方面已經(jīng)相當(dāng)成熟,包括如何分析性能熱點(diǎn)問題、有哪些工具以及最佳實(shí)踐等。然而,對(duì)于鴻蒙來說,它是一個(gè)全新的系統(tǒng)。直到 2024 年年中,鴻蒙的穩(wěn)定性和流暢性都還存在一些問題。這里重點(diǎn)講述小紅書在 2024 年與華為一起進(jìn)行了哪些實(shí)踐,以提升應(yīng)用的性能和用戶體驗(yàn)。
我們定義了一個(gè)性能指標(biāo)場(chǎng)景。這個(gè)指標(biāo)體系是小紅書與華為共同探討的結(jié)果,因?yàn)槿A為有一個(gè)性能工廠,它對(duì)每個(gè)應(yīng)用的評(píng)級(jí)都有一個(gè) S 標(biāo)標(biāo)準(zhǔn)。小紅書與華為一起確定了針對(duì)小紅書場(chǎng)景需要觀測(cè)的具體指標(biāo)。性能優(yōu)化的核心是慢函數(shù)指標(biāo),它主要包含兩部分:過程時(shí)長(zhǎng)和應(yīng)用體驗(yàn)的流暢性。
過程時(shí)長(zhǎng)主要包含以下三點(diǎn):
- 1)冷啟動(dòng)時(shí)長(zhǎng):這是用戶最關(guān)心的指標(biāo)之一,即從點(diǎn)擊應(yīng)用圖標(biāo)到應(yīng)用完成動(dòng)畫并展示第一幀的時(shí)間。對(duì)于多數(shù)應(yīng)用,首頁通常有緩存機(jī)制。例如,小紅書會(huì)緩存用戶上次刷新的筆記,淘寶會(huì)緩存用戶上次瀏覽的商品內(nèi)容;
- 2)場(chǎng)景完成時(shí)長(zhǎng):指完成某個(gè)特定場(chǎng)景所需的時(shí)間;
- 3)應(yīng)用響應(yīng)時(shí)長(zhǎng):指用戶操作界面后,界面真正發(fā)生變化的時(shí)間,即響應(yīng)時(shí)延。
流暢性方面,最基礎(chǔ)的觀測(cè)指標(biāo)是平均 FPS(幀率),包括丟幀數(shù)、最大連續(xù)丟幀數(shù)、丟幀卡頓次數(shù)以及卡頓率。卡頓率可以通過量化計(jì)算得出:當(dāng)一個(gè)場(chǎng)景中出現(xiàn)丟幀時(shí),丟幀的時(shí)長(zhǎng)與場(chǎng)景總時(shí)長(zhǎng)的比值即為卡頓率,它是一個(gè)小于 1 的百分比數(shù)值。
3.5 OS 能力 & 優(yōu)化實(shí)踐
首先,針對(duì) IO 場(chǎng)景,我們進(jìn)行了相應(yīng)的優(yōu)化。
鴻蒙 OS 的系統(tǒng)能力主要分為以下三個(gè)方面:
- 1)并行化能力鴻蒙 OS 提供了兩種并行化能力:Worker 和 TaskPool。Worker 類似于傳統(tǒng)的線程模型,每個(gè) Worker 都有自己的內(nèi)存空間和執(zhí)行單元,支持通過消息(message)進(jìn)行通信。TaskPool 則類似于協(xié)程或線程池,能夠動(dòng)態(tài)管理線程數(shù)量,支持標(biāo)記為 @concurrent 的函數(shù)直接在任務(wù)池中調(diào)度和運(yùn)行。這兩種機(jī)制都支持線程間隔離,內(nèi)存不共享;
- 2)多線程通信和數(shù)據(jù)傳輸在多線程通信方面,鴻蒙 OS 支持序列化數(shù)據(jù)傳輸和基于消息(message)的通信機(jī)制。此外,還引入了事件發(fā)射器(Emitter)用于系統(tǒng)事件的發(fā)布和訂閱。這種機(jī)制允許線程間通過消息傳遞來實(shí)現(xiàn)復(fù)雜的交互邏輯;
- 3)同步轉(zhuǎn)異步機(jī)制鴻蒙 OS 支持基于 Promise 的異步編程模型,包括 async 和 await 語法,以及 then 和 catch 方法。這種機(jī)制能夠有效提升應(yīng)用的響應(yīng)性和用戶體驗(yàn)。
4、并行化能力
在并行化能力方面,鴻蒙 OS 提供了兩套基礎(chǔ)實(shí)現(xiàn)方式。開發(fā)者可以通過 RTS(運(yùn)行時(shí)系統(tǒng))實(shí)現(xiàn)并行化,也可以通過底層庫(如 C++ 標(biāo)準(zhǔn)庫中的)實(shí)現(xiàn)。不過,如果完全依賴底層庫,可能會(huì)導(dǎo)致開發(fā)效率下降。為了滿足業(yè)務(wù)需求,鴻蒙 OS 在年初引入了 Worker 和 TaskPool 能力。Worker 類似于傳統(tǒng)的線程模型,每個(gè) Worker 都有獨(dú)立的內(nèi)存空間和執(zhí)行單元,支持通過消息進(jìn)行通信。消息可以包含可序列化的數(shù)據(jù),也可以通過指針直接遷移數(shù)據(jù)。TaskPool 則類似于線程池,能夠動(dòng)態(tài)管理線程數(shù)量,支持標(biāo)記為 @concurrent 的函數(shù)直接在任務(wù)池中調(diào)度和運(yùn)行。與安卓平臺(tái)的線程池不同,鴻蒙 OS 的 TaskPool 會(huì)根據(jù)硬件條件和任務(wù)負(fù)載動(dòng)態(tài)調(diào)整線程數(shù)量。這種機(jī)制避免了安卓平臺(tái)中因線程池?cái)?shù)量過多而導(dǎo)致的系統(tǒng)資源消耗問題。
接下來我們對(duì)比鴻蒙 OS 的 Worker 并行化能力和安卓端的相關(guān)特性。從多個(gè)維度來看,Worker 本質(zhì)上不推薦手動(dòng)創(chuàng)建,而是通過系統(tǒng)配置 build-provider.json 綁定 ETS 文件來實(shí)現(xiàn)創(chuàng)建。這一點(diǎn)與安卓端并無明顯差異,安卓端可以通過 THREAD 等方式啟動(dòng)線程。
在鴻蒙 OS 5.0 以下版本(如 4.2 版本)中,主要運(yùn)行的仍然是安卓系統(tǒng)。這種情況下,安卓線程數(shù)量存在上限,這對(duì)應(yīng)用開發(fā)者來說是一個(gè)挑戰(zhàn)。如果 SDK 集成過多,線程數(shù)可能超標(biāo),進(jìn)而導(dǎo)致應(yīng)用被系統(tǒng)強(qiáng)制終止,或出現(xiàn)業(yè)務(wù)場(chǎng)景異常崩潰等穩(wěn)定性問題。
數(shù)據(jù)傳輸方面:鴻蒙 OS 為了優(yōu)化 Worker 的性能和負(fù)載,對(duì) Worker 的數(shù)量和單個(gè) Worker 的傳輸上限進(jìn)行了限制。鴻蒙 Worker 的單個(gè)傳輸上限類似于安卓中的 Binder 機(jī)制,也存在類似的傳輸限制。不過,安卓線程通常沒有嚴(yán)格限制,因?yàn)榫€程本質(zhì)上是一個(gè)內(nèi)存拷貝過程,除非開發(fā)者通過指針等方式自定義線程間數(shù)據(jù)傳輸。
在傳輸格式上:鴻蒙 OS 支持通過 Sendable 接口進(jìn)行數(shù)據(jù)傳輸。Sendable 是一種注解方式定義的數(shù)據(jù)結(jié)構(gòu),具有傳染性,即如果一個(gè)類被標(biāo)記為 Sendable,其關(guān)聯(lián)屬性也必須是 Sendable 類型。鴻蒙 OS 支持基礎(chǔ)數(shù)據(jù)類型(如 number、string)和集合類型作為 Sendable 傳輸?shù)膬?nèi)容。對(duì)于跨模塊調(diào)用,鴻蒙 OS 不允許 Worker 跨 HAP 或跨 HSP 調(diào)用。相比之下,安卓應(yīng)用通常運(yùn)行在一個(gè)或多個(gè) Dex 文件中,允許跨 Dex 或跨模塊的線程間調(diào)用。
TaskPool 類似于雙端的協(xié)程概念,是一種輕量級(jí)線程,僅存儲(chǔ)函數(shù)。不過,TaskPool 與協(xié)程有所不同,它獨(dú)立于任務(wù)維度,且任務(wù)執(zhí)行時(shí)長(zhǎng)有限制(超過 3 分鐘會(huì)被系統(tǒng)自動(dòng)回收)。安卓平臺(tái)可以通過 ASM 插樁技術(shù)對(duì)線程的創(chuàng)建和執(zhí)行進(jìn)行監(jiān)控和優(yōu)化,但輕量級(jí)線程或協(xié)程的實(shí)現(xiàn)通常依賴于線程池或協(xié)程機(jī)制。
TaskPool 中的任務(wù)默認(rèn)支持?jǐn)?shù)據(jù)轉(zhuǎn)移(transfer),不支持拷貝。此外,TaskGroup 不支持 SDK 初始化包的加載。某些同學(xué)習(xí)慣在異步線程中觸發(fā) SDK 的行為,在鴻蒙 OS 上可能會(huì)因 TaskPool 生命周期結(jié)束而導(dǎo)致變量被釋放。
關(guān)于并行化數(shù)據(jù)傳輸?shù)?Sendable 概念:Sendable 通過系統(tǒng)提供的 SharedHeap(共享堆)實(shí)現(xiàn)傳輸。共享堆與本地堆(local Heap)的區(qū)別在于,共享堆支持 Sendable 化數(shù)據(jù)的傳輸,而本地堆則需要序列化。共享堆的管理和控制耗費(fèi)了華為專家大量時(shí)間和精力,其中還涉及復(fù)雜的異步鎖(async lock)機(jī)制。在 RTS 并發(fā)實(shí)例期間(包括 Worker、TaskPool 等),數(shù)據(jù)可以通過 Sendable 傳遞,但 Worker 需要使用單獨(dú)的 API。TaskPool 則完全支持 Sendable 的直接傳輸。這種異步鎖機(jī)制允許在 TaskPool 或 Worker 中鎖定其他任務(wù)中的某些函數(shù),實(shí)現(xiàn)線程間的同步,類似于安卓中的 synchronized 或其他鎖機(jī)制。
5、小紅書典型并行化場(chǎng)景
小紅書在一些典型化場(chǎng)景中已經(jīng)實(shí)現(xiàn)了并行化處理。例如,網(wǎng)絡(luò)請(qǐng)求是一個(gè)典型的耗時(shí)操作,因?yàn)檎?qǐng)求過程中涉及驗(yàn)簽和安全能力的處理,這些操作如果在主線程中同步完成,可能會(huì)導(dǎo)致應(yīng)用掉幀。當(dāng)用戶滑動(dòng)時(shí),掉幀現(xiàn)象會(huì)非常明顯,這通常是由于大量計(jì)算引起的。為了解決這一問題,我們采用了 Worker 化的方式,將這些操作移到 Worker 線程中,從而避免主線程的卡頓。
在進(jìn)行埋點(diǎn)時(shí),可能會(huì)涉及數(shù)據(jù)庫的 IO 操作,這些操作也不建議在主線程中執(zhí)行。通過將這些操作放到 Worker 線程中,可以有效避免對(duì)主線程的影響。
針對(duì)雙列布局中的圖片和資源預(yù)加載,我們采用華為自研的 RCP 網(wǎng)絡(luò)解決方案(類似于 HTTP),通過 Worker 線程在遠(yuǎn)端進(jìn)行下載,并在完成后將結(jié)果返回到主線程。此外,TaskPool 的應(yīng)用場(chǎng)景也非常廣泛,例如文件上傳、多媒體操作以及啟動(dòng)任務(wù)的編排等。TaskPool 的優(yōu)勢(shì)在于輕量化,避免了線程上下文切換帶來的不必要耗時(shí)。
關(guān)于冷啟動(dòng)和首刷場(chǎng)景的優(yōu)化。這部分主要包括兩個(gè)方面:模塊的懶加載和動(dòng)態(tài)組件的復(fù)用池。懶加載是應(yīng)用開發(fā)中常見的優(yōu)化手段,類似于安卓端的 class order 機(jī)制。當(dāng)應(yīng)用不需要某個(gè)類時(shí),可以延遲加載該類,直到真正需要使用時(shí)才加載。這種方式可以顯著提高冷啟動(dòng)階段的代碼加載效率,從而大幅降低冷啟動(dòng)時(shí)長(zhǎng)。
動(dòng)態(tài)組件和組件復(fù)用池則是為了解決 UI 組件重復(fù)創(chuàng)建的問題。在應(yīng)用中,可能會(huì)有多種相同類型的 UI 組件(例如小紅書中的筆記組件)。為了避免重復(fù)創(chuàng)建帶來的開銷,我們希望在運(yùn)行時(shí)盡量復(fù)用已有的組件,而不是頻繁地創(chuàng)建和銷毀。
6、類前端視角下的模塊懶加載
我們通過特定的分析工具對(duì)懶加載進(jìn)行了深入分析。如圖所示,我們能夠識(shí)別出啟動(dòng)過程中加載的各種模塊,包括 RNOH(React Native on Harmony)、Web engine(網(wǎng)頁引擎)、Red Player(播放器)等組件。這些模塊的加載過程涉及到多個(gè).so 文件,即共享對(duì)象文件。

通過自上而下的分析方法,我們可以清晰地看到每個(gè)模塊加載的具體耗時(shí)。進(jìn)一步分析這些.so 文件與 RTS(運(yùn)行時(shí)系統(tǒng))的關(guān)聯(lián),以及它們所引入的 Napi 的 TS 文件。我們進(jìn)行了懶加載潛在對(duì)象的分析,發(fā)現(xiàn)許多 RTS 實(shí)際上并不需要的類文件已經(jīng)被加載。這是因?yàn)殚_發(fā)者在編寫代碼時(shí),可能并未充分考慮到導(dǎo)入一個(gè)類或方法對(duì)應(yīng)用啟動(dòng)延遲的影響。
為了優(yōu)化這一過程,我們的目標(biāo)是減少字節(jié)碼中需要加載的類文件數(shù)量,從而加快應(yīng)用的冷啟動(dòng)速度。華為提供的編譯器能夠?qū)?RTS 編譯成 Ark bytecode(方舟字節(jié)碼),這是一種高效的字節(jié)碼格式。通過減少需要加載的類文件數(shù)量,我們可以顯著提高應(yīng)用的啟動(dòng)速度。
華為還提供了一種懶加載的導(dǎo)入方式,只有在真正需要使用某個(gè)類時(shí),它才會(huì)被加載。這種懶加載機(jī)制有助于減少應(yīng)用啟動(dòng)時(shí)的資源消耗。這引發(fā)了一個(gè)問題:為什么華為不默認(rèn)采用全懶加載方式,即只有在使用時(shí)才加載類文件呢?我已經(jīng)將這個(gè)問題反饋給華為,并且系統(tǒng)側(cè)可能會(huì)考慮在未來的版本中默認(rèn)采用懶加載方式,同時(shí)仍然允許用戶手動(dòng)選擇非懶加載的方式進(jìn)行類文件的加載。
7、動(dòng)態(tài)組件
在小紅書的首頁場(chǎng)景中,筆記卡組件在多個(gè)場(chǎng)景中被復(fù)用。為了避免重復(fù)創(chuàng)建 UI 導(dǎo)致的性能消耗,我們采用了動(dòng)態(tài)組件的概念。動(dòng)態(tài)組件的核心原理是利用占位符來延遲組件的創(chuàng)建,這與 Android 開發(fā)中使用 Stub 模式的概念相似。在這種模式下,可以使用一個(gè)代理對(duì)象(stub)來代表尚未初始化的組件,從而延遲組件的創(chuàng)建過程。當(dāng)真正需要渲染組件時(shí),再將渲染內(nèi)容填充進(jìn)去,從而避免每次調(diào)用構(gòu)建函數(shù)(如 build)時(shí)的耗時(shí)。
占位邏輯通過系統(tǒng)的 API 實(shí)現(xiàn),涉及到 NodeContainer 和 NodeController 的綁定關(guān)系。Container 和 Controller 一一映射,由 NodeCore 進(jìn)行管理。Container 僅管理當(dāng)前展現(xiàn)的內(nèi)存部分,使用完畢后需要將其放回池中進(jìn)行回收和再利用。以冷啟動(dòng)首刷為例,在啟動(dòng)階段可以先獲取磁盤上的筆記內(nèi)容,然后在 BuilderNode 中預(yù)先創(chuàng)建多個(gè) Image 組件。這樣,在等待網(wǎng)絡(luò)或推薦接口響應(yīng)時(shí),Image 組件已經(jīng)創(chuàng)建完畢,從而在首頁刷新時(shí)可以立即使用這些組件,這對(duì)于提高首刷非常有益。

對(duì)于組件復(fù)用池,當(dāng)動(dòng)態(tài)組件不再使用時(shí),需要將其返回到組件池中。對(duì)于自定義組件,通過 NoteContainer 占位方式,由 NodeController 進(jìn)行管理。在需要?jiǎng)?chuàng)建子組件時(shí),先在 NodePool 中查找,如果找不到,則創(chuàng)建新組件;如果找到,則嘗試復(fù)用。流程圖展示了從 Container 裝載 NodeItem 開始,通過 NodePool 查找,如果找到則進(jìn)行條件判斷和復(fù)用。
組件的新建和復(fù)用過程中,如果找到對(duì)應(yīng)的 NodeItem,則調(diào)用 build 方法并更新自定義組件的狀態(tài),完成復(fù)用。如果有對(duì)應(yīng)的 NodeItem,可以直接通過 update 函數(shù)更新內(nèi)部狀態(tài)并刷新 UI。但要注意,update 方法可能會(huì)因狀態(tài)變量過于復(fù)雜而導(dǎo)致更新延遲,出現(xiàn)圖像殘影。因此,需要拆分 state,使其足夠小,以確保狀態(tài)變更到通知 UI 的時(shí)間縮短,消除殘影。
我們的策略是優(yōu)先在 NodePool(節(jié)點(diǎn)池)中查找可用的 NodeItem(節(jié)點(diǎn)項(xiàng))。如果 NodePool 中存在可用的 NodeItem,我們就直接使用它,并通過 getNode 方法進(jìn)行 item 綁定,隨后更新其狀態(tài)以實(shí)現(xiàn)復(fù)用。如果 NodePool 中沒有找到對(duì)應(yīng)的 NodeItem,那么我們將通過 makeNode 方法調(diào)用 build 函數(shù)來創(chuàng)建新的節(jié)點(diǎn)項(xiàng)。
完成組件的復(fù)用后,我們需要將這些組件返回到緩存池中,以便在未來可以再次使用。這個(gè)過程涉及到 NodeContainer(節(jié)點(diǎn)容器)和 NodeController(節(jié)點(diǎn)控制器)的銷毀,并將 NodeItem 重新放回 NodePool 中。為了更有效地管理緩存,業(yè)務(wù)層可以利用 LRU(最近最少使用)算法,或者鴻蒙系統(tǒng)提供的 LRUCache 和 LiUHashMap 等數(shù)據(jù)結(jié)構(gòu),來自定義緩存的大小,從而優(yōu)化組件的復(fù)用和緩存策略。
8、滑動(dòng)類場(chǎng)景
在小紅書應(yīng)用中,滑動(dòng)類場(chǎng)景非常普遍,包括推薦頁的子頻道、個(gè)人頁中的收藏點(diǎn)贊以及用戶自己發(fā)布的筆記,還有搜索結(jié)果頁中的搜索結(jié)果和用戶商品等,這些都是雙列滑動(dòng)場(chǎng)景。這些雙列滑動(dòng)場(chǎng)景占據(jù)了小紅書用戶體驗(yàn)的 90% 到 95%,因此,滑動(dòng)體驗(yàn)的流暢性對(duì)于用戶的整體體驗(yàn)至關(guān)重要。
為了提升滑動(dòng)場(chǎng)景的流暢性,小紅書采用了 RCP 框架來優(yōu)化網(wǎng)絡(luò)資源的獲取。RCP 是華為提供的一個(gè)系統(tǒng)組件能力,主要解決網(wǎng)絡(luò)資源獲取效率問題。通過 RCP,開發(fā)者可以在需要時(shí)發(fā)起網(wǎng)絡(luò)請(qǐng)求,并自定義資源的寫入地址,如文件或 ArrayBuffer。RCP 負(fù)責(zé)高效地將資源寫入指定位置,而在不需要時(shí),可以取消 RCP 請(qǐng)求,從而優(yōu)化資源管理。

RCP 的核心能力在于能夠取消請(qǐng)求,并對(duì)弱網(wǎng)場(chǎng)景進(jìn)行了優(yōu)化,其建聯(lián)過程優(yōu)于 HTTP 1.1 或 2.0。基于 RCP,小紅書還應(yīng)用了華為俄研所提供的 Prefetch 方案。Prefetch 方案在瀑布流組件的可見區(qū)變更時(shí),通過 worker 線程(如 prefetched worker)啟動(dòng)資源獲取,當(dāng)不可見時(shí)關(guān)閉,從而優(yōu)化快速滑動(dòng)場(chǎng)景,減少不必要的帶寬消耗。
在快速滑動(dòng)過程中,有些 item 可能短暫消失,對(duì)于雙端場(chǎng)景,網(wǎng)絡(luò)請(qǐng)求可能已經(jīng)發(fā)出且在途,無法取消,導(dǎo)致帶寬浪費(fèi)。Prefetch 和 RCP 結(jié)合的方式可以優(yōu)化這種快滑場(chǎng)景,防止真正想要看的內(nèi)容出現(xiàn)白塊。Prefetched worker 線程管理多個(gè) RCP 請(qǐng)求,每個(gè)請(qǐng)求都有完整的生命周期。當(dāng)通過 RCP 請(qǐng)求獲取到所需資源時(shí),會(huì)通知主線程,主線程根據(jù)地址加載資源到 Image 組件或占位符 RQI 組件中。
在小紅書的開發(fā)過程中,我們遇到了一些性能熱點(diǎn)問題,這些問題大多是通過 Code Linter(代碼檢查工具)檢測(cè)出來的。由于開發(fā)節(jié)奏快,開發(fā)者在編寫代碼時(shí)可能難以關(guān)注到性能問題,因此需要 CI(持續(xù)集成)檢查工具來輔助檢查。
常見的性能熱點(diǎn)包括:
1)在列表場(chǎng)景中頻繁使用的 LadyForEach 組件,需要指定 key 以實(shí)現(xiàn)列表復(fù)用。如果開發(fā)者忘記指定 key,Code Linter 會(huì)報(bào)錯(cuò)提示;
2)在 onClick 或 onVisible 等函數(shù)中編寫空 callback(回調(diào)函數(shù))。當(dāng)這些空 callback 積累到一定數(shù)量(如幾百個(gè)或上千個(gè))時(shí),可能會(huì)嚴(yán)重拖慢應(yīng)用性能。Code Linter 可以掃描出這類問題;
3)未使用 TaskPool 處理網(wǎng)絡(luò)資源。例如,Image Bitmap 直接傳遞 URL 進(jìn)行同步加載,當(dāng)網(wǎng)絡(luò)阻塞時(shí)會(huì)導(dǎo)致 UI 線程卡頓;
4)復(fù)雜的 ETS 組件在列表場(chǎng)景下未實(shí)現(xiàn)重用。未設(shè)置重用的 ETS 組件在列表滾動(dòng)時(shí)需要重新構(gòu)建,非常耗時(shí)。組件嵌套層級(jí)過深也會(huì)導(dǎo)致性能問題。在安卓端,布局檢查器建議容器嵌套不超過四層;
5)使用 JSON.stringify 進(jìn)行對(duì)象序列化。JSON.stringify 有一定耗時(shí),尤其在處理 100KB 左右的數(shù)據(jù)時(shí),可能需要 10 毫秒左右。Code Linter 會(huì)提示這部分性能問題,但是否需要轉(zhuǎn)異步線程需要開發(fā)者自行判斷;
6)調(diào)用 Image 的 syncLoad(同步加載)。在某些場(chǎng)景下,如轉(zhuǎn)場(chǎng)動(dòng)畫,需要同步加載 image 以保證連貫性。但如果 image 是非磁盤資源(如網(wǎng)絡(luò)資源),會(huì)導(dǎo)致卡幀。Code Linter 可以掃描出這類問題;
7)關(guān)于編譯器的優(yōu)化。ETS 組件應(yīng)避免嵌套過深。如果嵌套過深,可以將每層函數(shù)通過系統(tǒng)的 builder param 或 builder 函數(shù)轉(zhuǎn)換。使用 @builder 注解標(biāo)識(shí)的函數(shù)會(huì)在編譯期間與 ETS 代碼整合,從而提高編譯器優(yōu)化效果。
Code Linter 支持全量掃描和基于 Git DIFF 的增量掃描,但目前華為的 Code Linter 還不能與 Git Prehook 關(guān)聯(lián),導(dǎo)致無法在流水線上自動(dòng)檢查。雖然 CI 檢查階段已有 Code Linter,但本地代碼提交階段仍需手動(dòng)運(yùn)行腳本,無法實(shí)現(xiàn)自動(dòng)檢查。我們正在催促華為解決這一問題。
9、UI 重載場(chǎng)景分幀方案
在處理 UI 重載場(chǎng)景時(shí),我們采用了一種稱為分幀方案的方法。分幀這個(gè)術(shù)語的含義是,當(dāng)應(yīng)用在一幀內(nèi)無法完成所有繪制工作,或者在多幀內(nèi)都無法完成時(shí),會(huì)導(dǎo)致屏幕卡頓現(xiàn)象。盡管用戶可以看到畫面,但卻無法進(jìn)行滑動(dòng)或操作。在這種情況下,分幀方案就顯得尤為合適。雖然分幀方案可能看起來不是最優(yōu)雅的解決辦法,但它確實(shí)能夠有效地解決性能問題,使應(yīng)用性能達(dá)到預(yù)期標(biāo)準(zhǔn)。分幀方案雖然看似是一種應(yīng)急措施,但它能夠幫助應(yīng)用性能達(dá)標(biāo)。
分幀方案的流程大致如下:假設(shè)我們有數(shù)據(jù) a、b、c 需要渲染,未采用分幀方案前,數(shù)據(jù) a、b、c 會(huì)同時(shí)到達(dá)并觸發(fā)狀態(tài)變更,進(jìn)而驅(qū)動(dòng)整個(gè) UI 進(jìn)行刷新。這會(huì)導(dǎo)致在一幀內(nèi)需要繪制大量 UI 組件,從而影響應(yīng)用性能。為了解決這個(gè)問題,我們采用分幀方案,將數(shù)據(jù) a、b、c 拆分開,分別在不同的幀中進(jìn)行渲染。例如,數(shù)據(jù) a 在第一幀中渲染完成后,通過調(diào)用宏觀指令讓其進(jìn)入下一階段,然后在下一幀中更新數(shù)據(jù) b,依此類推。
在小紅書的圖文筆記場(chǎng)景中,分幀方案得到了應(yīng)用。當(dāng)用戶在首頁的雙列場(chǎng)景中點(diǎn)擊一篇筆記進(jìn)入筆記詳情頁時(shí),這個(gè)過程涉及到許多組件的加載。我們可以將這些組件拆分成不同的幀,例如幀 a、幀 b 和幀 c。對(duì)于用戶而言,他們通常希望在第一時(shí)間看到整個(gè)大屏的畫面,因此我們會(huì)優(yōu)先在幀 a 中展示大圖。而在幀 b 和幀 c 中,我們?cè)偬幚眄敳繉?dǎo)航欄或底部交互區(qū)等內(nèi)容。通過這種分幀策略,我們能夠確保用戶在第一時(shí)間看到最關(guān)鍵的內(nèi)容,同時(shí)避免了因?yàn)橐淮涡约虞d過多組件而導(dǎo)致的性能問題。
10、鴻蒙NEXT調(diào)優(yōu)工具
傳統(tǒng)的主觀工具對(duì)于鴻蒙 OS 的性能分析仍然適用。例如,抖音和小紅書都通過競(jìng)品分析來進(jìn)行主觀測(cè)評(píng)。這種能力主要是通過錄屏來展示整個(gè)流程的耗時(shí)和時(shí)長(zhǎng),特別適合評(píng)估冷啟動(dòng)完成時(shí)延和轉(zhuǎn)場(chǎng)過程的性能。通過錄屏,我們可以逐幀查看用戶從點(diǎn)擊開始到結(jié)束的幀數(shù)和真實(shí)時(shí)長(zhǎng),以此來衡量整個(gè)過程的持續(xù)時(shí)間。
10.1 鴻蒙性能分析工具:IDE Profiler
除了主觀工具,我們還可以使用 IDE 提供的性能分析工具,如 Profiler,來分析慢函數(shù)。由于 ArkTS 編程語言框架主要通過 RTS 和 NAPI(原生應(yīng)用接口)進(jìn)行關(guān)聯(lián),因此需要能夠查看 ArkTS 和 NAPI 的整個(gè)堆棧層級(jí)。這與安卓有所不同,因?yàn)楫?dāng) Java 通過 Java Native API 與原生代碼交互時(shí),堆棧并不那么容易查看。
在小紅書的性能分析中,我們展示了一個(gè)整體線程分析的例子。在左側(cè),可以看到小紅書的主線程(如 com 點(diǎn)開頭的線程)、Daemon 線程、Worker 線程以及 FFRT 線程。FFRT 是一種運(yùn)行函數(shù)流的線程,可以執(zhí)行 TaskPool 上的函數(shù)。在下圖右側(cè),我們可以看到在 RTS 環(huán)境下的分析結(jié)果,其中頂部顯示了 NAPI 調(diào)用,底部則是一些 C++ 函數(shù)。整個(gè)調(diào)用棧和它們的執(zhí)行時(shí)長(zhǎng)是通過一種自上而下的視圖來展示的。利用這種視圖,我們可以精確地識(shí)別出哪些慢函數(shù)是造成界面卡頓的原因。

10.2 性能場(chǎng)景測(cè)試工具:DevEco Testing
DevEco Testing 是一個(gè)性能測(cè)試工具,它的功能非常全面,性能測(cè)試只是其中的一部分。除了性能測(cè)試,它還支持多種測(cè)試場(chǎng)景,包括 debug testing。在 debug testing 場(chǎng)景中,用戶可以自定義業(yè)務(wù)場(chǎng)景,監(jiān)測(cè) CPU 的耗時(shí)和負(fù)載、GPU 的耗時(shí)和負(fù)載、設(shè)備發(fā)熱情況以及功耗等問題。

使用 DevEco Testing 進(jìn)行性能測(cè)試的過程如下:首先定義測(cè)試場(chǎng)景,然后捕獲主幀數(shù)據(jù)。一旦開始捕獲,就可以觀測(cè)到 FPS(幀率)、GPU 負(fù)載以及整體功耗等數(shù)據(jù)。完成性能數(shù)據(jù)捕獲后,工具會(huì)生成一份報(bào)告,為用戶提供了一個(gè)完整的場(chǎng)景分析。不過,目前場(chǎng)景定義還缺乏腳本化能力,需要人工操作輔助。未來,我們期望能夠?qū)崿F(xiàn)場(chǎng)景定義的腳本化配置,類似于自動(dòng)化測(cè)試。這樣,就可以通過自動(dòng)化工具,實(shí)現(xiàn)更高效的測(cè)試流程。
11、小結(jié)與展望
在對(duì)性能場(chǎng)景進(jìn)行優(yōu)化后,我們可以看到顯著的收益。在實(shí)驗(yàn)室環(huán)境下的測(cè)試顯示,冷啟動(dòng)時(shí)間可以降低 50%,響應(yīng)時(shí)延可以低于 100 毫秒,完成時(shí)延則保持與雙端持平或更優(yōu)。在流暢性方面,在多場(chǎng)景和重載場(chǎng)景下均實(shí)現(xiàn)了 0 丟幀的成果。需要注意的是,這里的測(cè)試是在非重載模式下進(jìn)行的,即沒有同時(shí)運(yùn)行多個(gè)資源密集型應(yīng)用,如《王者榮耀》或《和平精英》等。在這種條件下,我們的核心場(chǎng)景,如冷啟動(dòng)、搜索和個(gè)人頁等,都能夠與雙端完全對(duì)齊。

展望未來,有幾個(gè)方向:
1)首先:我們希望能夠在全場(chǎng)景下實(shí)現(xiàn)組件復(fù)用,以最大程度地實(shí)現(xiàn) UI 復(fù)用。這樣可以在多個(gè)業(yè)務(wù)之間的轉(zhuǎn)場(chǎng)或 UI 創(chuàng)建過程中,將不必要的 UI 創(chuàng)建和消耗降到最低。
2)其次:我們正在考慮代碼延遲加載的 lazy 機(jī)制。華為內(nèi)部可能將其作為通用的解決方案,但在實(shí)施過程中我們發(fā)現(xiàn)了許多問題,例如全 lazy 加載可能會(huì)影響第三方 SDK,如支付寶等,因?yàn)樗鼈兛赡苓M(jìn)行了額外的二進(jìn)制優(yōu)化,導(dǎo)致加載失敗或無法響應(yīng)。因此,我們期望通過代碼延遲加載來實(shí)現(xiàn)持續(xù)治理,但目前它可能還不適合全場(chǎng)景的 lazy import。
3)最后:我們關(guān)注防劣化問題,即在每個(gè)版本發(fā)布時(shí),我們不希望性能指標(biāo)出現(xiàn)劣化。我們希望能夠在開發(fā)階段就定義劣化指標(biāo)和具體數(shù)據(jù),以防止應(yīng)用劣化。這部分可能需要借助 DevEco Testing 和主觀測(cè)評(píng)的方式來實(shí)現(xiàn)。包括我們關(guān)注的指標(biāo),例如冷啟動(dòng)和流暢性等,未來可能會(huì)納入防劣化場(chǎng)景。目前,我們的 CI 環(huán)節(jié)或 RC 環(huán)節(jié),包括流水線的性能管控和代碼 CR 機(jī)制,都能夠規(guī)避這類問題。
12、相關(guān)資料
[1] 鴻蒙NEXT官方開發(fā)指南
[2] 一年擼完百萬行代碼,企業(yè)微信的全新鴻蒙NEXT客戶端架構(gòu)演進(jìn)之路
[3] 鴻蒙NEXT如何保證應(yīng)用安全:詳解鴻蒙NEXT數(shù)字簽名和證書機(jī)制
[4] 開源IM聊天程序HarmonyChat:基于鴻蒙NEXT的WebSocket協(xié)議
[5] 微信純血鴻蒙版正式發(fā)布,295天走完微信14年技術(shù)之路!
[6] 即時(shí)通訊框架MobileIMSDK的鴻蒙NEXT端詳細(xì)介紹
[7] 即時(shí)通訊框架MobileIMSDK的鴻蒙NEXT端開發(fā)者手冊(cè)
[8] 擁抱國(guó)產(chǎn)化:轉(zhuǎn)轉(zhuǎn)APP的鴻蒙NEXT端開發(fā)嘗鮮之旅
[9] 微信Windows端IM消息數(shù)據(jù)庫的優(yōu)化實(shí)踐:查詢慢、體積大、文件損壞等
[10] 微信技術(shù)分享:揭秘微信后臺(tái)安全特征數(shù)據(jù)倉庫的架構(gòu)設(shè)計(jì)
[11] IM跨平臺(tái)技術(shù)學(xué)習(xí)(九):全面解密新QQ桌面版的Electron內(nèi)存優(yōu)化實(shí)踐
[12] 企業(yè)微信針對(duì)百萬級(jí)組織架構(gòu)的客戶端性能優(yōu)化實(shí)踐
[13] 揭秘企業(yè)微信是如何支持超大規(guī)模IM組織架構(gòu)的——技術(shù)解讀四維關(guān)系鏈
[14] 微信團(tuán)隊(duì)分享:詳解iOS版微信視頻號(hào)直播中因幀率異常導(dǎo)致的功耗問題
[15] 微信團(tuán)隊(duì)分享:微信后端海量數(shù)據(jù)查詢從1000ms降到100ms的技術(shù)實(shí)踐
[16] 大型IM工程重構(gòu)實(shí)踐:企業(yè)微信Android端的重構(gòu)之路
[17] IM技術(shù)干貨:假如你來設(shè)計(jì)微信的群聊,你該怎么設(shè)計(jì)?
[18] 微信團(tuán)隊(duì)分享:來看看微信十年前的IM消息收發(fā)架構(gòu),你做到了嗎
[19] 總是被低估,從未被超越,揭秘QQ極致絲滑背后的硬核IM技術(shù)優(yōu)化
[20] 首次公開,最新手機(jī)QQ客戶端架構(gòu)的技術(shù)演進(jìn)實(shí)踐
[21] 大型IM穩(wěn)定性監(jiān)測(cè)實(shí)踐:手Q客戶端性能防劣化系統(tǒng)的建設(shè)之路
(本文已同步發(fā)布于:http://www.52im.net/thread-4821-1-1.html)