Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 494, comments - 13, trackbacks - 0, articles - 1

          本文由企業(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)定。

          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é)

          [12] 偽即時通訊:分享滴滴出行iOS客戶端的演進過程

          [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 找到我)。


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 大丰市| 吉首市| 呼图壁县| 太仓市| 台州市| 通河县| 夏河县| 阜阳市| 泰兴市| 巴中市| 昌平区| 呼玛县| 横峰县| 嘉禾县| 临沭县| 固镇县| 鱼台县| 新邵县| 沁阳市| 庐江县| 怀来县| 保山市| 平武县| 舒城县| 中西区| 治县。| 望江县| 临猗县| 开远市| 武陟县| 沙雅县| 贵溪市| 旺苍县| 怀宁县| 渝北区| 临沧市| 湘西| 宁夏| 融水| 塘沽区| 张家港市|