這是《使用Kotlin開發一個現代的APP》系列文章的第三部分,還沒看過前2部分的,可以先看一下:
【譯】使用Kotlin從零開始寫一個現代Android 項目-Part1
【譯】使用Kotlin從零開始寫一個現代Android 項目-Part2
正文開始!
什麼是RxJava ?
關於RxJava,一個廣泛的概念是-RxJava是用於異步編程的API的Java實現,它具有可觀察流和響應式的API。實際上,它是這三個概念的結合:觀察者模式、迭代器模式和函數式編程。這裏也有其他編程語言實現的庫,如:RxSwift、RxJs 、RxNet等。
我RxJava上手很難,有時,它確實很令人困惑,如果實施不當,可能會給您帶來一些問題。儘管如此,我們還是值得花時間學習它。我將嘗試通過簡單的步驟來解釋RxJava。
首先,讓我們回答一些簡單的問題,當您開始閱讀有關RxJava時,可能會問自己:
我們真的需要它嗎?
答案是否定的,RxJava只是可以在Android開發中使用的又一個庫。如果使用Kotlin開發,它也不是必須的,我希望你明白我說的,它只一個很幫助你的庫,就像你使用的所以其他庫一樣。
要學習RxJava2,必須先學RxJava1嗎?
你可以直接從RxJava2開始,不過,作爲Android開發人員,知道這兩種情況對你還是有好處的,因爲你可能會參與維護其他人的RxJava1代碼。
我看到有RxAndroid,應該使用RxAndroid還是RxJava?
RxJava能用在任何Java開發平臺,不僅僅是Android,比如,對於後端開發來說,RxJava 可以與Spring等框架一起使用,RxAndroid是一個庫,其中包含在Android中使用RxJava所需的庫。因此,如果要在Android開發中使用RxJava,則必須再添加RxAndroid。稍後,我將解釋RxAndroid基於RxJava所添加的內容。
我們使用Kotlin開發,爲什麼不用RxKotin呢?
我們沒有必要另外再添加一個Rx 庫了,因爲Kotlin與Java是完全兼容的,這裏確實有一個RxKotin庫:https://github.com/ReactiveX/RxKotlin ,不過該庫是在RxJava之上編寫的。它只是將Kotlin功能添加到RxJava。您可以將RxJava與Kotlin一起使用,而無需使用RxKotlin庫。爲了簡單起見,在這一部分中我將不使用RxKotlin。
如何將Rxjava2添加到項目中?
要使用RxJava,你需要在build.gradle
中添加如下代碼:
dependencies {
...
implementation "io.reactivex.rxjava2:rxjava:2.1.8"
implementation "io.reactivex.rxjava2:rxandroid:2.0.1"
...
}
然後,點擊sync
,下載Rxjava庫。
RxJava包含了些啥?
我想把RxJava分爲以下三部分:
- 1、用於觀察者模式和數據流的類:
Observables
和Observers
- 2、Schedulers
- 3、數據流操作符
Observables
和 Observers
我們已經解釋了這種模式。您可以將Observable視爲數據的源(被觀察者
),將Observer視爲接收數據的源(觀察者
)。
有很多創建Observables的方式,最簡單的方法是使用Observable.just()
來獲取一個項目並創建Observable來發射該項目。
讓我們轉到GitRepoRemoteDataSource
類並更改getRepositories
方法,以返回Observable:
class GitRepoRemoteDataSource {
fun getRepositories() : Observable<ArrayList<Repository>> {
var arrayList = ArrayList<Repository>()
arrayList.add(Repository("First from remote", "Owner 1", 100, false))
arrayList.add(Repository("Second from remote", "Owner 2", 30, true))
arrayList.add(Repository("Third from remote", "Owner 3", 430, false))
return Observable.just(arrayList).delay(2,TimeUnit.SECONDS)
}
}
Observable <ArrayList <Repository >>
表示Observable發出Repository對象的數組列表。如果要創建發出Repository對象的Observable ,則應使用Observable.from(arrayList)
。
.delay(2,TimeUnit.SECONDS)
表示延遲2s後纔開始發射數據。
但是,等等!我們並沒有高數Observable何時發射數據啊?Observables通常在一些Observer訂閱後就開始發出數據。
請注意,我們不再需要以下接口了
interface OnRepoRemoteReadyCallback {
fun onRemoteDataReady(data: ArrayList<Repository>)
}
在GitRepoLocalDataSource:
類中做同樣的更改
class GitRepoLocalDataSource {
fun getRepositories() : Observable<ArrayList<Repository>> {
var arrayList = ArrayList<Repository>()
arrayList.add(Repository("First From Local", "Owner 1", 100, false))
arrayList.add(Repository("Second From Local", "Owner 2", 30, true))
arrayList.add(Repository("Third From Local", "Owner 3", 430, false))
return Observable.just(arrayList).delay(2, TimeUnit.SECONDS)
}
fun saveRepositories(arrayList: ArrayList<Repository>) {
//todo save repositories in DB
}
}
同樣的,也不需要這個接口了:
interface OnRepoLocalReadyCallback {
fun onLocalDataReady(data: ArrayList<Repository>)
}
現在,我們需要在repository
中返回Observable
class GitRepoRepository(private val netManager: NetManager) {
private val localDataSource = GitRepoLocalDataSource()
private val remoteDataSource = GitRepoRemoteDataSource()
fun getRepositories(): Observable<ArrayList<Repository>> {
netManager.isConnectedToInternet?.let {
if (it) {
//todo save those data to local data store
return remoteDataSource.getRepositories()
}
}
return localDataSource.getRepositories()
}
}
如果網絡已連接,我們從遠程數據源返回Observable,否則,從本地數據源返回Observable,同樣的,我們也不再需要OnRepositoryReadyCallback
接口。
如你所料,我們需要更改在MainViewModel中獲取數據的方式。現在我們應該從gitRepoRepository
獲取Observable並訂閱它。一旦我們向Observer訂閱了該Observable,Observable將開始發出數據:
class MainViewModel(application: Application) : AndroidViewModel(application) {
...
fun loadRepositories() {
isLoading.set(true)
gitRepoRepository.getRepositories().subscribe(object: Observer<ArrayList<Repository>>{
override fun onSubscribe(d: Disposable) {
//todo
}
override fun onError(e: Throwable) {
//todo
}
override fun onNext(data: ArrayList<Repository>) {
repositories.value = data
}
override fun onComplete() {
isLoading.set(false)
}
})
}
}
一旦Observer訂閱了Observable,onSubscribe
方法將被調用,主要onSubscribe
的參數Disposable
,稍後將講到它。
每當Observable發出數據時,將調用onNext()
方法。當Observable完成s數據發射時,onComplete()
將被調用一次。之後,Observable終止。
如果發生某些異常,onError()
方法將被回調,然後Observable終止。這意味着Observable將不再發出數據,因此onNext()
不會被調用,也不會調用onComplete()
。
另外,請注意。如果嘗試訂閱已終止的Observable,則將收到IllegalStateException
。
那麼,RxJava如何幫助我們?
- 首先,我們擺脫了這些接口,它是所有repository和數據源建立的樣板接口。
- 如果我們使用接口,並且在數據層中發生某些異常,則我們的應用程序可能會崩潰。使用RxJava錯誤將在
onError()
方法中返回,因此我們可以向用戶顯示適當的錯誤消息。 - 因爲我們始終將RxJava用於數據層,它更清晰。
- 我之前沒有告訴過你:以前的方法可能會導致內存泄漏。
使用RxJava2和ViewModel時,如何防止內存泄漏
我們再一次看一下ViewModel的生命週期圖
一旦Activity銷燬,ViewModel的onCleared
方法將被調用,在onCleared
方法中,我們需要取消所有訂閱
class MainViewModel(application: Application) : AndroidViewModel(application) {
...
lateinit var disposable: Disposable
fun loadRepositories() {
isLoading.set(true)
gitRepoRepository.getRepositories().subscribe(object: Observer<ArrayList<Repository>>{
override fun onSubscribe(d: Disposable) {
disposable = d
}
override fun onError(e: Throwable) {
//if some error happens in our data layer our app will not crash, we will
// get error here
}
override fun onNext(data: ArrayList<Repository>) {
repositories.value = data
}
override fun onComplete() {
isLoading.set(false)
}
})
}
override fun onCleared() {
super.onCleared()
if(!disposable.isDisposed){
disposable.dispose()
}
}
}
我們可以優化一下上面的代碼:
首先,使用DisposableObserver
替換Observer
,它實現了Disposable並且有dispose()
方法,我們不再需要onSubscribe()
方法,因爲我們可以直接在DisposableObserver實例上調用dispose()
。
第二步,替換掉返回Void的.subscribe()
方法,使用.subscribeWith()
方法,他能返回指定的Observer
class MainViewModel(application: Application) : AndroidViewModel(application) {
...
lateinit var disposable: Disposable
fun loadRepositories() {
isLoading.set(true)
disposable = gitRepoRepository.getRepositories().subscribeWith(object: DisposableObserver<ArrayList<Repository>>() {
override fun onError(e: Throwable) {
// todo
}
override fun onNext(data: ArrayList<Repository>) {
repositories.value = data
}
override fun onComplete() {
isLoading.set(false)
}
})
}
override fun onCleared() {
super.onCleared()
if(!disposable.isDisposed){
disposable.dispose()
}
}
}
上面的代碼還可以繼續優化:
我們保存了一個Disposable實例,因此,我們纔可以在onCleared()
回調中調用dispose()
,但是等等!我們需要爲每一個調用都這樣做嗎?如果有10個回調,那麼我們得保存10個實例,在onCleared()
中取消10次訂閱?顯然不可能,這裏有更好的方法,我們應該將它們全部保存在一個存儲桶中,並在調用onCleared()
方法時,將它們全部一次處理。我們可以使用CompositeDisposable
。
CompositeDisposable
:可容納多個Disposable的容器
因此,每次創建一個Disposable,都需要將其添加到CompositeDisposable
中:
class MainViewModel(application: Application) : AndroidViewModel(application) {
...
private val compositeDisposable = CompositeDisposable()
fun loadRepositories() {
isLoading.set(true)
compositeDisposable.add(gitRepoRepository.getRepositories().subscribeWith(object: DisposableObserver<ArrayList<Repository>>() {
override fun onError(e: Throwable) {
//if some error happens in our data layer our app will not crash, we will
// get error here
}
override fun onNext(data: ArrayList<Repository>) {
repositories.value = data
}
override fun onComplete() {
isLoading.set(false)
}
}))
}
override fun onCleared() {
super.onCleared()
if(!compositeDisposable.isDisposed){
compositeDisposable.dispose()
}
}
}
感謝Kotlin的擴展函數,我們還可以更進一步:
與C#和Gosu相似,Kotlin提供了使用新功能擴展類的能力,而不必繼承該類,也就是擴展函數。
讓我們創建一個新的包,叫做extensions
,並且添加一個新的文件RxExtensions.kt
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
operator fun CompositeDisposable.plusAssign(disposable: Disposable) {
add(disposable)
}
現在我們可以使用+ =
符號將Disposable對象添加到CompositeDisposable實例:
class MainViewModel(application: Application) : AndroidViewModel(application) {
...
private val compositeDisposable = CompositeDisposable()
fun loadRepositories() {
isLoading.set(true)
compositeDisposable += gitRepoRepository.getRepositories().subscribeWith(object : DisposableObserver<ArrayList<Repository>>() {
override fun onError(e: Throwable) {
//if some error happens in our data layer our app will not crash, we will
// get error here
}
override fun onNext(data: ArrayList<Repository>) {
repositories.value = data
}
override fun onComplete() {
isLoading.set(false)
}
})
}
override fun onCleared() {
super.onCleared()
if (!compositeDisposable.isDisposed) {
compositeDisposable.dispose()
}
}
}
現在,我們運行程序,當你點擊Load Data
按鈕,2s之後,程序crash,然後,如果查看日誌,您將看到onNext
方法內部發生錯誤,並且異常的原因是:
java.lang.IllegalStateException: Cannot invoke setValue on a background thread
爲何會發生這個異常?
Schedulers
RxJava附帶有調度器(Schedulers),使我們可以選擇在哪個線程代碼上執行。更準確地說,我們可以選擇使用subscribeOn()
方在哪個線程執行,observeOn()
方法可以觀察哪個線程觀察者。通常情況下,我們所有的數據層代碼都應該在後臺線程執行,例如,如果我們使用Schedulers.newThread()
,每當我們調用它時,調度器都會給我們分配一個新的線程,爲了簡單起見,Scheduler中還有其他一些方法,我將不在本博文中介紹。
可能您已經知道所有UI代碼都是在Android 主線程上完成的。 RxJava是Java庫,它不瞭解Android主線程,這就是我們使用RxAndroid的原因。 RxAndroid使我們可以選擇Android Main線程作爲執行代碼的線程。顯然,我們的Observer應該在Android Main線程上運行。
讓我們更改一下代碼:
...
fun loadRepositories() {
isLoading.set(true)
compositeDisposable += gitRepoRepository
.getRepositories()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableObserver<ArrayList<Repository>>() {
...
})
}
...
然後再運行代碼,一切都正常了,nice~
其他 observables types
這裏還有一些其他的observable 類型
- Single: 被觀察者僅發射一個數據,或者是一個異常
- Maybe: 被觀察者不發射數據,或者僅發射一個數據,或者是一個異常
- Completable : 發射
onSuccess()
事件或者異常 - Flowable:和
Observable<T>
一樣,不發射數據,或者發射n個數據,或者發射異常,但是Observable不支持背壓,而Flowable卻支持。
*
什麼是背壓(backpressure)?
爲了記住一些概念,我喜歡將它們與現實中的一些例子類比
把它類比成一個通道,如果你向通道中塞入瓶頸能夠接受的最多的商品,這將會變得很糟,這裏也是同樣的,有時,你的觀察者無法處理其收到的事件數量,因此需要放慢速度。
你可以看看RxJava 關於背壓的文檔:https://github.com/ReactiveX/RxJava/wiki/Backpressure-(2.0)
操作符
RxJava中,最牛逼的就是它的操作符了,僅用一行代碼即可在RxJava中解決一些通常需要10行或更多行的問題。這些是操作符可以幫我們做的:
- 合併observables
- 過濾
- 按條件來做操作
- 將observables 轉換爲其他類型
我給你舉一個例子,讓我們將數據保存到GitRepoLocalDataSource中。因爲我們正在保存數據,所以我們需要Completable來模擬它。假設我們還想模擬1秒的延遲。天真的方法是:
fun saveRepositories(arrayList: ArrayList<Repository>): Completable {
return Completable.complete().delay(1,TimeUnit.SECONDS)
}
爲什麼說天真?
Completable.complete()
返回一個Completable實例,該實例在訂閱後立即完成。
一旦Completable 完成後,它將終止。因此,之後將不執行任何運算符(延遲是運算符之一)。在這種情況下,我們的Completable不會有任何延遲。讓我們找解決方法:
fun saveRepositories(arrayList: ArrayList<Repository>): Completable {
return Single.just(1).delay(1,TimeUnit.SECONDS).toCompletable()
}
爲什麼是這種方式?
Single.just(1)
創建一個Single實例,並且僅發射一個數字1,因爲我們用了delay(1,TimeUnit.SECONDS)
,因此發射操作延遲1s。
toCompletable()
返回一個Completable,它丟棄Single的結果,並在此Single調用onSuccess
時調用onComplete
。
因此,上面的代碼將返回Completable,並且1s後調用onComplete()
。
現在,我們應該更改我們的GitRepoRepository。讓我們回顧一下邏輯。我們檢查互聯網連接。如果有互聯網連接,我們從遠程數據源獲取數據,將其保存在本地數據源中並返回數據。否則,我們僅從本地數據源獲取數據。看一看:
fun getRepositories(): Observable<ArrayList<Repository>> {
netManager.isConnectedToInternet?.let {
if (it) {
return remoteDataSource.getRepositories().flatMap {
return@flatMap localDataSource.saveRepositories(it)
.toSingleDefault(it)
.toObservable()
}
}
}
return localDataSource.getRepositories()
}
使用了.flatMap
,一旦remoteDataSource.getRepositories()
發射數據,該項目將被映射到發出相同項目的新Observable。我們從Completable創建的新Observable發射的相同項目保存在本地數據存儲中,並且將其轉換爲發出相同發射項的Single。因爲我們需要返回Observable,所以我們必須將Single轉換爲Observable。
很瘋狂,huh? 想象一下RxJava還能爲我們做些啥!
RxJava是一個非常有用的工具,去使用它,探索它,我相信你會愛上它的!
以上就是本文得全部內容,下一篇文章將是本系列的最後一篇文章,敬請期待!
文章首發於公衆號:
「 技術最TOP 」
,每天都有乾貨文章持續更新,可以微信搜索「 技術最TOP 」
第一時間閱讀,回覆【思維導圖】【面試】【簡歷】有我準備一些Android進階路線、面試指導和簡歷模板送給你