本文由企業(yè)微信客戶端團隊黃瑋分享,原題“在流沙上筑城:企微鴻蒙開發(fā)演進”,下文進行了排版優(yōu)化和內(nèi)容修訂。
1、引言
當企業(yè)微信團隊在2024年啟動鴻蒙Next版開發(fā)時,我們面對的是雙重難題:
- 1)在WXG小團隊模式下,如何快速將數(shù)百萬行級企業(yè)應(yīng)用移植到全新操作系統(tǒng)?
- 2)在鴻蒙API 還是Preview的初期,如何保持業(yè)務(wù)代碼的穩(wěn)定,在API快速更新的浪潮中巋然不動?
DataList框架給出了破局答案(即通過三重機制構(gòu)建數(shù)字負熵流):
- 1)結(jié)構(gòu)化熵減:將業(yè)務(wù)邏輯渲染到UI的過程抽象為數(shù)據(jù)流,使鴻蒙與Android共享同一套數(shù)據(jù)驅(qū)動的開發(fā)機制;
- 2)動態(tài)熵減:通過抽象出來的UI數(shù)據(jù)層屏蔽鴻蒙API的變化,讓業(yè)務(wù)代碼歷經(jīng)三個版本的UI層大改而不受影響;
- 3)認知熵減:將跨平臺差異封裝為一系列通用組件,降低開發(fā)者心智負荷,可以專注于業(yè)務(wù)開發(fā)而不用關(guān)心技術(shù)變更。
本文將要分享的是企業(yè)微信的鴻蒙Next客戶端架構(gòu)的演進過程,面對代碼移植和API不穩(wěn)定的挑戰(zhàn),提出了DataList框架解決方案。通過結(jié)構(gòu)化、動態(tài)和認知三重熵減機制,將業(yè)務(wù)邏輯與UI解耦,實現(xiàn)數(shù)據(jù)驅(qū)動開發(fā)。采用MVDM分層架構(gòu)(業(yè)務(wù)實體層、邏輯層、UI數(shù)據(jù)層、表示層),屏蔽系統(tǒng)差異,確保業(yè)務(wù)代碼穩(wěn)定。

技術(shù)交流:
- 移動端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
2、企業(yè)微信客戶端框架進化史
羅馬不是一天建成的,我們在開發(fā)框架方面,也經(jīng)歷了 發(fā)現(xiàn)問題、探索方案 、優(yōu)化改進 的過程。
野蠻生長(2019年前):
- 1)背景:團隊缺乏統(tǒng)一規(guī)范,開發(fā)風格各異;
- 2)問題:相同功能重復(fù)實現(xiàn),維護成本高。
初步探索(2019-2022):
- 1)背景:急需統(tǒng)一開發(fā)范式,提高開發(fā)效率;
- 2)實現(xiàn):EasyList框架,提出"一切皆列表"理念,封裝模板代碼,讓開發(fā)者專注于業(yè)務(wù)開發(fā);
- 3)問題:未嚴格隔離業(yè)務(wù)與UI,退化為MVC模式;抽象能力不足,組件復(fù)用率極低。
漸入佳境(2022-2024):
- 1)創(chuàng)新:實現(xiàn)了基于數(shù)據(jù)驅(qū)動/分層隔離的DataList框架;
- 2)價值:框架提供抽象能力,降低開發(fā)認知負擔;讓每一個組件都具備復(fù)用能力,極大提高了復(fù)用率,助力通用組件從個位數(shù)突破至50+。
3、企業(yè)微信客戶端框架整體設(shè)計
3.1 整體架構(gòu)設(shè)計
DataList是一套基于數(shù)據(jù)驅(qū)動的分層隔離框架,整體架構(gòu)圖如下圖所示。

▲ 圖1:DataList MVVM架構(gòu)圖
接下來將從數(shù)據(jù)流向、分層架構(gòu)的角度分別對這張圖進行講解。
3.2 數(shù)據(jù)流向設(shè)計
從數(shù)據(jù)流向的角度,DataList框架可以簡單分為Data/List兩部分:
- 1)List:業(yè)務(wù)邏輯部分,簡單來說就是業(yè)務(wù)數(shù)據(jù)如何轉(zhuǎn)換為UI數(shù)據(jù);
- 2)Data:數(shù)據(jù)驅(qū)動部分,UI數(shù)據(jù)如何渲染為實際的UI/如何驅(qū)動UI刷新。

▲ 圖2:DataList數(shù)據(jù)流向圖
3.3 MVDM環(huán)形分層設(shè)計
DataList通過將業(yè)務(wù)數(shù)據(jù)到UI數(shù)據(jù)的轉(zhuǎn)換邏輯獨立出來,系統(tǒng)形成了清晰的邊界層次:
- 1)業(yè)務(wù)實體層(Repo):負責請求數(shù)據(jù),拿到業(yè)務(wù)數(shù)據(jù)(保持穩(wěn)定);
- 2)業(yè)務(wù)邏輯層(ViewModel):處理業(yè)務(wù)邏輯,負責業(yè)務(wù)數(shù)據(jù)到UI數(shù)據(jù)的轉(zhuǎn)換(保持穩(wěn)定);
- 3)UI數(shù)據(jù)層(CellData/ViewData):對UI層的抽象(內(nèi)部適應(yīng)變化,對外接口穩(wěn)定);
- 4)表示層(Cell):處理具體UI渲染(擁抱變化,適配平臺新特性)。
相當于MVVM(Model-View-ViewModel)變成了MVDM(Model-View-Data-ViewModel)。
箭頭代表依賴指向:

▲ 圖3:DataList環(huán)形分層圖
這里介紹下UI數(shù)據(jù)層。
將整個控件數(shù)據(jù)化,即為ViewData:
export class TextData extends BaseData {
text?: string | Resource
fontColor?: ResourceColor
fontSize?: number | string | Resource
fontWeight?: number | FontWeight | string
將多個ViewData組合起來,成為一個組件CellData:
//由Image+Text組成
export class ImgTextCellData extends BaseCellData {
builder: WrappedBuilder<[]> = wrapBuilder(ImgTextCellBuilder)
root: RowData
img?: ImgData //對應(yīng)Image控件
text?: TextData //對應(yīng)Text控件
}
由于CellData內(nèi)不含任何業(yè)務(wù)代碼,所以不受限于業(yè)務(wù),天然可以復(fù)用。下圖是組件復(fù)用統(tǒng)計(現(xiàn)有58個組件,數(shù)千次復(fù)用)。

▲ 圖4:通用組件復(fù)用統(tǒng)計
這樣分層的好處:
- 1)方便UI大規(guī)模復(fù)用;
- 2)跨平臺代碼一致性;
- 3)隔離業(yè)務(wù)與UI,UI層變動不影響業(yè)務(wù)邏輯。
3.4 無可刪減:DataList開發(fā)示例
完美的達成,不在于無可增添,而在于無可刪減。 ——《風沙星辰》 安托萬·德·圣-埃克蘇佩里
梳理一下,開發(fā)一個業(yè)務(wù)需求,哪些部分是無可刪減的?
其實就是業(yè)務(wù)相關(guān)的部分:
- 1)數(shù)據(jù)請求;
- 2)業(yè)務(wù)數(shù)據(jù)轉(zhuǎn)為UI(UI數(shù)據(jù))。
這些都是必須由開發(fā)者填寫的邏輯,這些步驟框架最多只能簡化,不能代勞。
比如:我們開發(fā)一個極簡版本的人員列表,看下對應(yīng)步驟。
數(shù)據(jù)請求:
//Repo對應(yīng)Model層
class DemoContactRepo():IListRepository<DemoContactReq,DemoContactRsp> {
override fun requestData(
req: DemoContactReq,//請求參數(shù)
callback: (rsp: DemoContactRsp) -> Unit,//結(jié)果回調(diào)
errorCallback: (errorCode: Int, errorMsg: Any?) -> Unit//錯誤回調(diào)
) {
//請求數(shù)據(jù),返回
ContactService.getContact(req){contacts->
callback(contacts)
}
}
}
數(shù)據(jù)轉(zhuǎn)換:
//繼承自單數(shù)據(jù)源列表基類,泛型指明請求與返回的業(yè)務(wù)數(shù)據(jù)類型
class DemoContactViewModel: SingleListViewModel<DemoContactReq, DemoContactRsp>() {
/**
* 業(yè)務(wù)數(shù)據(jù)轉(zhuǎn)為UI數(shù)據(jù)
*/
overridefun transferData(data: DemoContactRsp): List<ICellData> {
returndata.contacts.map {
ImgPhotoTextImgCellData( //通用組件
dataId = it.id,
photo = PhotoData(url = it.avatar),//一個圖片控件
leftText = TextData(text = it.name))//一個文本控件
}
}
/**
* 拉取數(shù)據(jù)所用的倉庫(對應(yīng)Model層)
*/
overridefun initRepository(): IListRepository<DemoContactReq, DemoContactRsp> {
return DemoContactRepo()
}
/**
* 初次或刷新頁面時的請求參數(shù)
*/
overridefun refreshParam(arguments: Bundle?): DemoContactReq {
return DemoContactReq(0,20)
}
}
算上注釋,「總計39行」,一個極簡版聯(lián)系人列表就開發(fā)完成了。

▲ 圖5:DataList聯(lián)系人 Demo
如果是一個本地靜態(tài)頁面,可以去掉網(wǎng)絡(luò)請求部分,直接堆砌通用組件(CellData)即可,完整代碼只要40行。
//繼承自本地靜態(tài)列表基類,無數(shù)據(jù)請求
class DemoAttendanceViewModel:LocalSingleListViewModel() {
//...
//🔧 樂高式組件拼裝
overridefun transformCellDataList(): List<ICellData> {
return listOf(
attendanceCellData("打卡人員","員工A").section(1),
attendanceCellData("規(guī)則名稱","打卡規(guī)則abc").section(1),
attendanceCellData("規(guī)則類型","固定上下班").section(2),
attendanceCellData("打卡時間","周一至周五,09:00-10:00").section(2),
attendanceCellData("打卡方式","手機+智慧考勤機").section(3),
attendanceCellData("打卡位置","天府三街198號").section(3),
attendanceCellData("打卡Wi-Fi", "未設(shè)置").section(3),
attendanceCellData("打卡設(shè)備", "").section(3),
TextCellData(TextData.tips("位置和Wi-Fi滿足任意一項即可打卡")).noneDivider(),
attendanceCellData("加班規(guī)則","以加班申請為準").section(4),
attendanceCellData("更多設(shè)置","").section(5),
ButtonCellData(ButtonData("刪除規(guī)則", buttonStyle = R.style.button_l_white, textColor = R.color.day_night_color_chrome_red.getColor())).section(6))
}
//對通用Cell的簡單封裝
privatefun attendanceCellData(title:String,desc:String):ImgPhotoTextImgCellData{
return ImgPhotoTextImgCellData(/*設(shè)置屬性*/)
}
}

▲ 圖6:DataList靜態(tài)列表 Demo
3.5 MVDM架構(gòu)的延遲決策實踐
如果想設(shè)計一個便于推進各項工作的系統(tǒng),其策略就是要在設(shè)計中盡可能長時間地保留盡可能多的可選項。 ——《整潔架構(gòu)之道》
通過MVDM分層架構(gòu),我們構(gòu)建了業(yè)務(wù)邏輯與UI渲染的解耦機制。但真正的考驗來自鴻蒙Next開發(fā)——當?shù)讓覣PI如流沙般變動時,如何保持上層建筑的穩(wěn)定?
通過UI數(shù)據(jù)層的隔離,MVDM的UI層歷經(jīng)三個大版本的架構(gòu)演進,業(yè)務(wù)層仍保持穩(wěn)定:
- 1)妥協(xié)版:快速啟動業(yè)務(wù)開發(fā);
- 2)適配版:擁抱動態(tài)屬性能力;
- 3)優(yōu)化版:突破性能瓶頸。
這三次蛻變完美詮釋了"流沙筑城"的技術(shù)哲學:在持續(xù)變化的基礎(chǔ)設(shè)施上,通過架構(gòu)設(shè)計構(gòu)建確定性。接下來我們將深入每個階段的演變歷程。
4、第一版:系統(tǒng)限制下的妥協(xié)
4.1 目標:快速啟動
由于我們所有頁面都基于DataList開發(fā),需要盡快實現(xiàn)數(shù)據(jù)綁定能力,讓業(yè)務(wù)開發(fā)可以啟動。
4.2 實現(xiàn)思路
鴻蒙和Compose一樣,UI組件是函數(shù)而不是類,沒辦法像Android那樣,拿到控件的對象進行賦值。
@Component
export struct DemoPage{
build(){
Text("Hello World!") //這是一個函數(shù),沒法拿到它的對象,也就沒法進行動態(tài)賦值
}
}
如果要實現(xiàn)數(shù)據(jù)與UI的綁定,只能在這里對所有屬性進行遍歷調(diào)用.。
4.3 技術(shù)方案
在現(xiàn)有API的基礎(chǔ)上,我們只能實現(xiàn)這個方案。

▲ 圖7:數(shù)據(jù)綁定第一版
直接把所有屬性列出來,全部調(diào)一遍,如果data里對應(yīng)屬性沒有賦值,就相當于用null調(diào)用了一次。
4.4 實踐問題
這個方案有很多問題:
- 1)即使我在Data里只設(shè)置了一個屬性,也需要執(zhí)行一遍所有函數(shù);
- 2)某些屬性函數(shù),用null調(diào)用和不調(diào)用,表現(xiàn)是不一樣的,這種屬性無法列出;
- 3)太丑,不優(yōu)雅。
我們迫切需要一個能動態(tài)設(shè)置屬性的方案,因此我向華為官方提出了需求。

▲ 圖8:向華為提需求
這個需求交付之后,就有了第二版。
5、第二版:動態(tài)屬性下的數(shù)據(jù)綁定
5.1 接入動態(tài)屬性設(shè)置能力
之前提的需求,華為給的解決方案是AttributeModifer。
這是官網(wǎng)的介紹:

▲ 圖9:Modifier能力介紹
5.2 技術(shù)方案
接入AttributeModifer后,UI層的寫法如下:
@Component
export struct WwText {
@ObjectLink data: TextData
@State modifier: TextModifier = new TextModifier(new TextData())
aboutToAppear(): void {
this.modifier.data = this.data
}
build() {
Text(this.data.text)
.attributeModifier(this.modifier) //通過modifier更新屬性,不必再調(diào)其他函數(shù)
}
}
這里更新的原理大致如下圖:

▲ 圖10:第二版更新機制
TextData被@Observed注解之后,實際上是被動態(tài)代理了:
- 1)代理類觀察到屬性變化;
- 2)從記錄的set里找到觀察者;
- 3)調(diào)用觀察者的更新函數(shù)(實際流程比較復(fù)雜,很多調(diào)用);
- 4)這個更新函數(shù)里面就會執(zhí)行Modifier里面的applyNormalAttribute函數(shù),最后將屬性動態(tài)設(shè)置到控件上。
WwText編譯后的ts代碼如下:
//WWText.ts
export class WwText extends ViewPU {
//...
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
//這里就是會刷新的部分
Text.create(this.data.text);
Text.attributeModifier.bind(this)(ObservedObject.GetRawObject(this.modifier));
}, Text);
Text.pop();
}
}
5.3 實踐問題
實際使用中發(fā)現(xiàn),這套方案有兩方面很顯著的問題。
1)問題1:代碼膨脹:
在實際應(yīng)用這些Ww系列封裝組件的場景,可以看到編譯后的代碼膨脹的非常明顯,兩行編譯后變成了二十行。

▲ 圖11:ets源碼/ts產(chǎn)物
一個通用組件,編譯后從4k變成了75k。

▲ 圖12:編譯后體積變化
問題2:性能消耗:
這個寫法的性能也非常差,主要是三個方面。
1)冗余刷新:
在applyAttribute這里,如果TextData里面設(shè)置了10個屬性,但是本次只更新了一個屬性,那么在觸發(fā)更新之后,仍然會10個屬性都重新設(shè)置一遍。
export class TextModifier extends BaseModifier<TextAttribute> {
//...
applyAttribute(instance: TextAttribute, data: TextData) {
super.applyAttribute(instance, data)
if (data.fontColor || data.fontColor == 0) {
instance.fontColor(data.fontColor)
}
if (data.textAlign) {
instance.textAlign(data.textAlign)
}
//...
}
}
2)狀態(tài)管理:
現(xiàn)在鴻蒙這套狀態(tài)管理機制,在DataList數(shù)據(jù)綁定的場景下性能不足。查了一下鴻蒙狀態(tài)管理機制的源碼,狀態(tài)變量是通過動態(tài)代理來感知屬性變化的,具體一點就是通過SubscribableHandler來代理屬性的set、get等操作,源碼如下。
class SubscribableHandler{
get(target,property,receiver){
//...
switch(property){
default:
const result = Reflect.get(target,property,receiver)//反射獲取屬性
if(/*...*/){
let isTracked = this.isPropertyTracked(target, propertyStr);
this.readCbFunc_.call(this.obSelf_, receiver, propertyStr, isTracked);
}
}
}
}
經(jīng)過測試:這個get函數(shù)的耗時為萬次9ms。而我們的Modifier里面恰好有很多if,需要拿值來判斷。
簡單算一下,一個頁面10個cell,每個cell5個Text,每個Text23個屬性+45個基礎(chǔ)屬性:
一次刷新get次數(shù) = 10X5X(23+45) = 3400次
3400/10000X9 = 3ms
也就是說,沒有執(zhí)行任何具體邏輯,只是取值判斷,就消耗了「3ms」。而鴻蒙120幀率的情況,一幀的渲染時間也只有8.3ms。
3)節(jié)點增多:
對原生控件進行包裝后(Text ==> WwText),View樹里會增加一個節(jié)點(橙色)。如果某些情況圖方便給外層組件又設(shè)置了屬性,還會再額外增加一個渲染節(jié)點(紅色)。
比如下面這個組件:
Column(){
WwText({data:this.data1}).width("100%")
WwText({data:this.data2})
}
對應(yīng)的View樹如下:

▲ 圖13:節(jié)點增多示意
節(jié)點從兩個變成了五個,而鴻蒙的渲染性能優(yōu)化就是要求節(jié)點越少越好。
6、第三版:基于自定義狀態(tài)管理的性能優(yōu)化
6.1 目標:性能優(yōu)化
第三版的目標就是解決第二版的諸多問題,進行性能優(yōu)化。
6.2 實現(xiàn)思路
針對這些問題,分析的思路如下:

▲ 圖14:第三版問題分析
6.3 技術(shù)方案
1)去掉控件包裝:
前面提到使用包裝控件有兩個弊端:
- 1)編譯后的代碼增加,體積增大;
- 2)增加節(jié)點,消耗性能。
因此,我們決定去掉包裝,使用原生控件。
那么有兩個問題:
- 1)原本的控件基礎(chǔ)邏輯放哪里(比如WwPhoto里加載圖片的邏輯);
- 2)之前提到,我們用AttributeModifier時,控件的屬性函數(shù)我們可以動態(tài)調(diào)用,但是構(gòu)造函數(shù)不行,那如何更新構(gòu)造函數(shù)?
這兩個問題都可以用 AttributeUpdater來解決,它是AttributeModifier的子類。
劃重點:

▲ 圖15: AttributeUpdater說明-劃重點
去掉包裝類之后,原本放到包裝類里面的基礎(chǔ)邏輯,可以放到對應(yīng)的Updater里面。
例如:
- 1)WwText ==> Text + TextUpdater;
- 2)WwPhoto ==> Image + PhotoUpdater。
2)自定義狀態(tài)管理:
升級為Updater之后,如果對應(yīng)的Data仍然是狀態(tài)變量,那么我們?nèi)et的時候消耗依舊。 這里先解釋一下,為什么我們的Data要加@Observed注解。
按官方的用法,只有多層嵌套監(jiān)聽的場景才需要@Observed注解
其實這里是因為我們的所有業(yè)務(wù)邏輯都在ViewModel里面,而不是按照官方方案放在Page里。就會存在修改無法被感知的問題,如下圖所示。

▲ 圖16:為何要加@Observed
說回正題,既然要去掉這個官方的狀態(tài)管理,那么就有兩處改動:
- 1)去掉Data上的@Observed注解;
- 2)在View里面不再加狀態(tài)注解。
那么,如何驅(qū)動UI刷新?
正好,AttributeUpdater里面可以直接拿到attribute對象,可以通過這個對象直接設(shè)置屬性,那么問題就回到了如何感知Data屬性的變更。
正常情況首先想到的就是TypeScript的動態(tài)代理,即Proxy,鴻蒙的狀態(tài)管理就是這么做的,其實現(xiàn)基于前文提到的SubscribableHandler,里面用了反射,性能不足。想要不反射,要么就字符串匹配,依次調(diào)用對應(yīng)函數(shù),既然如此,不如徹底一點,直接使用靜態(tài)代理。
export class BaseData
//view的實例,由Update賦值和清理
ins?:INS
//用于刷新構(gòu)造函數(shù)
updateConstructorFunc?: () =>void
private _width?: Length
private _height?: Length
//...
set width(width: Length|undefined) {
this._width = width
this.ins?.width(width) //設(shè)置屬性時直接設(shè)置到view上
}
get width():Length|undefined{
returnthis._width
}
//...
最后,配套Updater的實現(xiàn)如下:
export class BaseUpdater> extends AttributeUpdater<T, C> {
data?: DATA
constructor(data?: DATA) {
super()
this.data = data
}
//用于批量刷新所有已設(shè)置的屬性,上屏或reuse時觸發(fā)
updateData(data?: DATA, instance?: T): BaseUpdater<DATA, T, C> {
//...
this.setUpdateFunc(this.data, ins)
if (ins) {
this.applyAttribute(ins, this.data)
this.refreshConstructor()
}
returnthis
}
//設(shè)置屬性
applyAttribute(instance: CommonAttribute, data: BaseData) {
if (data.width || data.width == 0) {
instance.width(data.width)
}
if (data.height || data.height == 0) {
instance.height(data.height)
}
//...
}
}
第三版的改動總結(jié)如下:

▲ 圖17:第三版改動總結(jié)
這些改動之后,通用組件內(nèi)部UI層的實現(xiàn)也需修改:
@Component
export struct ImgTextCell {
@Consume@Watch("updateData") cellData: ImgTextCellData
rootUpdater = new RowUpdater()
imgUpdater = new ImageUpdater()
textUpdater = new TextUpdater()
aboutToAppear() {
this.updateData()
}
aboutToReuse() {
this.updateData()
}
build() {
Row() {
Image(ImageUpdater.EMPTY).attributeModifier(this.imgUpdater)
Text().attributeModifier(this.textUpdater)
}.attributeModifier(this.rootUpdater)
}
//data與updater綁定
private updateData() {
this.rootUpdater.updateData(this.cellData.root)
this.imgUpdater.updateData(this.cellData.img)
this.textUpdater.updateData(this.cellData.text)
}
}
雖然Cell內(nèi)部實現(xiàn)變化很大,但是對業(yè)務(wù)方來說,CellData和Data的對外使用方法沒有變化。
Data與Updater為何要分開。
其實這里的Cell寫法看起來還是有優(yōu)化空間的,比如你可能會想到,為何不把Data和Updater結(jié)合到一起,比如:
export class BaseData extends BaseUpdater{
//...
}
然后Cell的寫法就可以簡化成:
@Component
export struct ImgTextCell {
@Consume cellData: ImgTextCellData
build() {
Row() {
Image(ImageUpdater.EMPTY).attributeModifier(this.cellData.img)
Text().attributeModifier(this.cellData.text)
}.attributeModifier(this.cellData.root)
}
}
分兩種情況討論一下:
- 1)修改Data內(nèi)部的值:這兩種寫法,都是通過AttributeUpdater內(nèi)部的attribute對象進行更新,都是改那個更新哪個,沒毛病;
- 2)增/刪/改 Data對象本身。

▲ 圖18:修改 Data 本身的兩種情況
6.3 升級效果
1)體積降低:
以PhotoTextCell為例,升級之后代碼編譯后的體積明顯降低了,僅為升級前的9.3%。

可以再對比下編譯后的內(nèi)容。
ets源碼:
build() {
Row() {
Image("").attributeModifier(this.imgUpdater)
Text().attributeModifier(this.textUpdater)
}.attributeModifier(this.rootUpdater)
}
ts產(chǎn)物:
initialRender() {
this.observeComponentCreation2((elmtId, isInitialRender) => {
Row.create();
Row.attributeModifier.bind(this)(this.rootUpdater);
}, Row);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Image.create("");
Image.attributeModifier.bind(this)(this.imgUpdater);
}, Image);
this.observeComponentCreation2((elmtId, isInitialRender) => {
Text.create();
Text.attributeModifier.bind(this)(this.textUpdater);
}, Text);
Text.pop();
Row.pop();
}
可以看到編譯產(chǎn)物少了很多層嵌套,代碼結(jié)構(gòu)清爽多了,我們的hap當時改完之后體積直接少了「十幾M」。
2)性能提升:
升級之后性能也有明顯提升:
- 1)通用組件PhotoTextCell的復(fù)用耗時從4.3ms降低到0.9ms;
- 2)首頁的會話列表,復(fù)用的幀率由卡頓的32幀提升到絲滑的118幀。
由于鴻蒙的動態(tài)幀率機制,118其實就是滑動時滿幀。

▲ 圖19:升級前后幀率對比
7、本文小結(jié)
在鴻蒙生態(tài)快速迭代的"流沙"環(huán)境下,DataList框架通過三重熵減機制構(gòu)建了確定性開發(fā)范式,鴻蒙DataList的三次技術(shù)演進本質(zhì)是一場對抗API不確定性的架構(gòu)實踐。
簡單總結(jié)一下:
1)第一版(妥協(xié)版):基于API遍歷屬性實現(xiàn)基礎(chǔ)數(shù)據(jù)綁定,雖快速啟動業(yè)務(wù)開發(fā)但存在冗余調(diào)用與性能隱患;
2)第二版(適配版):引入AttributeModifier動態(tài)屬性機制,可進行屬性的動態(tài)更新,卻因狀態(tài)管理機制本身的性能消耗和控件包裝導(dǎo)致代碼膨脹與性能劣化;
3)第三版(優(yōu)化版):創(chuàng)新采用自定義狀態(tài)管理,剝離包裝層直接操作原生控件,結(jié)合AttributeUpdater實現(xiàn)靜態(tài)代理與精準屬性更新,使通用組件編譯體積縮減至9.3%、復(fù)用耗時降低79%,幀率從32幀躍升至118幀。
三次架構(gòu)升級始終貫徹MVDM分層理念,通過UI數(shù)據(jù)層的隔離,實現(xiàn)業(yè)務(wù)邏輯零修改適配UI層巨變。包含這三次主要的升級在內(nèi),過去一年DataList的UI層經(jīng)歷了十多次改動(包括API變化與對鴻蒙了解更深入而進行的性能優(yōu)化)。這些變更揭示了"流沙筑城"的核心邏輯:「表層擁抱變化,中層消化沖擊,核心業(yè)務(wù)層保持穩(wěn)定」。
UI數(shù)據(jù)層在此場景中負責消化技術(shù)變化帶來的沖擊,允許團隊:
- 1)通過接口抽象延遲具體實現(xiàn)決策;
- 2)在知識完備后通過實現(xiàn)替換進行漸進式優(yōu)化;
- 3)保持核心業(yè)務(wù)代碼的語義穩(wěn)定性。
這些最終讓企業(yè)微信鴻蒙團隊于2024年底完成了企業(yè)微信鴻蒙NEXT第一版「100萬行,600+頁面」的開發(fā),并成功發(fā)布。
至此,關(guān)于企業(yè)微信鴻蒙NEXT開發(fā)架構(gòu)演進講解完畢。
8、相關(guān)資料
[1] 微信純血鴻蒙版正式發(fā)布,295天走完微信14年技術(shù)之路!
[2] 鴻蒙NEXT如何保證應(yīng)用安全:詳解鴻蒙NEXT數(shù)字簽名和證書機制
[3] 開源IM聊天程序HarmonyChat:基于鴻蒙NEXT的WebSocket協(xié)議
[4] 大型IM工程重構(gòu)實踐:企業(yè)微信Android端的重構(gòu)之路
[5] 企業(yè)微信的IM架構(gòu)設(shè)計揭秘:消息模型、萬人群、已讀回執(zhí)、消息撤回等
[6] 企業(yè)微信針對百萬級組織架構(gòu)的客戶端性能優(yōu)化實踐
[7] 企業(yè)微信客戶端中組織架構(gòu)數(shù)據(jù)的同步更新方案優(yōu)化實戰(zhàn)
[8] 微信團隊分享:微信支付代碼重構(gòu)帶來的移動端軟件架構(gòu)上的思考
[9] 微信團隊原創(chuàng)分享:微信客戶端SQLite數(shù)據(jù)庫損壞修復(fù)實踐
[10] 從客戶端的角度來談?wù)勔苿佣薎M的消息可靠性和送達機制
[11] 愛奇藝技術(shù)分享:愛奇藝Android客戶端啟動速度優(yōu)化實踐總結(jié)
[13] 移動端IM實踐:Android版微信如何大幅提升交互性能(一)
[14] 百度公共IM系統(tǒng)的Andriod端IM SDK組件架構(gòu)設(shè)計與技術(shù)實現(xiàn)
[15] 首次公開,最新手機QQ客戶端架構(gòu)的技術(shù)演進實踐
[16] IM開發(fā)干貨分享:有贊移動端IM的組件化SDK架構(gòu)設(shè)計實踐
[17] 馬蜂窩旅游網(wǎng)的IM客戶端架構(gòu)演進和實踐總結(jié)
[18] 蘑菇街基于Electron開發(fā)IM客戶端的技術(shù)實踐
[19] IM開發(fā)干貨分享:我是如何解決大量離線消息導(dǎo)致客戶端卡頓的
(本文已同步發(fā)布于:http://www.52im.net/thread-4812-1-1.html)
作者:Jack Jiang (點擊作者姓名進入Github)
出處:http://www.52im.net/space-uid-1.html
交流:歡迎加入即時通訊開發(fā)交流群 215891622
討論:http://www.52im.net/
Jack Jiang同時是【原創(chuàng)Java
Swing外觀工程BeautyEye】和【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
本博文
歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處(也可前往 我的52im.net 找到我)。