B 站直播間基於視圖交互的架構演進
本期作者
杜峯
嗶哩嗶哩資深開發工程師
背景
- B 站的直播間作爲整個 APP 中交互最爲複雜的單頁面之一,其承擔的業務量已經不亞於一個小型 APP。對比 APP 的結構會發現許多相同處,但與組成 APP 的各個獨立 Activity 不同,直播間由各個獨立的視圖組成。
從 APP 維度看每個 Activity 是一個業務單元,類比直播間維度每個直播間中的 View 是一個業務單元。
- 業務邏輯基於 MVVM 的設計分爲三層(Service 即 M 層),理想狀態下各個業務間的交互是內聚的,各業務間不會感知到其他業務的存在,View 顯示需要的數據和狀態都由各自對應的 ViewModel 提供。
- 現實情況中業務交互並沒有那麼理想,直播間中的一個 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 的場景,主要有兩個原因:
-
與 View 對應的 ViewModel 無法提供需要的數據,其他 Viewmodel 持有需要的數據
-
View 的某個操作或數據改變需要告知其他業務,其他 Viewmodel 包含需要調用的方法
直播間內日益複雜的業務會進一步加重 View 之間的耦合,特別是在新增的業務對老業務有改動時,開發人員慣性的去尋找有沒有現成的邏輯處理,如果有就會想辦法複用,而現存的設計將大部分業務邏輯集中在 ViewModel 中處理,就必定會導致 ViewModel 引用的不可控。
** 1.3 問題的分析**
直播間各業務引用關係錯亂只是表象,最直接的原因就是業務數據的訪問的不規範,錯綜複雜的引用關係會加重業務間的耦合情況,耦合的業務邏輯又會增加業務加載流程和數據分發的複雜度,週而復始,形成了惡性循環。
針對以上問題打破惡性循環,我們通過腳本分析了直播間內 60 + 業務模塊,列出了 1400 + 個引用 ViewModel 的具體使用場景,並且整理了的理想中的數據提供方作爲後續改造的參考:
02 基於視圖劃分的業務數據
直播間中的業務使用 MVVM 的結構構建,我們提供了一套統一的構建模板來構建和管理各層邏輯,單個業務中每層有各自維護的數據和狀態信息,這些數據禁止躍層訪問,並跟隨各層的生命週期創建和銷燬。
** 2.1 數據使用場景**
數據使用分爲三個場景:初始化數據,交互數據,對外提供數據
初始化數據
-
一個業務的初始化一般處於房間加載的某個任務中
-
業務初始化,由當前任務提供該階段可以訪問的數據,作爲初始化數據
-
初始化數據會轉化爲業務專有的數據結構(圖中 Data),供內部邏輯、視圖使用和管理
交互數據
-
一個房間所有業務初始化完成後,如果沒後續的交互,理論上是完全靜止的,任何改變當前直播間的動作都可以看做是一個交互,而每個交互都會帶上一些數據
-
用戶每次對業務 View 的點擊、滑動等都會產生一些事件並帶上相應的數據,這些事件可能直接在 View 層就已經消費掉,也可能會觸發一系列的邏輯交互
-
Socket 和 Http 的響應作爲另一類交互數據的來源,由 Service 層向上通知到各個業務邏輯層,業務邏輯需要監聽這些數據變化做相應處理
-
此外每個業務都可能會關心其他業務的改變,這些往往改變也會帶來一些數據,依據這些數據業務可能需要對自己的邏輯和 View 進行相應的操作
對外提供數據
-
在交互數據裏有提到關心其他業務的變化,這部分的變化應該由每個業務在 API 中決定暴露那些事件和數據供外部使用
-
如果關心某個業務的變化,可以通過 ServiceManager 獲取對應業務的 API,通過關心業務 API 暴露的方法來獲取、訂閱數據
a. provideData 類型方法:對外暴露提供數據的方法,由類型方法提供的數據表示,該業務可以對外提供的數據
b. notifyChange 類型方法:有數據變化需要對外通知時對外暴露的方法,其他業務通過該類型方法可以訂閱相應的變化通知
-
每個業務在提供數據時應該考慮清除需要暴露的數據,不可直接暴露 Data 給外部使用
-
每個業務在接收到其他業務的變化通知時,應該在對應的處理裏消費掉傳過來的數據,不要持有該部分數據
** 2.2 數據的流向**
-
進入直播間時會請求一組初始化接口,響應數據將會由數據分發器管理,分發到各個業務的 Services,不同各個業務拿到各自關心的數據後放到各自的 businessData 中
-
各業務的 Service 中將會管理業務所持有的數據,ViewModel 想要獲取或改變某個數據時,需要持有對應業務的 Service
-
ViewModel 中將各業務的原始數據組合處理後通過 LiveData 通知對應的 View,View 可以通過 ViewModel 對原始數據進行修改
-
View 間的事件(純粹的 UI 變化)將由 ViewEventManager 作爲通道進行傳遞,傳遞過程中的數據爲一次性數據,不可作爲該次事件處理外的邏輯數據使用
03 直播間的視圖結構和業務區域
** 3.1 直播間的結構和區域劃分 **
-
在加入上下滑邏輯之前,房間的概念與整個 Activity 等價,一個房間在 Activity 被銷燬時釋放所有資源
-
在加入上下滑邏輯後,房間的概念變爲滑動組件中的一個 Item,一個房間在 Item 被划走時釋放所有資源,爲了更好的理解業務運行邏輯,我們根據直播間的視圖結構對業務區域進行了劃分
a. 容器區域(Global)包含滑動組件和 DIALOG 業務層(目前僅話題和各種引導用到),在進房時創建該區域
b. 房間區域(Room)包含房間業務層和播放器業務層,這兩部分視圖均掛載在滑動組件的 RoomItem 上,在滑動停止時創建該區域
-
在用戶執行的滑動操作停止後會釋放上一個 RoomItem 的資源,並重新創建房間區域掛載到停止後的 RoomItem 根佈局上,而容器區域中的資源仍然隨 Activity 的銷燬而銷燬,當前的房間區域也會隨容器區域的銷燬而銷燬
-
業務僅需要聲明自己屬於 Global 還是 Room 區域,並在創建、銷燬的回調中編寫邏輯,而不需要關心自己何時被創建和銷燬
** 3.2 按區域劃分加載流程**
-
以構建 item 中房間容器的時機爲分割點,之前的加載流程屬於容器區域,之後的加載流程屬於房間區域
-
房間初始化接口請求(P0、P1 接口)比較特殊,請求時機以及請求的上游處理邏輯屬於容器區域,但是接口響應數據的處理邏輯屬於房間區域
-
在直播間銷燬的流程中,在容器銷燬流程和房間銷燬流程中都需要銷燬的邏輯,歸爲房間區域管理,僅在容器銷燬流程中銷燬的邏輯歸容器區域管理
04 架構演進中的一些思考
-
架構最後是爲業務需求場景服務的,那它也要順應業務的變化而適時調整。來 B 站直播的期間經歷了直播間從不能滑動到可以上下滑,從老直播間爲主到以新版直播間爲主,整個產品交互形態發生了巨大變化,新的架構演進方向往往取決於對新業務形態的認知。
-
隨着業務的不斷髮展和改變以及組織架構的調整,因爲趕工期、圖方便而設計不合理但剛好能用的代碼會越來越多,原先用起來很順暢的架構必定會慢慢腐爛變質,一直修修補補只是在掩飾問題和推遲問題的爆發,作爲一線開發我完全可以理解開發時的內心想法:
-
別人都這麼寫,就算是不合理,跟着也中不會錯
-
時間不夠了,這坨代碼真爛,但我只是來改點小功能,等誰改不動了誰去改
-
現有的架構根本沒考慮到我這種場景,先隨便找個地方放着,能實現需求再說
- 基於以上思考,我認爲架構演進的目的主要有兩個:
-
打破團隊的不滿:打破保守的做法,要積極面對不合理的地方。團隊不定期需要着手開啓重構,將大家平日對代碼的不滿釋放出來。整理直播間老大難的歷史債,將架構的腐化(效率降低、抱怨上升)轉化爲架構優化的動力。
-
團隊意識的培養:培養全員架構的意識,架構演進的過程中會牽扯衆多模塊的重構,在各個模塊重構的過程中傳達架構的思想、形成團隊共識,形成 “人人都是架構師” 的氛圍。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/cLSqU7SnngaIpBZh1Uez3w