響應(yīng)式在前端領(lǐng)域已經(jīng)變得十分流行,很多主流框架都采用響應(yīng)式來(lái)進(jìn)行頁(yè)面的展示刷新。本文主要是探索一下響應(yīng)式在移動(dòng)端Android上的一些實(shí)踐,包括對(duì)響應(yīng)式思想的理解,以及目前Android上實(shí)現(xiàn)響應(yīng)式的一些手段,最后聊聊響應(yīng)式在Android開(kāi)發(fā)上的一些應(yīng)用。如果你也在傳統(tǒng)的開(kāi)發(fā)模式過(guò)程中隨著項(xiàng)目代碼的增加以及業(yè)務(wù)邏輯復(fù)雜度的增加發(fā)現(xiàn)諸如大量的繁瑣回調(diào)、不可控的內(nèi)存泄漏、空指針等問(wèn)題,希望本文的一些分享可以給你帶來(lái)一點(diǎn)點(diǎn)新的選擇。
本文主要包括以下幾部分:
一. 響應(yīng)式編程思想
二. 響應(yīng)式的實(shí)現(xiàn)手段:訂閱發(fā)布模式、LiveData、RxJava
三. 響應(yīng)式的應(yīng)用:MVVM、事件總線
四. 總結(jié)
一. 響應(yīng)式編程思想
概念:響應(yīng)式編程是一種通過(guò)異步和數(shù)據(jù)流來(lái)構(gòu)建事物關(guān)系的編程模型。
相信大家看到這句話和我差別不大,一定還是不大明白這句話的含義,那么如何更加通俗易懂的理解,是一個(gè)問(wèn)題,那么我們一步步來(lái)理解。
1. 響應(yīng)式的由來(lái)
我們先來(lái)聊一聊響應(yīng)式的由來(lái),對(duì)于它的由來(lái),我們可能需要先從一段常見(jiàn)的代碼片段看起:
int a=1;
int b=a+1;
System.out.print(“b=”+b) // b=2
a=10;
System.out.print(“b=”+b) // b=11
上面是一段很常見(jiàn)的代碼,簡(jiǎn)單的賦值打印語(yǔ)句,但是這種代碼有一個(gè)缺陷,那就是如果我們想表達(dá)的并不是一個(gè)賦值動(dòng)作,而是b和a之間的關(guān)系,即無(wú)論a如何變化,b永遠(yuǎn)比a大1。那么可以想見(jiàn),我們就需要花額外的精力去構(gòu)建和維護(hù)一個(gè)b和a的關(guān)系。
而響應(yīng)式編程的想法正是企圖用某種操作符幫助你構(gòu)建這種關(guān)系。它的思想完全可以用下面的代碼片段來(lái)表達(dá):
int a=1;
int b <= a+1; // <= 符號(hào)只是表示a和b之間關(guān)系的操作符
System.out.print(“b=”+b) // b=2
a=10;
System.out.print(“b=”+b) // b=11
這就是是響應(yīng)式的思想,它希望有某種方式能夠構(gòu)建關(guān)系,而不是執(zhí)行某種賦值命令。
至此你可能不禁要問(wèn),我們?yōu)槭裁葱枰獦?gòu)建關(guān)系的代碼而不是命令式的代碼呢?如果你翻一翻自己正在開(kāi)發(fā)的APP,你就能看到的每一個(gè)交互的頁(yè)面其實(shí)內(nèi)部都包含了一系列的業(yè)務(wù)邏輯。而產(chǎn)品的每個(gè)需求,其實(shí)也對(duì)應(yīng)了一系列的業(yè)務(wù)邏輯相互作用??傊?,我們的開(kāi)發(fā)就是在構(gòu)建一系列的業(yè)務(wù)邏輯之間的關(guān)系。你說(shuō)我們是不是需要構(gòu)建關(guān)系的代碼?
說(shuō)回響應(yīng)式,前期由于真實(shí)的編程環(huán)境中并沒(méi)有構(gòu)建關(guān)系的操作符,主流的編程語(yǔ)言并不支持這種構(gòu)建關(guān)系的方式,所以一開(kāi)始響應(yīng)式主要停留在想的層面,直到出現(xiàn)了Rx和一些其他支持這種思想的框架,才真正把響應(yīng)式編程引入到了實(shí)際的代碼開(kāi)發(fā)中。
Rx是響應(yīng)式拓展,即支持響應(yīng)式編程的一種拓展,為響應(yīng)式在不同語(yǔ)言中的實(shí)現(xiàn)提供指導(dǎo)思想。
2. 什么是響應(yīng)式編程
說(shuō)完了了響應(yīng)式的由來(lái),我們就可以談?wù)勈裁词琼憫?yīng)式編程了。
響應(yīng)式編程是一種通過(guò)異步和數(shù)據(jù)流來(lái)構(gòu)建事務(wù)關(guān)系的編程模型。這里每個(gè)詞都很重要,“事務(wù)的關(guān)系”是響應(yīng)式編程的核心理念,“數(shù)據(jù)流”和“異步”是實(shí)現(xiàn)這個(gè)核心理念的關(guān)鍵。為了幫助大家理解這個(gè)概念,我們不妨以APP初始化業(yè)務(wù)為例來(lái)拆解一下這幾個(gè)詞。

3. 事務(wù)的關(guān)系
-
事務(wù) 是一個(gè)十分寬泛的概念,它可以是一個(gè)變量,一個(gè)對(duì)象,一段代碼,一段業(yè)務(wù)邏輯.....但實(shí)際上我們往往把事務(wù)理解成一段業(yè)務(wù)邏輯(下文你均可以將事務(wù)替換為業(yè)務(wù)邏輯來(lái)理解),比如上圖中,事務(wù)就是指
APP初始化中的四個(gè)業(yè)務(wù)邏輯。 -
事務(wù)的關(guān)系 這種關(guān)系不是類的依賴關(guān)系,而是業(yè)務(wù)之間實(shí)際的關(guān)系。比如
APP初始化中,SDK初始化,數(shù)據(jù)庫(kù)初始化,登陸接口,他們共同被跳轉(zhuǎn)頁(yè)面業(yè)務(wù)所依賴。但是他們?nèi)齻€(gè)本身并沒(méi)有關(guān)聯(lián)。這也只是業(yè)務(wù)之間較為簡(jiǎn)單的關(guān)系,實(shí)際上,根據(jù)我們的需求App端會(huì)產(chǎn)生出許多業(yè)務(wù)之間錯(cuò)綜復(fù)雜的關(guān)系。
4. 數(shù)據(jù)流
數(shù)據(jù)流只是事務(wù)之間溝通的橋梁。
比如在APP初始化中,SDK初始化,數(shù)據(jù)庫(kù)初始化,登陸接口這些業(yè)務(wù)完成之后才會(huì)去安排頁(yè)面跳轉(zhuǎn)的操作,那么這些上游的業(yè)務(wù)在自己工作完成之后,就需要通知下游,通知下游的方式有很多種,其中最棒的的方式就是通過(guò)數(shù)據(jù)(事件)流。每一個(gè)業(yè)務(wù)完成后,都會(huì)有一條數(shù)據(jù)(一個(gè)事件)流向下游,下游的業(yè)務(wù)收到這條數(shù)據(jù)(這個(gè)事件),才會(huì)開(kāi)始自己的工作。
但是,只有數(shù)據(jù)流是不能完全正確的構(gòu)建出事務(wù)之間的關(guān)系的。我們依然需要異步編程。
5. 異步
異步編程本身是有很多優(yōu)點(diǎn)的,比如挖掘多核心CPU的能力,提高效率,降低延遲和阻塞等等。但實(shí)際上,異步編程也給我們構(gòu)建事務(wù)的關(guān)系提供了幫助。
在APP初始化中,我們能發(fā)現(xiàn)SDK初始化,數(shù)據(jù)庫(kù)初始化,登陸接口這三個(gè)業(yè)務(wù)本身相互獨(dú)立,應(yīng)當(dāng)在不同的線程環(huán)境中執(zhí)行,以保證他們不會(huì)相互阻塞。而假如沒(méi)有異步編程,我們可能只能在一個(gè)線程中順序調(diào)用這三個(gè)相對(duì)耗時(shí)較多的業(yè)務(wù),最終再去做頁(yè)面跳轉(zhuǎn),這樣做不僅沒(méi)有忠實(shí)反映業(yè)務(wù)本來(lái)的關(guān)系,而且會(huì)讓你的程序“反應(yīng)”更慢
總的來(lái)說(shuō),異步和數(shù)據(jù)流都是為了正確的構(gòu)建事務(wù)的關(guān)系而存在的。只不過(guò),異步是為了區(qū)分出無(wú)關(guān)的事務(wù),而數(shù)據(jù)流(事件流)是為了聯(lián)系起有關(guān)的事務(wù)。
6. APP初始化應(yīng)該怎么寫
許多使用Rx編程的同學(xué)可能會(huì)使用這種方式來(lái)完成APP的初始化。
Observable.just(context)
.map((context)->{login(getUserId(context))})
.map((context)->{initSDK(context)})
.map((context)->{initDatabase(context)})
.subscribeOn(Schedulers.newThread())
.subscribe((context)->{startActivity()})
其實(shí),這種寫法并不是響應(yīng)式的,本質(zhì)上還是創(chuàng)建一個(gè)子線程,然后順序調(diào)用代碼最后跳轉(zhuǎn)頁(yè)面。這種代碼依然沒(méi)有忠實(shí)反映業(yè)務(wù)之間的關(guān)系。
在我心目中,響應(yīng)式的代碼應(yīng)該是這樣的:
Observable obserInitSDK=Observable.create((context)->{initSDK(context)}).subscribeOn(Schedulers.newThread())
Observable obserInitDB=Observable.create((context)->{initDatabase(context)}).subscribeOn(Schedulers.newThread())
Observable obserLogin=Observable.create((context)->{login(getUserId(context))})
.map((isLogin)->{returnContext()})
.subscribeOn(Schedulers.newThread())
Observable observable = Observable.merge(obserInitSDK,obserInitDB,obserLogin)
observable.subscribe(()->{startActivity()})
大家應(yīng)該能很明顯看到兩段代碼的區(qū)別,第二段代碼完全遵照了業(yè)務(wù)之間客觀存在的關(guān)系,可以說(shuō)代碼和業(yè)務(wù)關(guān)系是完全對(duì)應(yīng)的。說(shuō)白了還是低耦合, 分化了實(shí)現(xiàn)的步驟, 功能更獨(dú)立, 自己的事情自己做, 最后在通過(guò)拼接(1??將事務(wù)作為對(duì)象 2??將事務(wù)和結(jié)果進(jìn)行綁定),完成需求。
如果一定要追求鏈?zhǔn)剑梢赃@樣寫:
Observable.just(context)
.observableOn(Schedulers.newThread())
.map((context)->{login(getUserId(context))})
.observableOn(Schedulers.newThread())
.map((context)->{initSDK(context)})
.observableOn(Schedulers.newThread())
.map((context)->{initDatabase(context)})
.observableOn(AndroidSchedulers.mainThread())
.subscribe((context)->{startActivity()})
那么這帶來(lái)了什么好處呢?當(dāng)initSDK,initDB,login都是耗時(shí)較長(zhǎng)的操作時(shí),遵照業(yè)務(wù)關(guān)系編寫響應(yīng)式代碼可以極大的提高程序的執(zhí)行效率,降低阻塞。
理論上講,遵照業(yè)務(wù)關(guān)系運(yùn)行的代碼在執(zhí)行效率上是最優(yōu)的。
7. 為什么引入響應(yīng)式編程
對(duì)響應(yīng)式編程有了一些了解之后,我知道馬上會(huì)由很多人跳出來(lái)說(shuō),不使用這些響應(yīng)式編程我們還不是一樣開(kāi)發(fā)APP?
在這里我希望你理解一點(diǎn),當(dāng)我們用老辦法開(kāi)發(fā)APP的時(shí)候,其實(shí)做了很多妥協(xié),比如上面的APP初始化業(yè)務(wù),三個(gè)無(wú)關(guān)耗時(shí)操作為了方便,我們往往就放在一個(gè)線程環(huán)境中去執(zhí)行,從而犧牲了程序運(yùn)行的效率。而且實(shí)際開(kāi)發(fā)中,這種類似的業(yè)務(wù)邏輯還有很多,甚至更加復(fù)雜。假如不引入響應(yīng)式的思路,不使用Rx的編程模型,我們面對(duì)這么些復(fù)雜的業(yè)務(wù)關(guān)系真的會(huì)很糟心。假如你做一些妥協(xié),那就會(huì)犧牲程序的效率,假如你千辛萬(wàn)苦構(gòu)建出業(yè)務(wù)關(guān)系,最終寫出來(lái)的代碼也一定很復(fù)雜難以維護(hù)。所以,響應(yīng)式編程其實(shí)是一種更友好更高效的開(kāi)發(fā)方式。
根據(jù)個(gè)人經(jīng)驗(yàn)來(lái)看,響應(yīng)式編程至少有如下好處:
- 在業(yè)務(wù)層面實(shí)現(xiàn)代碼邏輯分離,方便后期維護(hù)和拓展
- 極大提高程序響應(yīng)速度,充分發(fā)掘CPU的能力
- 幫助開(kāi)發(fā)者提高代碼的抽象能力和充分理解業(yè)務(wù)邏輯
- Rx豐富的操作符會(huì)幫助我們極大的簡(jiǎn)化代碼邏輯
注:
由于這篇文章講的是響應(yīng)式編程,因此更多的使用的 `Rx` 這個(gè)名稱,而不是`RxJava`,
因?yàn)閌RxJava`只是響應(yīng)式編程在`Java`語(yǔ)言中的實(shí)現(xiàn)。
不過(guò)里面的偽代碼都是使用`RxJava`來(lái)編寫的,希望大家能夠理解。
二. 實(shí)現(xiàn)響應(yīng)式的手段
上面說(shuō)的響應(yīng)式主要還是一種編程思想,而如何來(lái)實(shí)現(xiàn)這樣一種思想呢?當(dāng)然訂閱發(fā)布模式是基礎(chǔ),而像RxJava、LiveData等的出現(xiàn),讓響應(yīng)式編程的實(shí)現(xiàn)手段變得更加的豐富。
1. 訂閱發(fā)布模式
訂閱發(fā)布模式是實(shí)現(xiàn)響應(yīng)式的基礎(chǔ),這種模式我們都很熟悉了,主要是通過(guò)把觀察者的回調(diào)注冊(cè)進(jìn)被觀察者來(lái)實(shí)現(xiàn)二者的訂閱關(guān)系,當(dāng)被觀察者notify的時(shí)候,則所有的觀察就會(huì)自動(dòng)響應(yīng)。這種模式也實(shí)現(xiàn)了觀察者和被觀察者的解耦。
簡(jiǎn)單理解:A 是被觀察者,B、C、D 是觀察者。
當(dāng) A 發(fā)生變化時(shí)發(fā)出通知告知 B、C、D,
然后 B、C、D 可根據(jù) A 發(fā)送的數(shù)據(jù)來(lái)做具體的事情。
重要作用:
解耦,也就是將觀察者和被觀察者解耦。
解耦的好處不用多說(shuō),設(shè)計(jì)模式中 單一職責(zé)原則
也表明我們希望每個(gè)實(shí)體(類、函數(shù)、模塊等)能引起其改變的原因只有它自己,
也就是說(shuō),我們希望觀察者的邏輯和被觀察者的邏輯是分離的。
2. LiveData
LiveData是google發(fā)布的lifecycle-aware components中的一個(gè)組件,除了能實(shí)現(xiàn)數(shù)據(jù)和View的綁定響應(yīng)之外,它最大的特點(diǎn)就是具備生命周期感知功能,這使得他具備以下幾個(gè)優(yōu)點(diǎn):
-
解決內(nèi)存泄漏問(wèn)題。由于
LiveData會(huì)在Activity/Fragment等具有生命周期的lifecycleOwner onDestory的時(shí)候自動(dòng)解綁,所以解決了可能存在的內(nèi)存泄漏問(wèn)題。之前我們?yōu)榱吮苊膺@個(gè)問(wèn)題,一般有注冊(cè)綁定的地方都要解綁,而LiveData利用生命周期感知功能解決了這一問(wèn)題。 -
解決常見(jiàn)的 View 空異常。我們通常在一個(gè)異步任務(wù)回來(lái)后需要更新
View,而此時(shí)頁(yè)面可能已經(jīng)被回收,導(dǎo)致經(jīng)常會(huì)出現(xiàn)View空異常,而LiveData由于具備生命周期感知功能,在界面可見(jiàn)的時(shí)候才會(huì)進(jìn)行響應(yīng),如界面更新等,如果在界面不可見(jiàn)的時(shí)候發(fā)起notify,會(huì)等到界面可見(jiàn)的時(shí)候才進(jìn)行響應(yīng)更新。所以就很好的解決了空異常的問(wèn)題。 -
確保UI界面的數(shù)據(jù)狀態(tài)。
LiveData遵循觀察者模式。LiveData在生命周期狀態(tài)更改時(shí)通知Observer對(duì)象,更新這些Observer對(duì)象中的UI。觀察者可以在每次應(yīng)用程序數(shù)據(jù)更改時(shí)更新UI,而不是每次發(fā)生更改時(shí)更新UI。 -
配置的改變。當(dāng)前Activity配置改變(如屏幕方向),導(dǎo)致重新從
onCreate走一遍,這時(shí)觀察者們會(huì)立刻收到配置變化前的最新數(shù)據(jù)。 -
共享資源。只需要一個(gè)
LocationLivaData,連接系統(tǒng)服務(wù)一次,就能支持所有的觀察者。
LiveData的實(shí)現(xiàn)上可以說(shuō)是 訂閱發(fā)布模式+生命周期感知,對(duì)于Activity/Fragment等LifecycleOwner來(lái)說(shuō)LiveData是觀察者,監(jiān)聽(tīng)者生命周期,而同時(shí)LiveData又是被觀察者,我們通過(guò)觀察LiveData,實(shí)現(xiàn)數(shù)據(jù)和View的關(guān)系構(gòu)建。

LiveData是粘性的,這是你在使用前需要知道的,以免因?yàn)檎承栽斐梢恍﹩?wèn)題,使用EventBus的時(shí)候我們知道有一種事件模式是粘性的,特點(diǎn)就是消息可以在observer注冊(cè)之前發(fā)送,當(dāng)observer注冊(cè)時(shí),依然可接收到之前發(fā)送的這個(gè)消息。而LiveData天生就是粘性的,下面會(huì)講解為什么他是粘性的,以及如果在一些業(yè)務(wù)場(chǎng)景上不想要LiveData是粘性的該怎么做。
LiveData的實(shí)現(xiàn)原理
單純的貼源碼,分析源碼可能比較枯燥,所以下面就盡量以拋出問(wèn)題,
然后解答的方式來(lái)解析LiveData的原理。
1). LiveData是如何做到感知Activity/Fragment的生命周期?
lifecycle-aware compents的核心就是生命周期感知,要明白LiveData為什么能感知生命周期,就要知道Google的這套生命周期感知背后的原理是什么,下面基于之前lifeycycle這套東西對(duì)源碼進(jìn)行分析總結(jié):
首先Activity/Fragment是LifecycleOwner(26.1.0以上的support包中Activity已經(jīng)默認(rèn)實(shí)現(xiàn)了LifecycleOwner接口),內(nèi)部都會(huì)有一個(gè)LifecycleRegistry存放生命周期State、Event等。而真正核心的操作是,每個(gè)Activity/Fragment在啟動(dòng)時(shí)都會(huì)自動(dòng)添加進(jìn)來(lái)一個(gè)Headless Fragment(無(wú)界面的Fragment),由于添加進(jìn)來(lái)的Fragment與Activity的生命周期是同步的,所以當(dāng)Activity執(zhí)行相應(yīng)生命周期方法的時(shí)候,同步的也會(huì)執(zhí)行Headless Fragment的生命周期方法,由于這個(gè)這個(gè)Headless Fragment對(duì)我們開(kāi)發(fā)者來(lái)說(shuō)是隱藏的,它會(huì)在執(zhí)行自己生命周期方法的時(shí)候更新Activity的LifecycleRegistry里的生命周期State、Event,并且notifyStateChanged來(lái)通知監(jiān)聽(tīng)Activity生命周期的觀察者。這樣就到達(dá)了生命周期感知的功能,所以其實(shí)是一個(gè)隱藏的Headless Fragment來(lái)實(shí)現(xiàn)了監(jiān)聽(tīng)者能感知到Activity的生命周期。
基于這套原理,只要LiveData注冊(cè)了對(duì)Activity/Fragment的生命周期監(jiān)聽(tīng),也就擁有了感知生命周期的能力。從LiveData的源碼里體現(xiàn)如下:
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
下面附一張當(dāng)時(shí)對(duì)Google lifecycle-aware原理進(jìn)行源碼分析隨手畫的圖:

所以到這里我們基本上已經(jīng)知道了生命周期感知這套東西的原理,接下來(lái)我們就可以來(lái)看看
LiveData的實(shí)現(xiàn)原理了,下我把LiveData的源碼抽象為一張流程圖來(lái)展示,下面的其他問(wèn)題都可以在這張圖中找到答案:
可以看到,在
LiveData所依附的Activity/Fragment生命周期發(fā)生改變或者通過(guò)setValue()改變LiveData數(shù)據(jù)的時(shí)候都會(huì)觸發(fā)notify,但是觸發(fā)后,真正要走到最終的響應(yīng)(即我們注冊(cè)進(jìn)去的onChanged()回調(diào))則中間要經(jīng)歷很多判斷條件,這也是為什么LiveData能具有自己那些特點(diǎn)的原因。
2). LiveData為什么可以避免內(nèi)存泄漏?
通過(guò)上面,我們可以知道,當(dāng)Activity/Fragment的生命周期發(fā)生改變時(shí),LiveData中的監(jiān)聽(tīng)都會(huì)被回調(diào),所以避免內(nèi)存泄漏就變得十分簡(jiǎn)單,可以看上圖,當(dāng)LiveData監(jiān)聽(tīng)到Activity onDestory時(shí)則removeObserve,使自己與觀察者自動(dòng)解綁,這樣就避免了內(nèi)存泄漏。
源碼上體現(xiàn)如下:
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
3). LiveData為什么可以解決View空異常問(wèn)題?
這個(gè)問(wèn)題很簡(jiǎn)單,看上圖,因?yàn)?code>LiveData響應(yīng)(比如更新界面操作View)只會(huì)在界面可見(jiàn)的時(shí)候,如果當(dāng)前見(jiàn)面不可見(jiàn),則會(huì)延遲到界面可見(jiàn)的時(shí)候再響應(yīng),所以自然就不會(huì)有View空異常的問(wèn)題了。
那么LiveData是如何實(shí)現(xiàn):
- 只在界面可見(jiàn)的時(shí)候才響應(yīng)的
- 如果當(dāng)前界面不可見(jiàn),則會(huì)延遲到界面可見(jiàn)的時(shí)候再響應(yīng)
關(guān)于問(wèn)題1,因?yàn)?code>LiveData是能感知到生命周期的,所以在它回調(diào)響應(yīng)的時(shí)候會(huì)加一個(gè)額外的條件,就是當(dāng)前的生命周期必須是可見(jiàn)狀態(tài)的,才會(huì)繼續(xù)執(zhí)行響應(yīng),源碼如下:
private void considerNotify(ObserverWrapper observer) {
//如果界面不可見(jiàn),則不進(jìn)行響應(yīng)
if (!observer.mActive) {
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
//如果mVersion不大于mLastVersion,說(shuō)明數(shù)據(jù)沒(méi)有發(fā)生變化,則不進(jìn)行響應(yīng)
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}
@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
關(guān)于問(wèn)題2,在LiveData中有一個(gè)全局變量mVersion,而每個(gè)observer中有一個(gè)變量mLastVersion。當(dāng)我們每次setValue()修改一次LiveData的值的時(shí)候,全局的mVersion就會(huì)+1,這樣mVersion就大于mLastVersion:
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
復(fù)制代碼而當(dāng)界面重新可見(jiàn)的時(shí)候,只要判斷到mVersion大于mLastVersion,則就會(huì)進(jìn)行響應(yīng)刷新View,響應(yīng)后才會(huì)更新mLastVersion=mVersion。
4). LiveData為什么是粘性的?
所謂粘性,也就是說(shuō)消息在訂閱之前發(fā)布了,訂閱之后依然可以接受到這個(gè)消息,像EventBus實(shí)現(xiàn)粘性的原理是,把發(fā)布的粘性事件暫時(shí)存在全局的集合里,之后當(dāng)發(fā)生訂閱的那一刻,遍歷集合,將事件拿出來(lái)執(zhí)行。
而LiveData之所以本身就是粘性的,結(jié)合上面的原理圖我們來(lái)分析一下,比如有一個(gè)數(shù)據(jù)(LiveData)在A頁(yè)面setValue()之后,則該數(shù)據(jù)(LiveData)中的全局mVersion+1,也就標(biāo)志著數(shù)據(jù)版本改變,然后再?gòu)腁頁(yè)面打開(kāi)B頁(yè)面,在B頁(yè)面中開(kāi)始訂閱該LiveData,由于剛訂閱的時(shí)候內(nèi)部的數(shù)據(jù)版本都是從-1開(kāi)始,此時(shí)內(nèi)部的數(shù)據(jù)版本就和該 LiveData 全局的數(shù)據(jù)版本 mVersion 不一致,根據(jù)上面的原理圖,B頁(yè)面打開(kāi)的時(shí)候生命周期方法一執(zhí)行,則會(huì)進(jìn)行notify,此時(shí)又同時(shí)滿足頁(yè)面是從不可見(jiàn)變?yōu)榭梢?jiàn)、數(shù)據(jù)版本不一致等條件,所以一進(jìn)B頁(yè)面,B頁(yè)面的訂閱就會(huì)被響應(yīng)一次。這就是所謂的粘性,A頁(yè)面在發(fā)消息的時(shí)候B頁(yè)面是還沒(méi)創(chuàng)建還沒(méi)訂閱該數(shù)據(jù)的,但是一進(jìn)入B頁(yè)面一訂閱,之前在A中發(fā)的消息就會(huì)被響應(yīng)。
那么有些業(yè)務(wù)場(chǎng)景我們是不想要這種粘性的,我們希望只有當(dāng)我們訂閱了該數(shù)據(jù)之后,該數(shù)據(jù)的改變才通知我們,通過(guò)上面的分析,這一點(diǎn)應(yīng)該還是比較好辦到的,只要我們訂閱的時(shí)候?qū)⑷值?code>mVersion同步到內(nèi)部的數(shù)據(jù)版本,這樣訂閱時(shí)候就不會(huì)出現(xiàn)內(nèi)部數(shù)據(jù)版本與全局的mVersion不一致,也就去除了粘性。我這里自定義了一個(gè)可以控制是否需要粘性的LiveData。
3. RxJava
當(dāng)然我個(gè)人認(rèn)為不管是鏈?zhǔn)綄懛?,還是線程模型,異或是解決回調(diào)問(wèn)題都談不上是RxJava的核心優(yōu)點(diǎn),有很多人引入RxJava后項(xiàng)目里只是利用RxJava方便的線程模型來(lái)做簡(jiǎn)單的異步任務(wù),其實(shí)如果只是做異步任務(wù),有非常多種的方式可以替代RxJava。鏈?zhǔn)綄懛ǖ脑捑透皇蔷幋a上的糖果了。如果在沒(méi)有正確的理解RxJava的核心優(yōu)勢(shì)基礎(chǔ)上在代碼里對(duì)RxJava進(jìn)行跟風(fēng)式的濫用,很多時(shí)候你會(huì)發(fā)現(xiàn),代碼并沒(méi)有變簡(jiǎn)潔,甚至有時(shí)候很簡(jiǎn)單的事情被搞的變復(fù)雜了。
我所理解的RxJava的核心優(yōu)勢(shì)應(yīng)該是它可以對(duì)復(fù)雜邏輯進(jìn)行拆分成為一個(gè)一個(gè)的Observable后,RxJava的各種操作符予這些解耦的Observable能夠合理的進(jìn)行再組織的能力,并且它給予了你足夠豐富的再組織能力。這種分拆再組織的能力是十分強(qiáng)大的,只有運(yùn)用好RxJava這種強(qiáng)大的能力,才能真正意義上使你原來(lái)非常復(fù)雜的揉在一團(tuán)的邏輯代碼變得清晰、簡(jiǎn)潔,本質(zhì)上是因?yàn)?code>RxJava給你提供了這種強(qiáng)大方便的組織能力,我覺(jué)得有點(diǎn)像一種編程模式,你可以放心的將復(fù)雜的邏輯拆塊,最后RxJava給你提供了豐富的組織、變換、串聯(lián)、控制這些塊的能力,只有這個(gè)時(shí)候你才會(huì)真正覺(jué)得這是個(gè)好東西,而不應(yīng)該是跟風(fēng)使用,但是心里也說(shuō)不清楚為什么要使用。
回到文章的主題響應(yīng)式,Rxjava就不繼續(xù)展開(kāi)了,這篇只說(shuō)關(guān)于文章主題響應(yīng)式的,看一下RxJava基本使用的時(shí)候一般如下:
//step 1
Observable<String> observable = Observable
.create(new OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("通知觀察者");
subscriber.onCompleted();
} });
//step 2
Observer<String> observer = new Observer<String>() {
@Override
public void onCompleted() {
System.out.println("invoked in onCompleted ");
}
@Override
public void onError(Throwable e) {
System.out.println("invoked in onError ");
}
@Override
public void onNext(String str) {
Log.i("tag", "接收到消息" + str);
System.out.println("接收到消息: " + str);
}
};
//step 3
observable.subscribe(observer);
可以看到被觀察者、觀察者,然后通過(guò)subscribe()把他們進(jìn)行綁定。當(dāng)然可能不看源碼的話唯一有一點(diǎn)疑惑的地方是:
這里notify觀察者的方式是通過(guò)e.onNext(),然后就會(huì)觸發(fā)Observer中的onNext。其實(shí)如果notify觀察者的方式寫成observer.onNext(),就非常明了了。從源碼上看e.onNext()里最后調(diào)用到的就是observer.onNext(),所以就是普通的訂閱發(fā)布模式。
到這里基本上可以知道,訂閱發(fā)布模式是基礎(chǔ),LiveData和RxJava是基于訂閱發(fā)布模式去實(shí)現(xiàn)自己不同的特點(diǎn),比如LiveData的生命周期感知能力,RxJava的話自身具備的能力就更雜更強(qiáng)大一點(diǎn)。下面來(lái)看看響應(yīng)式的應(yīng)用,利用這些響應(yīng)式手段,我們可以來(lái)做些什么,主要舉兩個(gè)例子。
三. 響應(yīng)式的應(yīng)用
1. MVVM
MVC、MVP、MVVM三者并不是說(shuō)哪種模式一定優(yōu)于其他模式,三者有自己各自的優(yōu)缺點(diǎn),主要看具體的項(xiàng)目和具體場(chǎng)景下哪種更適合你的需求,可以更加高效的提升你的項(xiàng)目代碼質(zhì)量和開(kāi)發(fā)效率。
下面所闡述的MVVM的優(yōu)點(diǎn)和缺點(diǎn),都是基于利用Google lifecycle-aware Components的LiveData+ViewModel來(lái)實(shí)現(xiàn)MVVM的基礎(chǔ)上來(lái)說(shuō)的。當(dāng)然這些優(yōu)缺點(diǎn)都是基于我們項(xiàng)目中應(yīng)用實(shí)踐以及個(gè)人的一些看法,所以還是要結(jié)合自己的實(shí)際場(chǎng)景。
1). MVVM優(yōu)點(diǎn)
目前我們產(chǎn)線的項(xiàng)目中占比最大的還是MVP,最開(kāi)始說(shuō)了,其實(shí)使用MVP在解決代碼解耦的基礎(chǔ)上,我們寫起代碼通常是順序性思維,比較流暢,后期去維護(hù)以及代碼閱讀上也相對(duì)流暢,同時(shí)在實(shí)際開(kāi)發(fā)中它也引起了幾個(gè)主要的問(wèn)題:
內(nèi)存泄漏。由于
Presenter里持有了Activity對(duì)象,所以當(dāng)Presenter中執(zhí)行了異步耗時(shí)操作時(shí),有時(shí)候會(huì)引起Activity的內(nèi)存泄漏。
????解決方案: 一個(gè)是可以將Presenter對(duì)Activity的引用設(shè)置為軟引用,還有一個(gè)就是去管理你的異步耗時(shí)任務(wù),當(dāng)Activity退出時(shí)保證他們被取消掉。View空指針異常。有的時(shí)候由于各種原因,
Activity已經(jīng)被回收了,而此時(shí)Presenter中要更新view的話經(jīng)常就會(huì)引起view空異常問(wèn)題。
????解決方案: 當(dāng)然最簡(jiǎn)單的解決方案就是在Presenter中每次要回調(diào)更新界面的時(shí)候都判斷下View(Activity)是否為空,但是這種方式顯然太過(guò)煩瑣可無(wú)法避免疏漏,所以我們可以利用 動(dòng)態(tài)代理 來(lái)實(shí)現(xiàn)代理每個(gè)更新界面的方法,自動(dòng)實(shí)現(xiàn)在每個(gè)更新界面方法之前都判斷一下view是否為空。這樣之后我們就可以大膽的寫代碼而不會(huì)出現(xiàn)view空異常。大量繁瑣的回調(diào)。不知道當(dāng)頁(yè)面足夠復(fù)雜的時(shí)候你是否也體會(huì)過(guò)
Presenter中大量的回調(diào)接口,有時(shí)候這種回調(diào)多了以后,總感覺(jué)這種方式來(lái)更新界面不是非常優(yōu)雅。
上面說(shuō)了幾個(gè)MVP的缺點(diǎn),以及為了解決這些缺點(diǎn),你可以做的一些事。當(dāng)然大量繁瑣的回調(diào)這個(gè)缺點(diǎn)暫時(shí)沒(méi)有很好的解決方案。
而 利用LiveData來(lái)實(shí)現(xiàn)MVVM,剛好能解決以上說(shuō)的這幾個(gè)問(wèn)題,上面說(shuō)了LiveData的優(yōu)點(diǎn)就是能解決內(nèi)存泄漏和View空異常,所以不用做任何額外的事,MVP的前兩個(gè)問(wèn)題就解決了。而第三個(gè)問(wèn)題,由于在MVVM中ViewModel(相當(dāng)于MVP中Presenter)并不持有view的引用,而是只處理數(shù)據(jù)邏輯,所以不存在大量繁瑣回調(diào)的問(wèn)題,只要在Activity中構(gòu)建好數(shù)據(jù)與界面的關(guān)系,利用LiveData來(lái)綁定數(shù)據(jù)與界面的響應(yīng)就可以了,之后只要ViewMoedl中數(shù)據(jù)發(fā)生變化,則響應(yīng)的界面就會(huì)跟著響應(yīng)改變。
所以相對(duì)于MVP來(lái)說(shuō),利用LiveData來(lái)實(shí)現(xiàn)的這套MVVM,不僅能解決上面說(shuō)的這些問(wèn)題,而且使得數(shù)據(jù)與界面的解耦更加徹底,ViewModel中只負(fù)責(zé)數(shù)據(jù)的邏輯處理,所以做單元測(cè)試也十分方便。只要在Activity中構(gòu)建好數(shù)據(jù)與界面響應(yīng)的關(guān)系即可。
2). MVVM缺點(diǎn)
當(dāng)然在我看來(lái)MVVM也有自己的缺點(diǎn),通過(guò)全篇對(duì)響應(yīng)式的探討,應(yīng)該可以知道對(duì)于響應(yīng)式來(lái)說(shuō),最重要的就是關(guān)系的構(gòu)建,其實(shí)對(duì)于MVVM來(lái)說(shuō)一切看起來(lái)都很美好,但是如果涉及到的頁(yè)面邏輯足夠復(fù)雜的時(shí)候,你是否依然能夠建立清晰的關(guān)系,是否能夠保證構(gòu)建的關(guān)系一定可靠,就顯得非常重要,一但你構(gòu)建的關(guān)系有問(wèn)題,則就會(huì)引起bug, 而且這種問(wèn)題bug的排查似乎沒(méi)有順序性思維代碼的那么直接。對(duì)于代碼的閱讀和維護(hù)上,其他人是否能正確的理解和用好你所構(gòu)建的關(guān)系,可能也是一個(gè)問(wèn)題。
最后,貼上一張利用LiveData+Viewmodel實(shí)現(xiàn)MVVM的架構(gòu)圖,也就是Google Architecture Components:

2. 事件總線
說(shuō)到事件總線,我們可能第一個(gè)想到的就是EventBus,他是簡(jiǎn)單的利用了訂閱發(fā)布模式來(lái)實(shí)現(xiàn)的,上面已經(jīng)說(shuō)了,幾種實(shí)現(xiàn)響應(yīng)式的手段的核心都是很相似的,所以自然用RxJava、LiveData也能非常簡(jiǎn)單的實(shí)現(xiàn)一個(gè)事件總線。
EventBus是基于基礎(chǔ)的訂閱發(fā)布模式去實(shí)現(xiàn)的,基本原理的話,就是在register的時(shí)候,解析所有的注解(早期是利用反射解析,后來(lái)為了提高性能,利用編譯時(shí)注解生成代碼),然后將觀察者注冊(cè)進(jìn)全局map,之后在其他地方post一個(gè)消息就可以利用tag在全局集合里找到所有對(duì)應(yīng)的觀察者,然后notify就可以了。
而RxJava和LiveData的核心基礎(chǔ)就是訂閱發(fā)布模式,加上他們自己的優(yōu)勢(shì)特點(diǎn),如LiveData的生命周期感知功能,所以利用他們產(chǎn)生的RxBus、LiveDataBus都只要很少的代碼就能實(shí)現(xiàn)事件總線的功能,因?yàn)?code>LiveData具有避免內(nèi)存泄漏的優(yōu)點(diǎn),所以比EventBus和RxBus還多一個(gè)優(yōu)點(diǎn)就是不用解綁。
如果想深入理解請(qǐng)?jiān)斠?jiàn):
Android消息總線的演進(jìn)之路:用LiveDataBus替代RxBus、EventBus
四. 總結(jié)
這是作者對(duì)響應(yīng)式編程在Android上一些個(gè)人見(jiàn)解,包括對(duì)響應(yīng)式編程思想的理解,以及對(duì)在Android上響應(yīng)式編程實(shí)現(xiàn)手段原理的一些解讀,最后是對(duì)這些工具手段的具體應(yīng)用。目前市面上各種響應(yīng)式層出不窮,所以我們有必要去理解他,吸收他的優(yōu)點(diǎn)。本文的重點(diǎn)偏向文章的主題響應(yīng)式,文中的很多點(diǎn)比如lifecycle-aware、RxJava等展開(kāi)講的話都能有很大的篇幅,之后有時(shí)間可以歸納成文。
Demo參考
五. 參考資料
重新理解響應(yīng)式編程
《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》
響應(yīng)式編程在Android 中的一些探索
Android官網(wǎng)Android Jetpack系列(LiveData ,ViewModel,Room等)