響應式在前端領域已經變得十分流行,不少主流框架都採用響應式來進行頁面的展現刷新。本文主要是探索一下響應式在移動端Android上的一些實踐,包括對響應式思想的理解,以及目前Android上實現響應式的一些手段,最後聊聊響應式在Android開發上的一些應用。若是你也在傳統的開發模式過程當中隨着項目代碼的增長以及業務邏輯複雜度的增長髮現諸如大量的繁瑣回調、不可控的內存泄漏、空指針等問題,但願本文的一些分享能夠給你帶來一點點新的選擇。前端
本文主要包括如下幾部分:java
概念:響應式編程是一種經過異步和數據流來構建事物關係的編程模型。react
如何更加通俗易懂的理解?git
其實咱們的業務開發中自己蘊含着各類各樣的事物關係,以加載一個列表爲例,列表數據爲空時展現怎樣的界面,列表有數據時如何展現,數據加載失敗時怎麼展現,這就是列表數據與列表界面之間的事物關係。github
而咱們一般編碼模式,「人」在其中扮演了太重的角色
,咱們既要負責去取數據,取完數據還要負責把數據傳送給界面展現,操碎了心。某種意義上這是一種順序性思惟的編程,我要作什麼,而後作什麼,最後作什麼,循序漸進編寫就行了。具體以下圖:編程
而響應式編程
,則從一開始就明確構建了事物與事物之間的關係
,解脫了"人"
,以後一個事物發生變化另外一個事物就自動響應。提早構建好事物與事物之間的關係,減輕"人"的角色。畫個圖,以下:架構
上面說的響應式主要仍是一種編程思想,而如何來實現這樣一種思想呢?固然訂閱發佈模式是基礎,而像RxJava、LiveData等的出現,讓響應式編程的實現手段變得更加的豐富。app
訂閱發佈模式是實現響應式的基礎,這種模式咱們都很熟悉了,主要是經過把觀察者的回調註冊進被觀察者來實現兩者的訂閱關係,當被觀察者notify的時候,則全部的觀察就會自動響應。這種模式也實現了觀察者和被觀察者的解耦。框架
LiveData是google發佈的lifecycle-aware components中的一個組件,除了能實現數據和View的綁定響應以外,它最大的特色就是具有生命週期感知功能,這使得他具有如下幾個優勢:less
解決內存泄漏問題
。因爲LiveData會在Activity/Fragment等具備生命週期的lifecycleOwner onDestory的時候自動解綁,因此解決了可能存在的內存泄漏問題。以前咱們爲了不這個問題,通常有註冊綁定的地方都要解綁,而LiveData利用生命週期感知功能解決了這一問題。解決常見的View空異常
。咱們一般在一個異步任務回來後須要更新View,而此時頁面可能已經被回收,致使常常會出現View空異常,而LiveData因爲具有生命週期感知功能,在界面可見的時候纔會進行響應,如界面更新等,若是在界面不可見的時候發起notify,會等到界面可見的時候才進行響應更新。因此就很好的解決了空異常的問題。LiveData的實現上能夠說是訂閱發佈模式+生命週期感知
,對於Activity/Fragment等LifecycleOwner來講LiveData是觀察者,監聽者生命週期,而同時LiveData又是被觀察者,咱們經過觀察LiveData,實現數據和View的關係構建。
LiveData是粘性的,這是你在使用前須要知道的,以避免由於粘性形成一些問題,使用EventBus的時候咱們知道有一種事件模式是粘性的,特色就是消息能夠在observer註冊以前發送,當observer註冊時,依然可接收到以前發送的這個消息。而LiveData天生就是粘性的,下面會講解爲何他是粘性的,以及若是在一些業務場景上不想要LiveData是粘性的該怎麼作。
單純的貼源碼,分析源碼可能比較枯燥,因此下面就儘可能以拋出問題,而後解答的方式來解析LiveData的原理。
lifecycle-aware compents的核心就是生命週期感知,要明白LiveData爲何能感知生命週期,就要知道Google的這套生命週期感知背後的原理是什麼,下面是我基於以前lifeycycle這套東西剛出來時候對源碼進行的一個分析總結(如今的最新代碼可能和以前有點出入,可是原理上基本是同樣的):
首先Activity/Fragment是LifecycleOwner(26.1.0以上的support包中Activity已經默認實現了LifecycleOwner接口),內部都會有一個LifecycleRegistry存放生命週期State、Event等。而真正核心的操做是,每一個Activity/Fragment在啓動時都會自動添加進來一個Headless Fragment(無界面的Fragment)
,因爲添加進來的Fragment與Activity的生命週期是同步的,因此當Activity執行相應生命週期方法的時候,同步的也會執行Headless Fragment的生命週期方法,因爲這個這個Headless Fragment對咱們開發者來講是隱藏的,它會在執行本身生命週期方法的時候更新Activity的LifecycleRegistry裏的生命週期State、Event, 而且notifyStateChanged來通知監聽Activity生命週期的觀察者。這樣就到達了生命週期感知的功能,因此實際上是一個隱藏的Headless Fragment來實現了監聽者能感知到Activity的生命週期。
基於這套原理,只要LiveData註冊了對Activity/Fragment的生命週期監聽,也就擁有了感知生命週期的能力。從LiveData的源碼裏體現以下:
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
``````
``````
owner.getLifecycle().addObserver(wrapper);//註冊對Activity/Fragment生命週期的監聽
}
複製代碼
下面附一張當時對Google lifecycle-aware原理進行源碼分析隨手畫的圖:
因此到這裏咱們基本上已經知道了生命週期感知這套東西的原理,接下來咱們就能夠來看看LiveData的實現原理了,下我把LiveData的源碼抽象爲一張流程圖來展現,下面的其餘問題均可以在這張圖中找到答案:
能夠看到,在LiveData所依附的Activity/Fragment生命週期發生改變或者經過setValue()改變LiveData數據的時候都會觸發notify
,可是觸發後,真正要走到最終的響應(即咱們註冊進去的onChanged()回調)則中間要經歷不少判斷條件,這也是爲何LiveData能具備本身那些特色的緣由.
經過上面,咱們能夠知道,當Activity/Fragment的生命週期發生改變時,LiveData中的監聽都會被回調
,因此避免內存泄漏就變得十分簡單,能夠看上圖,當LiveData監聽到Activity onDestory時則removeObserve,使本身與觀察者自動解綁。這樣就避免了內存泄漏。 源碼上體現以下:
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
複製代碼
這個問題很簡單,看上圖,由於LiveData響應(好比更新界面操做View)只會在界面可見的時候,若是當前見面不可見,則會延遲到界面可見的時候再響應,因此天然就不會有View空異常的問題了。
那麼LiveData是如何實現:
只在界面可見的時候才響應的
若是當前界面不可見,則會延遲到界面可見的時候再響應
關於問題1,由於LiveData是能感知到生命週期的,因此在它回調響應的時候會加一個額外的條件,就是當前的生命週期必須是可見狀態的,纔會繼續執行響應,源碼以下:
private void considerNotify(ObserverWrapper observer) {
//若是界面不可見,則不進行響應
if (!observer.mActive) {
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
//若是mVersion不大於mLastVersion,說明數據沒有發生變化,則不進行響應
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}
複製代碼
@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
複製代碼
關於問題2,在LiveData中有一個全局變量mVersion
,而每一個observer中有一個變量mLastVersion
。當咱們每次setValue()修改一次LiveData的值的時候,全局的mVersion就會+1,這樣mVersion就大於mLastVersion:
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
複製代碼
而當界面從新可見的時候,只要判斷到mVersion大於mLastVersion,則就會進行響應刷新View,響應後纔會更新mLastVersion=mVersion。
所謂粘性,也就是說消息在訂閱以前發佈了,訂閱以後依然能夠接受到這個消息,像EventBus實現粘性的原理是,把發佈的粘性事件暫時存在全局的集合裏,以後當發生訂閱的那一刻,遍歷集合,將事件拿出來執行。
而LiveData之因此自己就是粘性的,結合上面的原理圖咱們來分析一下,好比有一個數據(LiveData)在A頁面setValue()以後,則該數據(LiveData)中的全局mVersion+1,也就標誌着數據版本改變,而後再從A頁面打開B頁面,在B頁面中開始訂閱該LiveData,因爲剛訂閱的時候內部的數據版本都是從-1開始,此時內部的數據版本就和該LiveData全局的數據版本mVersion不一致
,根據上面的原理圖,B頁面打開的時候生命週期方法一執行,則會進行notify,此時又同時知足頁面是從不可見變爲可見、數據版本不一致等條件,因此一進B頁面,B頁面的訂閱就會被響應一次。這就是所謂的粘性,A頁面在發消息的時候B頁面是還沒建立還沒訂閱該數據的,可是一進入B頁面一訂閱,以前在A中發的消息就會被響應。
那麼有些業務場景咱們是不想要這種粘性的,咱們但願只有當咱們訂閱了該數據以後,該數據的改變才通知咱們,經過上面的分析,這一點應該仍是比較好辦到的,只要咱們訂閱的時候將全局的mVersion同步到內部的數據版本,這樣訂閱時候就不會出現內部數據版本與全局的mVersion不一致,也就去除了粘性。我這裏自定義了一個能夠控制是否須要粘性的LiveData。
具體代碼見: CustomStickyLiveData
RxJava是能夠實現響應式編程的另一個手段,Rxjava也是熱度很是高的一個開源庫,固然咱們都知道RxJava一個是有訂閱發佈模式解耦的優勢,還有其線程模型、鏈式寫法都是其優勢。
固然我我的認爲無論是鏈式寫法,仍是線程模型,異或是解決回調問題都談不上是RxJava的核心優勢,有不少人引入RxJava後項目裏只是利用RxJava方便的線程模型來作簡單的異步任務,其實若是隻是作異步任務,有很是多種的方式能夠替代RxJava。鏈式寫法的話就更只是編碼上的糖果了。若是在沒有正確的理解RxJava的核心優點
基礎上在代碼裏對RxJava進行跟風式的濫用,不少時候你會發現,代碼並無變簡潔,甚至有時候很簡單的事情被搞的變複雜了。
我所理解的RxJava的核心優點應該是它能夠對複雜邏輯進行拆分紅爲一個一個的Observable後,RxJava的各類操做符予這些解耦的Observable可以合理的進行再組織的能力,而且它給予了你足夠豐富的再組織能力。這種分拆再組織的能力是十分強大的,只有運用好RxJava這種強大的能力,才能真正意義上使你原來很是複雜的揉在一團的邏輯代碼變得清晰、簡潔,本質上是由於RxJava給你提供了這種強大方便的組織能力,我以爲有點像一種編程模式,你能夠放心的將複雜的邏輯拆塊,最後RxJava給你提供了豐富的組織、變換、串聯、控制這些塊的能力,只有這個時候你纔會真正以爲這是個好東西,而不該該是跟風使用,可是內心也說不清楚爲何要使用。
回到文章的主題響應式,Rxjava就不繼續展開了,這篇只說關於文章主題響應式的:
看一下RxJava基本使用的時候通常以下:
Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> e) throws Exception {
e.onNext("通知觀察者");
}
}).subscribe(new io.reactivex.Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String s) {
Log.i("tag", "接收到消息" + s);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
複製代碼
能夠看到被觀察者、觀察者,而後經過subscribe()把他們進行綁定。固然可能不看源碼的話惟一有一點疑惑的地方是: 這裏notify觀察者的方式是經過e.onNext(),而後就會觸發Observer中的onNext。其實若是notify觀察者的方式寫成observer.onNext(),就很是明瞭了。從源碼上看e.onNext()裏最後調用到的就是observer.onNext(),因此就是普通的訂閱發佈模式。
到這裏基本上能夠知道,訂閱發佈模式是基礎,LiveData和RxJava是基於訂閱發佈模式去實現本身不一樣的特色,好比LiveData的生命週期感知能力,RxJava的話自身具有的能力就更雜更強大一點。下面來看看響應式的應用,利用這些響應式手段,咱們能夠來作些什麼,主要舉兩個例子。
MVC、MVP、MVVM三者並非說哪一種模式必定優於其餘模式,三者有本身各自的優缺點,主要看具體的項目和具體場景下哪一種更適合你的需求,能夠更加高效的提高你的項目代碼質量和開發效率。
我下面所闡述的MVVM的優勢和缺點,都是基於利用Google lifecycle-aware Components的LiveData+ViewModel來實現MVVM的基礎上來講的。固然這些優缺點都是我基於咱們項目中應用實踐以及我的的一些見解,鞋適不適合只有腳知道,因此仍是要結合本身的實際場景。
目前咱們產線的項目中佔比最大的仍是MVP,最開始說了,其實使用MVP在解決代碼解耦的基礎上,咱們寫起代碼一般是順序性思惟,比較流暢,後期去維護以及代碼閱讀上也相對流暢,同時在實際開發中它也引發了幾個主要的問題:
內存泄漏
。因爲Presenter裏持有了Activity對象,因此當Presenter中執行了異步耗時操做時,有時候會引發Activity的內存泄漏。
解決的方案: 一個是能夠將Presenter對Activity的引用設置爲軟引用,還有一個就是去管理你的異步耗時任務,當Activity退出時保證他們被取消掉。
View空指針異常
。有的時候因爲各類緣由,Activity已經被回收了,而此時Presenter中要更新View的話常常就會引發view空異常問題。
解決方案: 固然最簡單的解決方案就是在Presenter中每次要回調更新界面的時候都判斷下View(Activity)是否爲空,可是這種方式顯然太過煩瑣可沒法避免疏漏,因此咱們能夠利用動態代理
來實現代理每一個更新界面的方法,自動實如今每一個更新界面方法以前都判斷一下view是否爲空。這樣以後咱們就能夠大膽的寫代碼而不會出現view空異常。
大量繁瑣的回調。
不知道當頁面足夠複雜的時候你是否也體會過Presenter中大量的回調接口,有時候這種回調多了之後,總感受這種方式來更新界面不是很是優雅。
上面說了幾個MVP的缺點,以及爲了解決這些缺點,你能夠作的一些事。固然大量繁瑣的回調這個缺點暫時沒有很好的解決方案。
而利用LiveData來實現MVVM,恰好能解決以上說的這幾個問題
,上面說了LiveData的優勢就是能解決內存泄漏和View空異常,因此不用作任何額外的事,MVP的前兩個問題就解決了。而第三個問題,因爲在MVVM中ViewModel(至關於MVP中Presenter)並不持有view的引用,而是隻處理數據邏輯,因此不存在大量繁瑣回調的問題,只要在Activity中構建好數據與界面的關係,利用LiveData來綁定數據與界面的響應就能夠了,以後只要ViewMoedl中數據發生變化,則響應的界面就會跟着響應改變。
因此相對於MVP來講,利用LiveData來實現的這套MVVM,不只能解決上面說的這些問題,並且使得數據與界面的解耦更加完全,ViewModel中只負責數據的邏輯處理,因此作單元測試也十分方便。只要在Activity中構建好數據與界面響應的關係便可。
固然在我看來MVVM也有本身的缺點
,經過全篇對響應式的探討,應該能夠知道對於響應式來講,最重要的就是關係的構建
,其實對於MVVM來講一切看起來都很美好,可是若是涉及到的頁面邏輯足夠複雜的時候,你是否依然可以創建清晰的關係,是否可以保證構建的關係必定可靠,就顯得很是重要,一但你構建的關係有問題,則就會引發bug, 並且這種問題bug的排查彷佛沒有順序性思惟代碼的那麼直接。對於代碼的閱讀和維護上,其餘人是否能正確的理解和用好你所構建的關係,可能也是一個問題。
最後,貼上一張利用LiveData+Viewmodel實現MVVM的架構圖,也就是Google Architecture Components:
說到事件總線,咱們可能第一個想到的就是EventBus,他是簡單的利用了訂閱發佈模式來實現的,上面已經說了,幾種實現響應式的手段的核心都是很類似的,因此天然用RxJava、LiveData也能很是簡單的實現一個事件總線。
EventBus是基於基礎的訂閱發佈模式去實現的,基本原理的話,就是在register的時候,解析全部的註解(早期是利用反射解析,後來爲了提升性能,利用編譯時註解生成代碼),而後將觀察者註冊進全局map,以後在其餘地方post一個消息就能夠利用tag在全局集合裏找到全部對應的觀察者,而後notify就能夠了。
而RxJava和LiveData的核心基礎就是訂閱發佈模式,加上他們本身的優點特色,如LiveData的生命週期感知功能,因此利用他們產生的RxBus、LiveDataBus都只要不多的代碼就能實現事件總線的功能,由於LiveData具備避免內存泄漏的優勢,因此比EventBus和RxBus還多一個優勢就是不用解綁。
這是我對響應式編程在Android上一些我的看法,包括對響應式編程思想的理解,以及對在Android上響應式編程實現手段原理的一些解讀,最後是對這些工具手段的具體應用。目前市面上各類響應式層出不窮,因此咱們有必要去理解他,吸取他的優勢。本文的重點偏向文章的主題響應式,文中的不少點好比lifecycle-aware、RxJava等展開講的話都能有很大的篇幅,以後有時間能夠概括成文。