作者:leobertlan
前言
當時項目采用MVP分層設計,組員的代碼風格差異也較大,代碼中類職責賦予與封裝風格各成一套,隨著業務急速膨脹,代碼越發混亂。試圖用 MVI架構
+ 單向流
形成 掣肘
帶來一致風格。 但這種做法不夠以人為本,最終采用 “在MVP的基礎上進行了適當改造+設計約定的方式” 解決了問題,并未將MVI投入到商業項目中,于是 放棄了紙上談兵。
在半年前終于有機會在商業項目中進行實踐,同諸位談一談使用后的 個人感悟 ,并藉此講透MVI等架構。
所有內容將按照以下要點展開:
- 從架構的理念出發 – 簡單列明各種
MVX
的理念 , MVX:指代 MVC、MVP、MVVM、MVI - 擁抱復雜的同時實現簡化 – 通過對比理解單向數據流動所解決的痛點、設計Intent的原因等問題
- 單一可信數據源,不可僵化信奉
- 要想優雅,需要工具 – 借助聲明式、響應式編程工具,構建
流
,屏蔽命令式編程中的細節,同樣是聚焦和簡化 - 狀態和事件分家,絕不是吃飽了撐的 – 為什么要裂變出狀態和事件,如何界定
內容會很長,我會酌情再寫一些 解
,結合實例和代碼演示內容。
兩個項目的基本情況
相比于之前的巨型項目,這兩個項目的業務量均不大,一個是基于藍牙和局域網的操控類APP,下午簡稱APP-A,一個是內部使用的工具,分析公司各個產品的日志,簡稱APP-B。
雖然他們的業務深度要比一般的APP要深,但在 本質上一致 ,畢竟同類型業務量再多也僅僅是重復運用一套模式 ,并不影響本質。
和諸多項目的本質一致,均符合如下圖所示的邏輯分層,并在人機交互過程中執行業務邏輯:
- APP-A 是Android項目,圖方便純kotlin
- APP-B 是 Compose-Desktop項目,不得不kotlin
過于絮叨了,我們進入正文。
從架構的理念出發
謹記,實際情況中,MVI、MVVM這些架構均先由Web應用領域提出,用于解決瀏覽器Web應用研發中的問題。
在后續的應用領域發展過程中,存在共性問題,便引入了這些設計,并結合自身特點進行了拓展。
接下來我們聊一聊理念,不比武功。
圖片出自電影一代宗師
MVI的理念
MVI
脫胎于 Model View Intent
- Intent:驅動model發生改變的意圖,以UI中的事件最為常見;
- Model:業務模型,包含數據和邏輯,是對應
客觀實體
的程序建模
; - View:表現層的視圖,以UI方式呈現Model的狀態(以及事件),接受用戶輸入,轉換為UI事件
官方的這幅圖很好的呈現了三者之間的驅動關系:
這張圖非常簡單,它摒棄了驅動方式的細節,只體現了角色與驅動關系。
注意,只要設計中滿足 角色和驅動關系
符合上圖,就是MVI架構設計,并不限制 驅動方式的實現細節
經典的MVI驅動細節要比上圖復雜很多,下文再聊。
從軟件設計的原則出發:職責分離并封裝
的目的是 解耦
、 可獨立變化
、復用
。
顯然,區別于 MVVM
、 MVP
、 MVC
,角色上的差別在于 ViewModel、Presenter、Controller、Intent四者,而它們又是View和Model之間的紐帶。除此之外,V和M亦稍有不同。
MVC、MVP
MVC、MVP 中,C和P的職責體現為 控制、調度
。
MVP中 V
和 M
完全解耦可獨立變化,MVC中 M
直接操作 V
耦合高,在web應用中,C
需要直接操作DOM。
MVVM
MVVM中,提倡 數據驅動
, 數據源
被剝離到 VM
中,在 雙向綁定框架
的加持下,View層的輸入反映為數據的變化,數據的變化驅動視圖內容。
顯然,VM的職責限于維護數據狀態,如有必要,驅動View層消費數據狀態, 不必再關注如何操作視圖。
一般來說,雙向綁定框架已經引入觀察者模式實現,可響應式驅動,VM一般沒有必要關心 響應式驅動和下游觀察者生命周期問題
簡單思考之后會發現MVVM的問題,它的側重點在于 利用雙向綁定讓開發者專注于數據狀態的維護,從操作視圖更新中得以解放
,它難以解決 無天然狀態
問題,例如:按鈕點擊這類事件。
MVI
在MVI中,結合業務背景將UI事件等內容轉換為 Intent
,驅動Model層業務,Model層的業務結果反映為 視圖狀態
+ 事件
。
因此View層和Model層之間已經解耦,并可以吸收MVVM中的優點采用如下設計:
- 將雙向綁定退化為單向綁定,View層消費UI狀態流和事件流,這也意味著UI狀態的職責精簡,它不再承載View層的用戶輸入等事件
- 將UI狀態獨立,Model層僅產生
UI狀態的局部變化
和事件
下圖為經典的MVI原理示意圖:
在上文中,我們已經討論了各個角色的職責,下面逐步展開討論角色具備的特性和細節知識。
在此之前,還請謹記:合適的才是最好的
沒有絕對的最好的設計,只有最合適的設計。
再好的架構,都需要遵循其理念并結合項目因地制宜地進行調整,以獲得最佳使用效果。所以請讀者諸君務必在閱讀時,結合自身項目的情況仔細思考以下問題:
- 引入新框架所解決的痛點、衍生的問題、是否需要進行框架調整?
- 框架中的角色功能,為什么出現,又有怎樣的局限?
單向數據流動
MVI擁抱了結構復雜,但能夠靈活應對業務編碼時的各種情況,按部就班即可。
從MVI原理圖中,可以清晰的看到 “數據” 的流動方向。 起始于 Intent
,經過分類和選擇性消費后產生 Result
,對應的reducer函數計算后,得到最新的 State
(以及裂變出必要的 Event
,圖中未體現) ,驅動視圖。
注意:
單向
是指 單一方向- 此處的
數據
是廣義的、寬泛的。 - 僅描述數據流的 變化方向 ,與數據流的數量無關,但一般 形成有效工作 均需要兩條數據流(上行數據流和下行數據流)
即驅動數據流變化的方向是唯一的,在英文中的術語為:Unidirectional Data Flow
簡稱 UDF
。
MVC、MVP中的痛點
前文我們提到,在MVC和MVP中,著眼于 控制、調度 ,并不強調 數據流
的概念。
View和Model間之間的交互,一般有兩種編碼風格:雙向的API調用、單向的API調用+回調:
注意:以下兩圖并未體現Controller和Presenter細節,僅表意,從View層出發的API調用和回到View層的UI更新
雙向API調用如上圖。
單向API調用+回調更新UI如上圖。
顯而易見,這兩種方式無法繼續抽象,需根據實際業務進行命令式編碼。當UI復雜時,難以寫出清晰、易讀的代碼,維護難度激增。
MVVM解決UI更新代碼混亂問題
前文我們已經提到:MVVM中通過綁定框架,將UI事件轉化為數據變化,驅動業務;業務結果表現為數據變化,驅動UI更新。
顯而易見,維護樸素的數據要比直接維護復雜的UI要簡單。
但問題也同時產生,data1的變化有兩個可能的原因:
- Model層業務結果使其變化,并期望它驅動UI更新
- View層發生事件,反饋數據變化,并期望它驅動Model層邏輯
因此,框架需要考慮標識數據變化來源、或者其他手段消除方向性所帶來的問題。
并且MVVM難以靈活決定的 “何時調用Model層邏輯”,即大多數業務中,都需要結合多個屬性的變化形成組合條件來驅動Model層邏輯。
本篇并不重點討論MVVM,故不再展開MVVM解決循環更新的方案,以及衍生的問題。
盡管如此,MVVM中的數據綁定依舊解決了View層更新繁雜的問題。
用Intent靈活決定何時調用Model
既然數據驅動UI有極大的益處,且View層事件驅動ViewModel的數據變化有很多弊端 (需要建立很高的復雜度) ,那自然需要 趨利避害
僅保留數據驅動UI的部分,并增加Intent用以驅動Model層業務
在于 MVC/MVP
以及 MVVM
對比后不難得出結論:
- MVC/MVP中,View層通過調用C/P層API的方式最終調用到Model層業務,方式質樸、無難度。但業務量規模增大后接口方法數也會增多,導致C/P層尾大不掉,難以重用。
- MVVM中,VM層總是需要利用
技巧
進行模型概念轉換,以滿足業務響應滿足實際需求,需要很深厚的設計經驗才能寫出非常優秀的代碼,這并不友好。
作者按:我個人認為一個友好的設計,不應當劍走偏鋒,而應當大巧不工,能夠以力破法,達成 “使用者只需要吃透理論就可以解決各類問題” 的目標。
而MVI在架構角色中設計了Intent的角色:
- 它包含了業務調用的意圖和數據
- 從設計上可滿足
調用
與實現
的分離 - 架構模型中以Intent流的形式出現,下游對其的
篩選
、轉換
、消費
等行為可遵循FP范式
(即函數式編程范式、Functional Programming Patterns) ,邏輯的復用粒度為方法級,復用度更高更靈活 - 解決了MVVM中的方向性問題、MVC/MVP 中的靈活度問題等
單一可信數據源
我猜測讀者諸君都曾聽過這個詞,將 單一可信數據源
拆解一下:
- 單一
- 可信
- 數據源
在MVI背景下,數據源
指的是視圖對應的數據實體,它代表視圖的內容狀態。
可信指從數據源中獲取的數據是 最新的
、完整的
、可靠的
,否則是不可信的,我們沒有理由在編碼中使用不可信的數據源。
單一是指這樣的數據源僅一個。
在經典設計中,其內涵如下圖:
- 按照視圖的 所有的 內容狀態,定義一個不可變的
ViewState
- 按照業務初始化 ViewState 實例
- Model業務生成驅動 ViewState變化的Result
- 計算出新狀態,Reduce(Pre-ViewState,Result) -> New-ViewState
- 更新數據源
- View層消費ViewState
借助于數據綁定框架,可以很方便地解決視圖更新的問題。
想象一下,此時頁面UI非常復雜……
如果僵化的信奉這樣的 單一
,情況會如何呢?
- 復雜(大量屬性)的ViewState
- 復雜的UI更新計算,e.g. 100個屬性變了2個,依然需要計算98個屬性未變或者全量強制更新
在 APP-A和APP-B中,我分別使用了 DataBinding和Compose,但均無法避免該問題。
何為單一
從機器執行程序的原理上看,我們無法實現 多個內容一致的數據源 在 任意時刻 滿足 最新的
、可靠的
。
將視圖視為一個整體,規定它只擁有 一個 可信的數據源。在此基礎上看局部的視圖,它們也順其自然地僅擁有一個可信的數據源。
反過來看,當任意的局部視圖僅具有一個可信數據源時,整體視圖也僅有一個邏輯上的可信數據源。
據此,我們可以對 經典MVI實現
進行一定程度的改造,將ViewState進行局部分解,使得UI綁定部分的業務邏輯更 清晰、干凈。
請注意,復雜度不會憑空消失,我們為了讓 “UI綁定的業務邏輯更清晰、干凈”、“更新UI的計算量更少”,將復雜度轉移到了ViewState的拆分。拆分后,將具有 多個視圖部件的單一可信數據源,注意,為了不引起額外的麻煩、并且便于維護擴展,建議遵守以下條件:
- 基于業務需求,組合數據源形成新數據源
- 不在數據源的邏輯范圍之外進行數據源組合操作
舉個虛擬的例子:用戶需要實名認證 且 關注博主 ,才在界面上顯示某功能按鈕。下面使用代碼分別演示。
考慮到RxJava的廣泛度依舊高于Kotlin-Coroutine+flow,數據流的實現采用RxJava
注意,考慮到讀者可能會編寫demo做UDF局部的驗證,下文中的代碼以示例目的為主,兼顧編寫場景冒煙的方便性,流的類型不一定是構建完整UDF的最佳選擇。
經典實現
在經典MVI實現中,需要先定義ViewState
data class ViewState(/*unique id of current login user*/val userId: Int,/*true if the current login user has complete real-name verified*/val realNameVerified: Boolean,/*true if the current login user has followed the author*/val hasFollowAuthor: Boolean
) {
}
并定義ViewModel,創建ViewState流,忽略掉其初始化和其他部分
class VM {val viewState = BehaviorSubject.create<ViewState>()//ignore
}
并定義View層,忽略掉其他部分,簡單起見暫時不使用數據綁定框架
class View {private val vm = VM()lateinit var imgRealNameVerified: ImageViewlateinit var cbHasFollowAuthor: CheckBoxlateinit var someButton: Buttonfun onCreate() {//ignore view initializevm.viewState.subscribe {render(it)}}private fun render(state: ViewState) {imgRealNameVerified.isVisible = state.realNameVerifiedcbHasFollowAuthor.isChecked = state.hasFollowAuthorsomeButton.isVisible = state.realNameVerified && state.hasFollowAuthor//ignore other}
}
在JS中,JSON并不能附加邏輯,基本等價于Java中的POJO,故在數據源外部處理簡單邏輯的情況較為常見。而在Java、Kotlin中可以進行適當的優化,適當封裝,使得代碼更加干凈便于維護:
data class ViewState(//ignore
) {fun isSomeFuncEnabled():Boolean = realNameVerified && hasFollowAuthor
}class View {//ignoreprivate fun render(state: ViewState) {//...someButton.isVisible = state.isSomeFuncEnabled()}
}
拆分實現
依舊先定義邏輯上完整的ViewState:
class ComposedViewState(/*unique id of current login user*/val userId: Int,
) {/*** real-name-verified observable subject,feed true if the current login user has complete real-name verified* */val realNameVerified = BehaviorSubject.create<Boolean>()/*** follow-author observable subject, feed true if the current login user has followed the author* */val hasFollowAuthor = BehaviorSubject.create<Boolean>()val someFuncEnabled = BehaviorSubject.combineLatest(realNameVerified, hasFollowAuthor) { a, b -> a && b }
}
定義ViewModel,子模塊數據流均已定義,故而無需再定義全ViewState的流
class VM(val userId: Int) {val viewState = ComposedViewState(userId)//ignore
}
編寫View層的UI綁定,同樣簡單起見,不使用數據綁定框架
class View {private val vm = VM(1)lateinit var imgRealNameVerified: ImageViewlateinit var cbHasFollowAuthor: CheckBoxlateinit var someButton: Buttonfun onCreate() {//ignore view initializebindViewStateWithUI()}private fun bindViewStateWithUI() {vm.viewState.realNameVerified.subscribe {renderSection1(it)}vm.viewState.hasFollowAuthor.subscribe {renderSection2(it)}vm.viewState.someFuncEnabled.subscribe {renderSection3(it)}//...}private fun renderSection1(foo:Boolean) {imgRealNameVerified.isVisible = foo}private fun renderSection2(foo:Boolean) {cbHasFollowAuthor.isChecked = foo}private fun renderSection3(foo:Boolean) {someButton.isVisible = foo}
}
例子較為簡單,在實際項目中,如果遇到復雜頁面,則可以分塊進行處理。
注意:實際情況中,并沒有必要將每一個子數據源拆分到一個View級別的控件,那樣過于啰嗦,例子因非常簡單而無法豐滿起來。 e.g. 針對每一塊視圖區,例如作者區域,定義子ViewState類,創建其數據流即可。
作者按:務必評估,在一次Model業務產生的Result中,會引起數據流下游的更新次數。 為避免產生不可預期的問題,可通過類似以下方式,使下游響應次數表現和經典實現的情況一致。
額外定義PartialChange流或者功能等價的流,它用于標識 reduce
計算的開始和結束,可以將此期間的數據流的變化延遲到最后發送終態
更加推薦定義功能上等價的流
class ComposedViewState(/*unique id of current login user*/val userId: Int,
) {internal val changes = BehaviorSubject.create<PartialChange>()//ignoreval someFuncEnabled =BehaviorSubject.combineLatest(realNameVerified, hasFollowAuthor) { a, b -> a && b }.sync(PartialChange.Tag, changes)
}inline fun <reified T, S> Observable<T>.sync(tag: S, sync: BehaviorSubject<S>): Observable<T> {return BehaviorSubject.combineLatest(this, sync) { source, syncItem ->if (syncItem == tag) {syncItem} else {source}}.filter { it is T }.cast(T::class.java)
}
修改PartialChange,為reduce函數添加邊界:
PartialChange是Model產生的Result的表現物,封裝了ViewState的reduce函數邏輯,即如何從 Pre-ViewState 生成 新 ViewState
sealed class PartialChange {open fun reduce(state: ComposedViewState) {}/*** 同步標記,從頭開始到真實PartialChange之間,流的狀態生效* */object Tag : PartialChange()object None : PartialChange()class Foo(val a: Boolean, val b: Boolean) : PartialChange() {override fun reduce(state: ComposedViewState) {state.changes.onNext(Tag)state.realNameVerified.onNext(a)state.hasFollowAuthor.onNext(b)state.changes.onNext(this)}}
}
要想優雅,需要工具
采用響應式流,避免命令式編碼
想來這一點已不需要多做解釋。
在Android中,存在 LiveData
組件,它通過簡單的方式封裝了可觀測的數據,但實現方式簡單也限制了它的功能 不夠強大 。因此,建議使用 RxJava
或者 Kotlin-Coroutine & flow
構建數據流。
本節便不再展開。
采用數據綁定框架
采用 jetpack-compose
或者 DataBinding
均可以移除枯燥的UI命令式邏輯,在APP-A中我使用了DataBinding,在APP-B中我使用了Compose。
在 ViewState的代碼很棒時,均可以獲得優秀的編程體驗,從啰嗦的UI中解放出來。
作者的個人觀點:
關于Compose。Compose依舊屬于較新的事物,在商業項目中使用存在學習門檻和造輪工作。在目標用戶具有較高容忍度的情況下,已然可以進行嘗試。
關于DataBinding。一個近乎毀譽參半的工具,關于它的批判,大多集中于:xml中實現的邏輯難以閱讀、維護,這實際上是對DataBinding設計的誤解而帶來的錯誤使用。
DataBinding本身具有生成VM層的功能,但這一功能并不足夠強大,且沒有完善的使用指導,而在官方Demo中過度宣傳了它,導致大家認為DataBinding就該這樣使用。
僅使用基礎的數據綁定功能、和Resource或者Context有關的功能(例如字符串模板)、組件生命周期綁定等,適度自定義綁定。
何為狀態、何為事件。最后的一公里
首先區別于上文提到的UI事件,這里的狀態和事件均產生于數據流的末段,而UI事件處于數據流的首段。
UI事件屬于:A possible action that the user can perform that is monitored by an application or the operating system (event listener). When an event occurs an event handler is called which performs a specific task
在展開之前,先用一張圖回顧總結上文中對于 單向數據流
& 單一可信數據源
的知識
在 單向數據流動 章節中,提到了MVI的UDF設計:
- 系統捕獲的UI事件、其他偵聽事件(例如熄屏、應用生命周期事件),生成Intent,壓入Intent流中
- ViewModel層中篩選、轉換、處理Intent,實際是使用Model層業務,產生業務結果,即PartialChange
- PartialChange經過Reducer計算處理得到最新的ViewState,壓入ViewState流
- View層(廣義的表現層)響應并呈現最新的ViewState
在 單一可信數據源 章節中,提到View層應當采用 單一可信數據源
在這張圖中,我們僅體現了 狀態
即 ViewState。
關于GUI程序的認知
在展開前,先聊點理念上的內容。請讀者諸君思考下自己對于GUI程序的認知。
作者的理解:
程序狹義上是計算機能識別和執行的一組指令集,編程工作是在程序世界對
客觀實體
、業務邏輯
進行 建模和邏輯表達。而GUI程序擁有
用戶圖形界面
, 除了結合硬件接收用戶交互輸入外,可以將程序世界中的模型
以用戶圖形界面
等方式表現給用戶。表現出來的內容代表著客觀實體
其本質目的在于:通過 描述特征屬性 、 描述變化過程 等方式讓用戶感知并理解
客觀實體
而除了通過 程序語言描述 、 程序世界模擬展現 外,同樣可以通過 自然語言描述 達到目的,這也是產品經理的工作。
當然,產品經理往往需要借助一些工具來提升自己的自然語言表達能力,但無奈的是能用數學公式和邏輯推演表達需求的產品經理太少見了。
寫這段只是為了引入 他山之石
。
First-Order logic
在數學、哲學、語言學、計算機科學中,有一個概念 First-Order logic
,無論是產品需求還是計算機程序,都可以建立FOL表達。
當然,本篇不討論FOL,那是一個很龐大且偏離主題的事情。我僅僅是想借用其中的概念。
FOL表達 Event或者State時:
- Event 體現的是特定的變化
- State 體現的是客觀實體在任意時刻都適用的一組情況,即一段時間內無變化的條件或者特征
不難理解,變化是瞬時的,連續的變化是可分的。
但在人機交互中,瞬時意義很小,我們的目的在于讓用戶感知。
例如:“好友向你發送了一條消息的場景中”,消息抵達就是Event,它背后潛藏著 “消息數的變化”、“最新消息內容的變化” 等。 在常見的設計中:
- 應用需要彈出一個氣泡通知用戶這一事件
- 應用需要更新消息數,消息列表內容等,以呈現出最新的State
而為了讓用戶感知到,氣泡呈現時長并不是瞬時的,但在產品交互設計中依舊將其定義為事件。
分離狀態和事件,不是吃飽撐得
看山是山、看水是水
此時此刻,答案已經很明顯。
在通用的產品設計中,狀態和事件有不同的意義,如果程序中不分離出兩者,則必然是自找麻煩,這是公然挑釁 面向對象編程
的行為。如果不明確定義不同的Class,則勢必導致代碼混亂不堪,畢竟這是違背編程原則的事情。
在大多MVVM設計中,狀態和事件未分家,導致bug叢生,這一點便不再展開。
如何區分Event和State
State是一段時間內無變化的條件或者特征,它天然的 契合 了位于表現層的主體內容所對應的 數據模型特征。
Event是特定的變化,它在表現層體現,但與State的生命周期不一致,且并無一一對應的關系。
基于經驗主義,我們可以機械地、籠統地認為:頁面主體靜態內容所需要的數據屬于State范疇,氣泡提醒等短暫的物體所需要的數據屬于Event范疇。
從邏輯推演的角度出發,進行 等價邏輯推斷 和 條件限定下的邏輯推斷 ,一定序列的Event可以模型轉換為State。
事件粘性導致重復?只是框架設計的bug
看山不是山,看水不是水
前面提到,State是一段時間內無變化的條件或者特征,所以在程序設計中State具有粘性的特征。
如果Event也設計出這樣的粘性特征并造成重復消費,明顯是違背需求的,無疑是框架設計的Bug。此問題在各大論壇中很常見。
注意,我們無法脫離實際需求去二元化的討論事件本身該不該有粘性特征,只能結合實際討論框架功能是否存在bug
如果要實現以力破法,在框架設計層面上 Event體系的設計要比State體系要復雜 。因為從交互設計上:
- State 只需要考慮呈現的準確性和及時性,除去美觀、可理解性等等
- Event 需要考慮準確性、優先級、及時性、按條件丟棄等等,除去美觀、可理解性等等
舉個例子:網絡連接問題導致的Web-API調用失敗需要使用Toast提示網絡連接失敗
不難想象:
- 可能一瞬間的斷開網絡連接,會導致多個連接均返回失敗
- 可能連接問題未修復,10秒前請求失敗,當前請求又失敗了
難道連續彈出嗎?難道和上一次Event一致就不消費嗎?…
或許您會使用一些 劍走偏鋒的技巧
來解決問題,但技巧總是建立在特定條件下生效的,一旦條件發生變化,就會帶來煩惱,您很難控制上游的PM和交互設計師。
所以在框架層面需要針對產品、交互設計的泛化理念,設計準確的、靈活的Event體系。
準確的、靈活的Event體系
看山還是山,看水還是水
回到FOL中,為了更加準確的表達Event和State的含義,還需要一些額外的參數,例如:參與者
、地點
、時間
等。
想通這一點會發現,產品中定義的Event事件、及其消費邏輯均含有隱藏屬性,例如:
- 發生時間
- 客觀有效期
- 判斷有效的條件(如呈現的條件)
- 判斷失效的條件 ,用于實現提前失效
產品經理和交互設計師一般會使用 “響應時間”、“優先級” 等詞描述它們,但一般不嚴謹、不成體系,帶來期望不一致的問題
反觀State流,它代表了界面主體內容在時間軸上的完整變化,任意一個時間點均可以得出界面內容所對應的條件和特征。一旦State流中出現一個新的狀態,它均被及時的、準確的在表現層予以體現。
不難理解,一個State的生命周期為 從init或者reducer計算生成開始
至 reducer計算出新State、宿主生命期結束為止
,在State流中已然暗含:
- State之間無生命周期重疊
- 所有State的生命周期相加可填滿時間軸
前文提到Event是瞬時的,所以Event本身并沒有實質意義上的生命周期,為了方便表述,我們將 “Event從生成到在表現層不可觀測的階段” 定義為Event生命周期
而Event流 不同于 State流 ,因為Event的生命周期情況更加復雜:
- Event可能存在生命周期重疊
- 所有Event的生命周期相加可能無法覆蓋完整的時間軸
需要額外設計實現 。實現這一點后,從Event流中分流(以及裂變+組合)出的 子流 將和State流 性質一致。
此刻,您會發現,根據不同類型的事件交互控件所對應的交互特征,又將Event流結合條件流衍生出各個State流。完整的數據流細節如下:
Android 學習筆錄
Android 性能優化篇:https://qr18.cn/FVlo89
Android 車載篇:https://qr18.cn/F05ZCM
Android 逆向安全學習筆記:https://qr18.cn/CQ5TcL
Android Framework底層原理篇:https://qr18.cn/AQpN4J
Android 音視頻篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(內含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源碼解析筆記:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知識體:https://qr18.cn/CyxarU
Android 核心筆記:https://qr21.cn/CaZQLo
Android 往年面試題錦:https://qr18.cn/CKV8OZ
2023年最新Android 面試題集:https://qr18.cn/CgxrRy
Android 車載開發崗位面試習題:https://qr18.cn/FTlyCJ
音視頻面試題錦:https://qr18.cn/AcV6Ap