RxAndroid使用初探;簡潔、優雅、高效
引言
RxAndroid是一個開發庫、是一種程式碼風格、也是一種思維方式。
正如標題所言,RxAndroid的特點是簡潔、優雅、高效,它的優點是多執行緒切換簡單、資料變換容易、程式碼簡潔可讀性好、第三方支援豐富易於開發;缺點是學習成本較高、出錯難以排查。
用途與優勢
·起源
RxAndroid源於RxJava——"a library for composing asynchronous and event-based programs using observable sequences for the Java VM“,意為“一個在Java VM 上使用可觀測的序列來組成非同步的、基於事件的程式的庫”。
·用途
RxAndoid在我的粗略理解中,是一個實現非同步操作的庫,具有簡潔的鏈式程式碼,提供強大的資料變換。
在詳細解析 RxAndroid 的使用方式中,有這樣一個例子:
一個典型的Android圖文列表
這個介面是一個典型的Android圖文列表,取線上資料,並顯示為圖文列表,業務流程是這樣的:
1.輸入多個人名
2.根據人名,線上查詢對應的資訊
3.獲取返回資訊,顯示為圖文列表
我們在常規開發中,一般要做到這樣幾件事:
•用非同步執行緒線上查詢資料
•用foreach查詢每個使用者(沒有批量查詢的介面)
•訪問網路,查詢每個人名對應的github資訊(返回Json字串)
•型別轉換(Json->Java Object)
•在主執行緒中接收資料
•每收到一條資料,就用Adapter向列表新增一條,並重新整理介面
實現這樣的需求,可以說功能不多,程式碼不少,少說也得N個類+幾百行程式碼吧,加班到23:59:59應該就能搞定了…工作量主要在非同步網路操作、型別轉換和介面重新整理上。
不過,RxAndroid可以做到,用區區一小段兒程式碼就實現這個需求:
除了MainActivity、layout檔案和Adapter類之外,主要的業務邏輯都在這一段兒程式碼中實現了。感興趣的朋友可以在詳細解析 RxAndroid 的使用方式中找到這段兒程式碼。
剛才誰說要幾百行程式碼來著...
·優勢
我看到這段程式碼,想到的第一件事就是非同步好簡單、程式碼好簡潔,一個簡單、一個簡潔,這就意味著工作效率。
知識結構
前面說過,RxAndroid學習成本比較高,其中一個原因是出現的概念和知識點比較多,我個人傾向於把它們分為三部分:基本概念、執行緒控制和變換操作。
基本概念
先看一下剛才那段兒RxAndroid程式碼,裡面有這麼幾個元素:
圖中出現的基本概念,包括觀察者、被觀察者和訂閱、另外還有一個圖中沒有出現的事件。
執行緒控制和變換操作我們先略過,在後面再看。
我們理解一個觀察者、被觀察者、訂閱和事件的關係:
被觀察者、觀察者、訂閱和事件
這顯然是一個觀察者模式,其中有四個基本概念:Observable (可觀察者,即被觀察者)、 Observer (觀察者)、 subscribe (訂閱)、事件。Observable 和Observer 通過 subscribe() 方法實現訂閱關係,從而 Observable 可以在需要的時候發出事件來通知 Observer。
與傳統觀察者模式不同, RxJava 的事件回撥方法除了普通事件 onNext() (相當於 onClick() / onEvent())之外,還定義了兩個特殊的事件:onCompleted() 和 onError(),作為事件佇列的結束。
·基本概念—被觀察者Observable
被觀察者有這麼幾個特點:
•每一段Rx程式碼都從被觀察者開始
•被觀察者向觀察者丟擲onNext,onCompleted,onError事件,onNext可以連續多個,但onCompleted/onError只能出現一次,且出現後就結束事件佇列
•用一些操作符可以方便地生成Observable物件,例如這段程式碼等價於Observable.just("Hello","Hi","Aloha");
·基本概念—觀察者Observer及其變體
觀察者有這麼幾個特點:
•被觀察者丟擲事件,觀察者響應事件
•觀察者決定如何響應onNext/onCompleted/onError事件
•訂閱(.subscribe(...))的邏輯和寫法是有問題的,從邏輯上是觀察者訂閱被觀察者,但寫法卻好像是被觀察者訂閱了觀察者,這是為了不打斷Rx的鏈式程式碼格式,而且,我個人覺得寫成.subscribeby(...)更恰當
觀察者Observer有多種變體的寫法:
.subscribe(new Observer…)
.subscribe(new Subscriber…)
.subscribe(new onNextAction…,new onErrorAction…,new onCompleteAction…)
.subscribe(new onNextAction…,new onErrorAction…)
.subscribe(new onNextAction…)
其中,Observer是原始寫法;
Subscriber繼承了Observer,是推薦寫法;
也可以直接傳對三個事件的響應;
或者只響應兩個事件、或者只響應一個事件。
ActionN和FuncN
我們注意到,上圖的程式碼中出現了ActionN和FuncN的程式碼形式,這都是RxJava的介面:
ActionN,Action0/Action1/…ActionN是RxJava的介面,可傳入0~N個引數,沒有返回值;
FuncN,從Func0/Func1/…FuncN是RxJava的介面,可傳入0~N個引數,有返回值。
到這裡,其實我們就已經能讀懂大部分的Rx程式碼了,包括最前面那段兒。
·基本概念—訂閱、取消訂閱、延遲
RxAndroid在訂閱和取消訂閱時也有豐富的處理函式:
•doOnSubscribe,在subscribe()呼叫後&事件傳送前,執行,可以指定執行緒
observable.doOnSubscribe(…)
•doOnUnsubscribe,取消訂閱時的監聽
observable.doOnUnsubscribe(…)
•doOnCompleted,正常結束時的監聽
observable.doOnCompleted(…)
•doOnError,出錯時的監聽
observable.doOnError(…)
•doOnTerminate,即將被停止(正常/異常)監聽
observable.doOnTerminate(…)
•finallyDo,結束(正常/異常)監聽
observable.finallyDo(…)
•delay,延遲一段兒時間再發射(已運算)
observable.from(items).delay(5,TimeUnit.SECONDS).observeOn(Schedulers.newThread()).subscribeOn(Schedulers.newThread());
•delaySubscription,延遲onNext事件
observable.delaySubscription(2,TimeUnit.SECONDS)
•timeInterval,定製發射事件
observable.interval(1,TimeUnit.SECONDS).take(5).timeInterval();
•要及時釋放訂閱,否則可能造成記憶體洩漏
·基本概念—事件
就是刷一下事件這個概念的存在感,本節無內容...
·執行緒控制
RxAndroid的一個突出優勢是多執行緒控制非常簡單。
·執行緒控制—跨執行緒
和以往程式碼繁瑣的多執行緒控制不同,RxAndroid可以輕鬆地用一行程式碼來切換執行緒
兩個函式:
•subscribeOn()指定事件產生的執行緒(為前面的程式碼指定執行緒)
•observeOn()指定事件消費的執行緒(為後面的程式碼指定執行緒)
�多鍾執行緒:
•Schedulers.immediate,預設,當前執行緒,立即執行
•Schedulers.trampoline,當前執行緒,要等其他任務先執行
•Schedulers.newThread,啟用一個新執行緒
•Schedulers.io,擅長讀寫檔案、資料庫、網路資訊,一個無數量上限的執行緒池
•Schedulers.computation,擅長cpu密集型計算,固定執行緒池,不要做io操作,會浪費cpu
•AndroidSchedulers.mainThread,Android主執行緒
·執行緒控制—如何操作
我們舉個栗子,說明如何靈活地切換執行緒
需要注意的是:
•subscribeOn只能定義一次,除非是在定義doOnSubscribe
•observeOn可以定義多次,決定後續程式碼所在的執行緒
·執行緒控制—安全性
只要涉及到多執行緒,就一定要考慮安全性問題,這方面,我個人主要考慮3種方法
使用Rx快取:
被觀察者可以使用Rx快取,對同一個被觀察者物件執行訂閱或取消訂閱時,這個被快取的Observable不會去重複查詢資料,它會按自己的節奏去繼續執行網路請求,你需要在生命週期之外去做儲存。
使用RxLifecycle:
與Activity或Fragment繫結生命週期
在指定生命週期事件中解除訂閱
使用CompositeSubscription去收集所有Rx的Subscriptions,統一取消訂閱;
·變換操作
除了前面講的多執行緒操作簡單,Rx還有一個深受廣大群眾喜愛的特點,就是強大的資料變換能力。
所謂變換,就是將事件序列中的物件或整個序列進行加工處理,轉換成不同的事件或事件序列
一個簡單的例子:
實際上,變換有兩大類,一類是針對事件/資料的lift變換,另一類是針對Observable本身的compose變換:
·變換操作—lift變換
我們先看三個典型的lift變換函式:
flatmap函式,可以把把一組String轉換為一組User資料物件。
throttlefirst函式,在連續點選時,在兩秒內只響應第一個點選事件。
merge函式,可以把兩組資料合併為一組資料。
上述3個函式,代表三種lift變換操作符:
轉換操作符:能實現一對一轉換、一對多轉換、依次將輸出作為輸入、分組輸出 等
常用函式包括 map flatMap concatMap flatMapIterable switchMap scan groupBy ...
過濾操作符:能實現去重、取前N個、取後N個、取第N個、從第N個取、自定義過濾 等
常用函式包括 fileter take takeLast takeUntil distinct distinctUntilChanged skip skipLast ...
組合操作符:能實現資料合併、資料運算、資料排列、組合配對 等
常用函式包括 merge zip join combineLatest and/when/then switch startSwitch...
·變換操作—compose變換
compose變換不針對事件和序列,針對的是Observable本身,可以理解為,compose是定義一組變換模板,讓多個Observable執行同樣的變換
一個典型的例子是,用compose函式實現與RxActivity生命週期的繫結:
注意,這裡繫結的是RxActivity的生命週期,Activity本身是沒有這個功能的。
·回顧一下lift和compose變換的區別
lift變換,針對事件項和事件序列
lift的功能在於變換資料,定義如何響應事件:
·變換、如String->Bitmap;
·過濾、如只處理前三條;
·合併、如把兩組資料合併為一組
compose變換,針對Observable
compose的功能在於定義被觀察者的行為:
定義一組變換模板,讓多個Observable實現同一組變換
如:設定Observable的生命週期
變換是RxJava中非常重要,也比較複雜的一部分內容,這裡只是做了簡單的歸納和介紹,建議仔細閱讀給 Android 開發者的 RxJava 詳解和RxJava系列,對變換建立更深入更全面的瞭解。
·回顧知識結構
基本概念裡,我們主要了解了基於事件訂閱的開發風格
執行緒控制裡,我們瞭解到如何簡潔高效地操作非同步執行緒,決定每一段兒程式碼執行的執行緒
�變換操作裡,我們瞭解到RxAndroid如何使用強大的資料查詢/變換/使用
相關開發庫
�在使用RxAndroid的開發中,可選擇很多Rx特性的開發庫
Lambda表示式
Java8的特性,可以進一步簡化程式碼,但暫無Android Studio支援,需要retrolambda外掛,有相容性問題,可讀性和可維護性差,風險高,目前不建議使用
RxBinding
處理控制元件的UI事件,提供一些高階事件,普通如點選、長按、勾選,高階如防抖(throttleFirst,N秒內只允許點選一次)、等待(debounce,一定時間內沒有操作就觸發事件)
RxLifecycle
設定生命週期,如自動跟隨Activity或Fragment的生命週期去建立/銷燬,不用自己寫執行緒,但要使用Rx的Activity和Fragment
Retrofit
最匹配RxAndroid的網路訪問框架,支援Rx的鏈式呼叫,可直接返回資料物件,自定義特性也非常強大
SqlBrite
幫你用Rx的方式完成資料查詢,但它不是一個資料庫框架
·相關開發庫—Labmbda表示式
Lambda與Rx融合得更好,能使程式碼更簡潔(可是…再等等吧)
·相關開發庫—RxBinding
實質是用Observable去包裝UI事件,然後就可以用RxJava的各種轉換功能去擴充套件UI事件
�·相關開發庫—RxLifecycle
通過compose操作符呼叫,完成Observable和當前的UI元件繫結,實現生命週期同步。當UI元件生命週期結束時,就自動取消對Observable訂閱。
bindToLifecycle的生命週期與呼叫的位置有關:
��·相關開發庫—Retrofit
一段兒常規的Retrofit程式碼,包括介面檔案、初始化和呼叫:
在這個例子中,Retrofit拼url的過程如下:
•定義基礎url:"https://api.github.com/"
•定義@GET的引數:"users/{user}"
•定義函式的入參:@Path("user") String username
•基礎url+@GET中的url:"https://api.github.com/users/{user}“(如果@GET的引數是另一個url如http://www.google.com/users/{user},就可以替換掉原來的基礎url)
•根據引數@Path的傳值,更換user的值:"https://api.github.com/users/jake“
Tips: 基礎url不能寫成"https://api.github.com"(必須以“/”結尾)
上述程式碼是void函式,用callback返回資料,Retrofit還可以直接返回Observable物件:
上述例子均為GET方式,Retrofit還可以向伺服器提交POST資料:
因為POST提交Json需要加訊息頭"Content-type","application/json“,因為Retrofit預設使用的是okhttp這個網路框架,所以需要設定okhttp的攔截器Intercepter,攔截http請求進行監控,重寫或重試:
下面大概列舉一下Retrofit各種網路操作的寫法:
需要注意的是,Retrofit使用okhttp實現網路訪問,而okhttp在Android主執行緒執行時會報錯,所以要注意網路操作所在的執行緒。
建議閱讀Retrofit基本使用教程,詳細瞭解一下Retrofit這個網路框架
��·相關開發庫—SqlBrite
如上圖所示,SqlBrite使用Rx的訂閱模式來查詢 ,而不是直接執行查詢,SqlBrite不是一個資料庫框架,他依然需要讓你自己寫sql語句來建立資料庫查詢,就是查詢的等操作,採用了RX的方式來完成,這樣的好處,就是我們在展示列表的時候,增刪改查後,Sqlbrite都會自動的呼叫查詢,來改變你傳入列表的資料。
例項分析
·例項分析-改造目標
我手頭有一個專案,準備用RxAndroid進行改造,並評估改造效果,我希望做到以下幾點:
•改造一個自定義控制元件,使用Rx的Interval來替換執行緒,使用RxLifeCycle把取消訂閱託管給UI元件
•把原來的多層資料訪問及處理改用Rx的鏈式方式實現
•用Retrofit實現Rx風格的網路訪問
•保留MVP模式
•簡化程式碼,增強程式碼可讀性
·例項分析-環境準備
新增gradle引用:
compile'io.reactivex:rxandroid:1.1.0' //
RxAndroid
compile 'io.reactivex:rxjava:1.1.0' //推薦同時載入RxJava
compile'com.squareup.retrofit:retrofit:2.0.0-beta2' // Retrofit網路處理
compile'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' // Retrofit的rx解析庫
compile'com.squareup.retrofit:converter-gson:2.0.0-beta2' // Retrofit的gson庫
compile 'com.trello:rxlifecycle:0.4.0' //RxLifecycle管理Rx的生命週期
compile'com.trello:rxlifecycle-components:0.4.0' // RxLifecycle元件
// RxBinding
compile'com.jakewharton.rxbinding:rxbinding:0.3.0'
compile'com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.3.0'
compile'com.jakewharton.rxbinding:rxbinding-design:0.3.0'
·例項分析-一個自定義控制元件
我有一個自定義的播放控制元件,功能如下:
準備使用Rx的Interval來替換執行緒,使用RxLifeCycle把取消訂閱託管給UI元件
改造前後的程式碼如下:
用Rx的Interval代替原有的執行緒操作
用RxLifeCycle來實現與RxActivity的生命週期同步
改造後的效果:
•工作量大為減少
•不需要複雜的自定義執行緒
•程式碼變簡潔易讀,沒有了迷之縮排,業務邏輯呈鏈式
•提高了可維護性
·例項分析-多層資料訪問
原有的資料訪問框架參考的google開源的mvp,結構如下:
改造前的具體程式碼如下:
改造後的程式碼如下:
主要變化有:
•不使用callback,改為返回Observable,可以與UI層的Rx無縫對接
•網路訪問直接走Retrofit,程式碼更簡潔
•執行緒管理更精細,網路操作在io執行緒上,運算操作在computation執行緒上
•主要邏輯在同一段鏈式程式碼中,提高了程式碼可讀性
•RxJava有強大的操作符,可以進一步簡化程式碼
上述程式碼雖然減少了類檔案的數量,也簡化了程式碼,但結構上還是略顯繁瑣,可以� 進一步簡化,同時保留cache--local--net的三級資料訪問結構:
使用concat+first變換操作符,按照快取->資料庫->網路的順序依次查詢資料
concat()操作符可以持有多個Observable物件,並將它們按順序串聯成佇列
first()操作符可以從多個資料來源中,只檢索和傳送第一個返回的結果,並停止後續的佇列
這兩個操作符組合使用,可以實現逐級訪問三級快取,並更新和儲存資料的功能。
·例項分析-在MVP中使用RxAndroid
我原來的專案結構參考了google的mvp專案,大量的callback呼叫其實與RxAndroid的鏈式呼叫是不相容的,但經過對mvp和RxAndroid各自特點的比較後,我還是決定把他們融合在一起使用。
原因:
•mvp可以較好的剝離和複用業務邏輯,但是付出的代價是程式碼量和檔案數量的增加
•Rx的優勢在於非同步和簡潔,非同步不是mvp的強項,簡潔正好可以解決mvp程式碼龐大的問題
•Rx+mvp可以各取所長,不過mvp一般是通過回撥來處理非同步,在程式碼編寫上需要轉換風格
•在框架上要使用mvp的分層分工思想,而在具體的邏輯實現上,使用Rx的鏈式程式碼去做資料的查詢、處理、顯示
在具體操作上,使用了兩種修改形式:
一種部分修改是,修改Presenter中的重新整理函式,從回撥改為RxAndroid:
另一種修改是,放棄複雜的MVP檔案結構,全部在Fragment中實現:
採用只保留Fragment的方式,看起來比起mvp方式是個倒退,但其實這反而是最合適的做法,事實上,某些業務場景下,使用mvp本來就是過度設計了,我在這個問題上的考慮如下:
•首先考慮業務情況,複用的是Fragment,而不是Presenter,那就只保留Fragment
•Presenter層的非同步執行緒可以在Fragment中用RxLifecycle替代時,也不必須在Presenter中實現非同步執行緒
•資料層只做網路資料訪問,並做資料轉換,完全可以用Retrofit替代
•生命週期全部託管至RxFragment,能減少一部分工作量並消除這部分風險
事情證明,這樣改造後,檔案減少、程式碼減少、可讀性好、可維護性高
我個人對這個專案的改造持肯定態度,我相信深入學習RxAndroid後,還有進一步優化的空間
幾點建議
•Rx涉及的陌生概念比較多,前期上手有一定障礙,要多動手去試
•Rx的功能非常多,這裡只講了冰山一角,建議先系統地瞭解一下RxJava
•要用事件訂閱的思路做開發,和基於回撥的思路有很多區別,要慢慢體會
•Rx的鏈式程式碼比較特殊,和其他形式的程式碼不能混搭,所以同一個業務模組要同時使用Rx,否則工作難以銜接
•還是因為程式碼形式不能混搭,舊專案要改Rx的話,也會遇到需要一口氣打通整條邏輯鏈路的問題
相關文章
- Async:簡潔優雅的非同步之道非同步
- 如何簡潔優雅地部署PostgreSQL和Pgweb?SQLWeb
- 簡潔優雅的內容聚合工具-feedly
- RSS 一種簡潔優雅的資料訂閱方式
- 自監督SOTA框架 | BYOL(優雅而簡潔) | 2020框架
- RxJava(RxKotlin)、RxAndroid 簡單使用RxJavaKotlinAndroid
- EasyAdmin - 市面上最簡潔優雅的後臺管理系統
- Android 一款十分簡潔、優雅的日記 APPAndroidAPP
- 優雅的解決Retrofit RxAndroid關聯生命週期問題Android
- Mybatis原始碼初探——優雅精良的骨架MyBatis原始碼
- QueryList 4.0 簡潔、優雅、可擴充套件的PHP採集工具(爬蟲)套件PHP爬蟲
- Flarum v0.1.0 Beta 2 釋出,優雅簡潔的輕論壇程式
- Go Interface 的優雅使用,讓程式碼更整潔更容易測試Go
- Java學習-18 簡潔高效的jQueryJavajQuery
- Docker:構建API的簡潔高效之道DockerAPI
- 簡潔地使用 vim
- 讓程式碼變得優雅簡潔的神器:Java8 Stream流式程式設計Java程式設計
- Keka Mac:高效簡潔的壓縮解壓工具Mac
- iA Writer for Mac:簡潔高效的MarkDown寫作利器Mac
- 優雅的使用UITableViewUIView
- 如何優雅使用 vuexVue
- Flutter GetX使用---簡潔的魅力!Flutter
- Git命令簡潔使用指南Git
- 簡潔/易用/靈活/高效->RecyclerView介面卡封裝View封裝
- 如何優雅而高效地使用Matplotlib實現資料視覺化視覺化
- 如何優雅地使用 macOSMac
- 如何優雅的使用介面
- 如何優雅的使用MyBatis?MyBatis
- 如何優雅地使用 GitGit
- 9個JavaScript小技巧:寫出更簡潔,高效程式碼JavaScript
- 編寫高效且優雅的 Python 程式碼(1)Python
- 優雅通過HttpClientFactory使用HttpClientHTTPclient
- 如何優雅的使用Mock ServerMockServer
- Laravel如何優雅的使用SwooleLaravel
- 優雅的使用UITableView(Swift 中)UIViewSwift
- 優雅的使用UITableView(OC 上)UIView
- Linux 優雅使用哲學Linux
- R資料分析:如何簡潔高效地展示統計結果