欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android端內數據狀態(tài)同步方案VM-Mapping詳解

 更新時間:2021年09月07日 14:50:30   作者:冬天的毛毛雨  
這篇文章主要介紹了Android端內數據狀態(tài)同步方案VM-Mapping詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下

背景

西瓜在feed、詳情頁、個人主頁有一塊功能區(qū),包括了點贊、收藏、關注等功能。這些功能長久以來都是孤立的:多個場景下點贊、收藏、關注等狀態(tài)或數量不一致。在以往的業(yè)務迭代中,都是業(yè)務A有了需求,就加個點贊的請求,把自己業(yè)務模塊的UI更新下就完事了,業(yè)務B也自己搞一下。當西瓜開始從切面發(fā)力互動業(yè)務的時候,這些問題就凸顯出來了。線上出現了很多在頁面A點贊/收藏完一個視頻到頁面B點贊/收藏狀態(tài)或者點贊/收藏數不對的case。

例如:

問題拆解

在分析這塊業(yè)務時,梳理出幾種問題:

  1. 業(yè)務上場景太分散,體現到代碼上就是在activity、scene、viewholder、自定義view等各種個樣的容器,多個業(yè)務模塊、多個端(web、flutter)上都有很相似的操作,代碼跨度很大。
  2. 存量的代碼中有些場景是處理過同步問題的,但是處理的又不徹底,方案也不一樣,比如有的情況用了全局注冊callback,來通知所有對結果敏感的場景;有的情況用了Eventbus;有的情況是更新內存,但是卻只是個別幾個模塊通用。
  3. 一部分問題是原來的業(yè)務邏輯,比如,使用更新后的內存變量在多個頁面或者模塊傳遞引用,由于層次比較深引用值被中間的流程篡改。
  4. 一部分問題是服務端數據邏輯問題。

其中3、4點問題更像是邏輯bug。

多個端的數據同步可以通過跨端事件,每個端收到事件后更新自己就行。所以最復雜最難搞的問題就是端內多場景下的數據狀態(tài)同步問題。

端內問題聚焦在幾個case:

  • case1:普通頁面,如Activity or Fragment上的狀態(tài)同步;
  • case2:feed卡片的狀態(tài)同步;
  • case3:feed卡片內多個復雜層級之間的狀態(tài)同步;
  • case4:以上的組合。

目標

  • 數據狀態(tài)同步,是要保證兩個一致性:數據一致性、UI一致性;
  • 方案要使用簡單,理解簡單;
  • 盡可能減少性能開銷。

方案調研

EventBus

這個方案的本質是:監(jiān)聽者收到事件->更新UI/更新數據Model

  • 對于case1:如果是A頁面發(fā)起,B頁面被動接收,只需要在B頁面接收事件,更新B頁面的Model對象+UI即可。但是在收到事件之后,一定要把當前頁面的model對象更新,不然會有不一致的問題。
  • 對于case2
  1. eventbus注冊在ViewHolder 上:由于ViewHolder的復用,ViewHolder的數量是少于“ListData”的,那么意味著,只在ViewHolder上監(jiān)聽,會出現那些沒有和ViewHolder 建立聯系的數據無法被更新到。如果使用黏性事件,該事件會一直在內存中,粘性事件的膨脹不可控,很可能會造成嚴重的內存問題。
  2. eventbus注冊在Activity or 其它頁面上,收到事件后,遍歷數據列表,更新,然后通過RecyclerView的onDataItemChanged方法局部更新。但是在很多場景,比如西瓜feed,feed框架之下的view層次非常深。很多時候Rd只關注某類卡片下的某個UI組件,Feed框架和頂層頁面容器離的很遠,修改成本高,容易出錯,對feed框架或者頂層容器的侵入比較大。另外,onDataItemChanged的局部更新是ViewHolder 對應的itemView的,這個維度比較大,并不能刷新單獨的一個點贊按鈕。

基于k-v的監(jiān)聽、通知

以對象id為key,某個屬性值如點贊數為value。事件發(fā)生時,將修改值寫入k-v列表,監(jiān)聽者全部監(jiān)聽這個變化。當新進入一個場景時,查詢k-v列表作為最新值。這個方案和Eventbus粘性事件很像。

  • k-v 粒度太細,一直在內存中,非常容易膨脹,沒有合適的釋放時機,導致內存浪費;一旦移除,就可能概率的數據同步失效。
  • k-v列表內的狀態(tài)要使用者在合適的時機同步到業(yè)務層數據Model。

全局共享數據Model實例

同一個數據Model對象,比如一個卡片Model,每次更新都是全局可見的。但是很明顯,

  • 對數據Model的要求很高。一個業(yè)務層數據Model類型,要全局統一,比如,一個視頻卡片業(yè)務層的類型是“ModelA”,那么全局場景不能有“ModelB”表示卡片。在很多場景下,業(yè)務層會對原始數據Model進行包裝適配;
  • 內存占用很大;可能要緩存很多個列表。

基于注解的對象映射方案VM-Mapping

特點

  1. 以命名空間+指定字段值 為key,匹配相同注解名的字段的映射,打平了Model類型的不同、層級嵌套的約束;
  2. 直接更新結果到數據model(如article),與數據model視角的同步;
  3. 打平了多個頁面、復雜view層級嵌套的差異;
  4. 自動處理更新,使用者僅需要關心怎么更新UI,不需要考慮數據Model的一致性;
  5. 任意場景的支持。

思考

  1. 數據狀態(tài)同步,到底同步的是什么?
  2. 上述的方案中大致有幾個角色:事件、監(jiān)聽者、數據Model、UI。到底誰應該是主導者?
  3. 基于事件的方案都需要把狀態(tài)同步給數據Model,能簡化嗎?

圖片

這個過程中有四個角色,三個操作。

突破View層級的限制

從MVVM說起。

MVVM是一種軟件設計典范,用一種業(yè)務邏輯、數據、界面顯示分離的方法組織代碼。

MVVM本質上是一種數據驅動UI的理念。從這個理念看,數據狀態(tài)同步,同步的是數據Model,UI的變更是由數據的變更引起的,真正關注的點應該在數據本身上。

這樣,就不再需要額外一個接受事件的“容器”,來控制數據和UI了。到現在,只有三個角色,兩個操作了。

再回過頭看,為什么跨頁面、跨多View層級很難找到一個通用方案,是因為總在找一個“容器”來承載事件的接受,然后再做雙份(數據和View)的同步。而且這個“容器”通常本身就是一個頁面,或者其它不同層級上的view,本身就存在很多樣化,為這種多樣化適配,就會讓事情變得復雜。

假如不再找額外的“容器”,直接把監(jiān)聽綁定在數據上,那么View層級的限制也就不存在了。因為不管在什么場景,什么層級,真正的邏輯中心都是數據,View也是通過數據渲染出來的,View不關心自己在什么層級,只關心數據的變化。

突破類型的限制

這里有幾個類型的限制:

  1. 數據Model的類型是否只能一成不變,假如網絡請求的原始數據是A類型,在場景1直接用了A類型,在場景2為了適配UI對A做了包裝:

雖然類型不同,但是對A、B來說,都是要更新diggStatus的;

  1. 在Android,數據Model的類型是強類型,是從網絡由二進制流反序列化出來的,那么同一個二進流,既可以反序列化成A類型,又可以反序列化成B類型,只要滿足反序列化規(guī)則就行。但是事實上,他們的業(yè)務本質還是一個東西。
  2. 事件本身也是一個數據,只是它是用戶操作發(fā)起的,表象看和數據Model無關,但是一個事件既然能更新某個數據Model,那他們一定存在著對應關系。

這個問題的本質是,類型約束是語言特性,但是和業(yè)務屬性無關,只要他們能確認是一個業(yè)務含義,不管他們怎么換“馬甲”,他們總是能匹配上的。

這樣就演變成了:

  1. 怎么確定兩個類型是一個業(yè)務含義;
  2. 怎么確定屬性的對應關系(字段匹配)。

第一個好說,主要能有唯一的業(yè)務標識,就能確定是一個業(yè)務含義;怎么確定屬性的對應關系呢?

現有的技術體系里就有可以借鑒的思想:數據庫的使用。像jetpack 的Room組件:

可以看到,我們只要要在應用層這么定義一個數據Model叫User,為它加上注解,就可以把數據庫中的字段和我們的數據對應上。那么方案呼之欲出,注解是可以完成屬性匹配的。

于是乎整個流程就簡化成了:

這個流程可以看到,只剩下了兩個角色,和兩個操作了。

所謂數據更新UI,就是View-Model;數據映射數據,就是Data-Mapping,于是這個方案的名稱就是VM-Mapping。

詳細設計

需要對上述抽象流程做實現。

映射

前面說到,映射關系由注解維護,一個有三個注解:

  • Mappable注解 :

標注在class上,用來識別這個類是不是可以被處理。

其中mappingSpace是命名空間,表示是“一類”數據,可以和數據庫表名對比理解,mappingSpace就是tableName。

  • PrimaryKey注解:

標記在字段上,被標記的字段作為Model對象的唯一標識。

mappingSpace+PrimaryKey的值,就是在映射關系中的唯一業(yè)務標識。

  • MappableKey注解:

標注在字段上,需要被映射對應的字段

映射關系說明:

數據驅動UI

Android里有很多類似理念的東西,比如LiveData,就是數據更新通知到UI上。本質上數據驅動UI,就是在數據Data<->UI 之間建一個“橋梁”。

這個不過LiveData并不適合用在這里,理由是:

  • LiveData綁定的生命周期是LifecycleOwner,也就是Activity、Fragment維度,明顯我們的場景維度更細;
  • 直接observeForever也可以,但是由于View層級的多樣,調用方通常需要合適的時機移除;
  • LiveData 強引用了數據Data,這個“橋梁”本身對數據Data的生命周期造成了影響。

VM-Mapping做了個簡單方案。用了兩級HashMap,一級HashMap使用業(yè)務唯一標識(mappingSpace+PrimaryKey的值)為KEY,二級使用WeakHashMap,以數據Model實例為KEY,XGViewModel為VALUE。維護數據Data 和 UI回調之間的關系:

XGViewModel維護了通知給UI的弱引用回調合集。一個數據Model實例對應了一個XGViewModel。

當映射發(fā)生時,會通過業(yè)務標識Key,查找所有還沒有被回收的數據Model實例,然后通過對應的XGViewModel通知UI自己的變更。

總體流程

在這個流程中,業(yè)務使用只需要關心發(fā)起映射數據和更新視圖。

因為存在列表,那么會有一個列表的維護者,就是所謂的映射中心。映射中心有兩個核心能力:

  1. 收集需要被更新的數據Model列表;
  2. 查找匹配。

其它細節(jié)

  • 因為使用了反射,為了減少性能損耗,會對收集的數據Model類型做class和相關字段的緩存。
  • 列表存在膨脹現象,二級弱引用列表的key是數據Model實例本身,當它被虛擬機回收的時候,會把一級列表中的該項移除,當一級列表某個key下沒有內容時,也會把該key移除。
  • 移除的時機在每次添加數據Model到列表;
  • 移除的條件是一級列表長度達到閾值。

但是注意,這個移除并不會影響VM-Mapping的能力,因為VM-Mapping關注的是數據本身,當數據被回收的時候,不會有任何場景會用到這個數據,自然也不用關心是不是需要通知到它。

  • 為了避免影響主線程,和多線程競爭列表的問題,映射中心操作都在單子線程中處理。

方案對比

方案 優(yōu)勢 劣勢
Eventbus 理解成本低 事件、UI、數據Model三個角色都要保持一致,適配各種場景的成本高,不通用。
全局共享數據Model實例 使用簡單 條件苛刻;占用內存,膨脹不可控制。
基于k-v的監(jiān)聽、通知 各場景通用 粒度太細導致內存不可控制,移除策略會導致同步失效。事件需要手動同步數據Model。
VM-Mapping 使用簡單,不需要手動同步回數據Model,在所有場景下通用。 用到了反射,有一部分性能損耗。

方案收益

西瓜在之前遺留了大量的類似問題,一直沒有好的方案解決,要么存在根本性缺陷,要么實施成本高。VM-Mapping支持了在西瓜中視頻相關的核心場景快速接入,實現了線上點贊數異常問題清零。

后續(xù)計劃

  • 根據統計,由于使用運行時注解+反射,一個操作的耗時均值在10ms左右。仍然有可以優(yōu)化的空間。可以考慮使用編譯時注解維護數據映射關系。
  • 目前訂閱數據的變化,維度是數據本身,而不是變化的字段,可以考慮通過kotlin delegate 細化監(jiān)聽維度。

到此這篇關于Android端內數據狀態(tài)同步方案VM-Mapping詳解的文章就介紹到這了,更多相關Android端內數據狀態(tài)同步方案VM-Mapping內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • OpenGL Shader實現簡單轉場效果詳解

    OpenGL Shader實現簡單轉場效果詳解

    轉場效果常出現再視頻剪輯當中,用于銜接兩段視頻片段切換的過渡效果。本文將介紹如何利用OpenGL Shader實現簡單的轉場效果,需要的小伙伴可以參考一下
    2022-02-02
  • Android Bitmap像素級操作詳解

    Android Bitmap像素級操作詳解

    這篇文章主要介紹了Android Bitmap像素級操作詳解,想了解Bitmap的同學可以參考下
    2021-04-04
  • Android ViewFlipper簡單用法解析

    Android ViewFlipper簡單用法解析

    這篇文章主要為大家詳細介紹了Android ViewFlipper簡單用法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • Android開發(fā)中如何去掉app標題欄的實現

    Android開發(fā)中如何去掉app標題欄的實現

    這篇文章主要介紹了Android開發(fā)中如何去掉app標題欄的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • Android四大組件之Activity深入解讀生命周期

    Android四大組件之Activity深入解讀生命周期

    雖然說我們天天都在使用Activity,但是你真的對Activity的生命機制完全了解了嗎?Activity的生命周期方法只有七個,但是其實那只是默認的情況。也就是說在其他情況下,Activity的生命周期可能不會是按照我們以前所知道的流程,這就要說到Activity的啟動模式
    2022-07-07
  • AndroidStudio項目制作倒計時模塊的方法

    AndroidStudio項目制作倒計時模塊的方法

    本篇文章主要介紹了AndroidStudio項目制作倒計時模塊的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • internal修飾符探索kotlin可見性控制詳解

    internal修飾符探索kotlin可見性控制詳解

    這篇文章主要為大家介紹了internal修飾符探索kotlin可見性控制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-11-11
  • Android10 App啟動Activity源碼分析

    Android10 App啟動Activity源碼分析

    這篇文章主要為大家介紹了Android10 App啟動Activity源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • android自定義Camera實現錄像和拍照

    android自定義Camera實現錄像和拍照

    這篇文章主要為大家詳細介紹了android自定義Camera實現錄像和拍照功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • Android?RecyclerLineChart實現圖表繪制教程

    Android?RecyclerLineChart實現圖表繪制教程

    這篇文章主要為大家介紹了Android?RecyclerLineChart實現圖表繪制教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12

最新評論