B 站直播間基於視圖交互的架構演進

本期作者

杜峯

嗶哩嗶哩資深開發工程師

背景

  1. B 站的直播間作爲整個 APP 中交互最爲複雜的單頁面之一,其承擔的業務量已經不亞於一個小型 APP。對比 APP 的結構會發現許多相同處,但與組成 APP 的各個獨立 Activity 不同,直播間由各個獨立的視圖組成。

圖片

從 APP 維度看每個 Activity 是一個業務單元,類比直播間維度每個直播間中的 View 是一個業務單元。

  1. 業務邏輯基於 MVVM 的設計分爲三層(Service 即 M 層),理想狀態下各個業務間的交互是內聚的,各業務間不會感知到其他業務的存在,View 顯示需要的數據和狀態都由各自對應的 ViewModel 提供。

圖片

  1. 現實情況中業務交互並沒有那麼理想,直播間中的一個 View 顯示的數據會受到其他業務的數據、狀態影響,因此一個 View 除了需要處理自己內聚的邏輯爲還需要關心其他 View 的數據變化。

01 直播間業務交互現狀

** 1.1 現有交互邏輯**

直播間是一種單頁面強交互型業務場景,一個業務就經常需要會關心其他業務的狀態,因此垂直方向拓展業務場景就會很多,直播間中的業務幾乎都是在垂直屏幕方向上進行拓展的,產品在新增業務時往往會將重要的業務放在更顯眼的地方,因此需要儘量使重要的業務不被遮擋。

然而,直播間的視圖又不是一成不變的,與常規頁面面對的業務場景不同,直播間的視圖除了需要響應用戶自己的操作外,還需要根據主播和其他正在看直播的用戶操作展示和改變視圖,爲了保證整體展示邏輯沒問題經常會有視圖聯動的需求。

下圖列舉了部分直播間業務的構建位置和層級關係,按層劃分類似的視圖直播間中有 60 多個:

圖片

現存的設計將大部分業務邏輯集中在 ViewModel 中處理,因爲有着 LiveData 的存在,數據變化的監聽在 ViewModel 和 View 間變的容易。

當其他 ViewModel 已經存在自己 View 關心的 LiveData 或調用方法時,開發者很自然的會去引用一個現有的其他 ViewModel 來觀察他持有的 LiveData 或調用其方法。

圖片

** 1.2 現有交互邏輯帶來的問題**

不規範的 LiveData 的使用和糅雜在一起的業務邏輯導致了 ViewModel 引用的濫用,使得 View 間的耦合愈發嚴重,下圖是現有直播間 ViewModel 的引用關係:

圖片

直播間內日益複雜的業務會進一步加重 View 之間的耦合,在分析了上圖中的引用關係後,會發現目前之所以會出現 View 引用其他業務的 ViewModel 的場景,主要有兩個原因:

  1. 與 View 對應的 ViewModel 無法提供需要的數據,其他 Viewmodel 持有需要的數據

  2. View 的某個操作或數據改變需要告知其他業務,其他 Viewmodel 包含需要調用的方法

直播間內日益複雜的業務會進一步加重 View 之間的耦合,特別是在新增的業務對老業務有改動時,開發人員慣性的去尋找有沒有現成的邏輯處理,如果有就會想辦法複用,而現存的設計將大部分業務邏輯集中在 ViewModel 中處理,就必定會導致 ViewModel 引用的不可控。

** 1.3 問題的分析**

直播間各業務引用關係錯亂只是表象,最直接的原因就是業務數據的訪問的不規範,錯綜複雜的引用關係會加重業務間的耦合情況,耦合的業務邏輯又會增加業務加載流程和數據分發的複雜度,週而復始,形成了惡性循環。

圖片

針對以上問題打破惡性循環,我們通過腳本分析了直播間內 60 + 業務模塊,列出了 1400 + 個引用 ViewModel 的具體使用場景,並且整理了的理想中的數據提供方作爲後續改造的參考:

圖片

02 基於視圖劃分的業務數據

直播間中的業務使用 MVVM 的結構構建,我們提供了一套統一的構建模板來構建和管理各層邏輯,單個業務中每層有各自維護的數據和狀態信息,這些數據禁止躍層訪問,並跟隨各層的生命週期創建和銷燬。

圖片

** 2.1 數據使用場景**

數據使用分爲三個場景:初始化數據,交互數據,對外提供數據

圖片

初始化數據

  1. 一個業務的初始化一般處於房間加載的某個任務中

  2. 業務初始化,由當前任務提供該階段可以訪問的數據,作爲初始化數據

  3. 初始化數據會轉化爲業務專有的數據結構(圖中 Data),供內部邏輯、視圖使用和管理

交互數據

  1. 一個房間所有業務初始化完成後,如果沒後續的交互,理論上是完全靜止的,任何改變當前直播間的動作都可以看做是一個交互,而每個交互都會帶上一些數據

  2. 用戶每次對業務 View 的點擊、滑動等都會產生一些事件並帶上相應的數據,這些事件可能直接在 View 層就已經消費掉,也可能會觸發一系列的邏輯交互

  3. Socket 和 Http 的響應作爲另一類交互數據的來源,由 Service 層向上通知到各個業務邏輯層,業務邏輯需要監聽這些數據變化做相應處理

  4. 此外每個業務都可能會關心其他業務的改變,這些往往改變也會帶來一些數據,依據這些數據業務可能需要對自己的邏輯和 View 進行相應的操作

對外提供數據

  1. 在交互數據裏有提到關心其他業務的變化,這部分的變化應該由每個業務在 API 中決定暴露那些事件和數據供外部使用

  2. 如果關心某個業務的變化,可以通過 ServiceManager 獲取對應業務的 API,通過關心業務 API 暴露的方法來獲取、訂閱數據

    a. provideData 類型方法:對外暴露提供數據的方法,由類型方法提供的數據表示,該業務可以對外提供的數據

    b. notifyChange 類型方法:有數據變化需要對外通知時對外暴露的方法,其他業務通過該類型方法可以訂閱相應的變化通知

  3. 每個業務在提供數據時應該考慮清除需要暴露的數據,不可直接暴露 Data 給外部使用

  4. 每個業務在接收到其他業務的變化通知時,應該在對應的處理裏消費掉傳過來的數據,不要持有該部分數據

** 2.2 數據的流向**

  1. 進入直播間時會請求一組初始化接口,響應數據將會由數據分發器管理,分發到各個業務的 Services,不同各個業務拿到各自關心的數據後放到各自的 businessData 中

  2. 各業務的 Service 中將會管理業務所持有的數據,ViewModel 想要獲取或改變某個數據時,需要持有對應業務的 Service

  3. ViewModel 中將各業務的原始數據組合處理後通過 LiveData 通知對應的 View,View 可以通過 ViewModel 對原始數據進行修改

  4. View 間的事件(純粹的 UI 變化)將由 ViewEventManager 作爲通道進行傳遞,傳遞過程中的數據爲一次性數據,不可作爲該次事件處理外的邏輯數據使用

圖片

03 直播間的視圖結構和業務區域

** 3.1 直播間的結構和區域劃分 **

  1. 在加入上下滑邏輯之前,房間的概念與整個 Activity 等價,一個房間在 Activity 被銷燬時釋放所有資源

  2. 在加入上下滑邏輯後,房間的概念變爲滑動組件中的一個 Item,一個房間在 Item 被划走時釋放所有資源,爲了更好的理解業務運行邏輯,我們根據直播間的視圖結構對業務區域進行了劃分

    a. 容器區域(Global)包含滑動組件和 DIALOG 業務層(目前僅話題和各種引導用到),在進房時創建該區域

    b. 房間區域(Room)包含房間業務層和播放器業務層,這兩部分視圖均掛載在滑動組件的 RoomItem 上,在滑動停止時創建該區域

  3. 在用戶執行的滑動操作停止後會釋放上一個 RoomItem 的資源,並重新創建房間區域掛載到停止後的 RoomItem 根佈局上,而容器區域中的資源仍然隨 Activity 的銷燬而銷燬,當前的房間區域也會隨容器區域的銷燬而銷燬

  4. 業務僅需要聲明自己屬於 Global 還是 Room 區域,並在創建、銷燬的回調中編寫邏輯,而不需要關心自己何時被創建和銷燬

圖片

** 3.2 按區域劃分加載流程**

  1. 以構建 item 中房間容器的時機爲分割點,之前的加載流程屬於容器區域,之後的加載流程屬於房間區域

  2. 房間初始化接口請求(P0、P1 接口)比較特殊,請求時機以及請求的上游處理邏輯屬於容器區域,但是接口響應數據的處理邏輯屬於房間區域

  3. 在直播間銷燬的流程中,在容器銷燬流程和房間銷燬流程中都需要銷燬的邏輯,歸爲房間區域管理,僅在容器銷燬流程中銷燬的邏輯歸容器區域管理

圖片

04 架構演進中的一些思考

  1. 架構最後是爲業務需求場景服務的,那它也要順應業務的變化而適時調整。來 B 站直播的期間經歷了直播間從不能滑動到可以上下滑,從老直播間爲主到以新版直播間爲主,整個產品交互形態發生了巨大變化,新的架構演進方向往往取決於對新業務形態的認知。

  2. 隨着業務的不斷髮展和改變以及組織架構的調整,因爲趕工期、圖方便而設計不合理但剛好能用的代碼會越來越多,原先用起來很順暢的架構必定會慢慢腐爛變質,一直修修補補只是在掩飾問題和推遲問題的爆發,作爲一線開發我完全可以理解開發時的內心想法:

  1. 基於以上思考,我認爲架構演進的目的主要有兩個:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/cLSqU7SnngaIpBZh1Uez3w