前言
rxKotlin :一個(gè)在JVM上使用可觀測(cè)的序列來組成異步的、基于事件的程序的庫文兑。
我所理解的 rxKotlin 是一個(gè)實(shí)現(xiàn)異步操作的庫盒刚,Android開發(fā)過程中將會(huì)用到很多異步操作,這種響應(yīng)式編程的方式能使程序可讀性提高绿贞,思路清晰因块,使開發(fā)人員能更好地去做代碼維護(hù)。
為什么推薦RxKotlin以及Kotlin語言
Kotlin是由JetBrains公司最新開發(fā)的基于JVM的編程語言籍铁,2017 的Google IO 上Kotlin正式成為Android 開發(fā)的官方語言拒名。
在我們的日常Android開發(fā)過程中吩愧,有太多的業(yè)務(wù)需要用到異步操作,時(shí)而開啟新線程增显,時(shí)而切回主線程糖权。業(yè)務(wù)龐大之后,看著自己之前寫的業(yè)務(wù)代碼炸站,容易一臉懵逼 +_+ 星澳,如果寫了批注估計(jì)能好點(diǎn),要是當(dāng)時(shí)沒寫批注武契,估計(jì)會(huì)瞬間爆炸??募判。
先貼一組我分別用java 和 kotlin 寫的例子: 讀取一組文件夾中png格式的圖片,并在UI界面上進(jìn)行相應(yīng)的處理操作咒唆。
先貼出的是我用java寫的..以前我可能還真會(huì)這么去寫...有時(shí)候?qū)懼鴮懼a就直接飛到屏幕外面去了 O(∩_∩)O
final List<File> folders = new ArrayList<>();
new Thread(){
@Override
public void run() {
super.run();
for (File folder : folders) {
File[] files = folder.listFiles();
for (File file : files) {
if (file.getName().endsWith(".png")) {
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
runOnUiThread(new Runnable() {
@Override
public void run() {
// 處理 bitmap
}
});
}
}
}
}
}.start();
接下來是用rxKotlin寫的相同功能的代碼
Observable.from(folders)
.flatMap {
Observable.from(it.listFiles())
}
.filter {
it.name.endsWith(".png")
}
.map {
BitmapFactory.decodeFile(it.absolutePath)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
// 處理 bitmap
}
腫么樣?是不是感覺瞬間邏輯上就變得清晰了起來释液?(主要是逼格一下子就上去了??)
這就是我為什么推崇kotlin和響應(yīng)式編程的理由全释。可能一個(gè)人學(xué)會(huì)一門技能误债,他可以靠著這項(xiàng)技能吃一輩子的飯浸船,但是,如果他不斷更新或加強(qiáng)這項(xiàng)技能寝蹈,也許他很快就能吃上肉吧李命。
原理解析
1. 核心:觀察者模式
該庫的異步實(shí)現(xiàn),是通過擴(kuò)展的觀察者模式來實(shí)現(xiàn)的箫老。
什么是觀察者模式封字?用一個(gè)通俗點(diǎn)的例子說明。
這里,Button
作為一個(gè)被觀察的對(duì)象阔籽,觀察者OnClickListenner
監(jiān)聽其點(diǎn)擊事件流妻。 當(dāng)按鈕被點(diǎn)擊時(shí),觸發(fā)一個(gè) OnClick
的消息事件笆制,然后傳遞給 OnClickListener
绅这。如果用觀察者模式的方式去定義此次點(diǎn)擊事件的話,我會(huì)這么說: 觀察者 OnClickListener
通過setOnClickListener()
去訂閱了 被觀察者Button
在辆,一旦Button
被點(diǎn)擊证薇,變回觸發(fā)事件 OnClick
。
換言之匆篓,就有了如下的對(duì)應(yīng)關(guān)系:
Button ---> 被觀察者
OnclickListener ---> 觀察者
setOnClickListener() ---> 訂閱
OnClick() ---> 事件
2. 實(shí)現(xiàn)方式
哦差點(diǎn)忘了棕叫,本文中的鏈?zhǔn)讲僮魅?code>map,
filter
之類的api在這里我就不一一介紹了,感興趣的話可以去找一些rxJava的文章看看它們的介紹與用法奕删,一抹多的文章都介紹了它們的基本用法俺泣。
1)創(chuàng)建 observer
創(chuàng)建觀察者observer
,它將決定事件到來的時(shí)候做出什么樣的動(dòng)作完残。
var observer = object : Observer<String> {
override fun onNext(t: String?) {
Log.e("name",t)
}
override fun onError(e: Throwable?) {
Log.e("error",e.toString())
}
override fun onCompleted() {
Log.e("status","completed!!")
}
}
它里面有三個(gè)會(huì)掉方法伏钠,可以自行定義當(dāng)事件到來時(shí),執(zhí)行什么樣的響應(yīng)操作谨设。隨著每一個(gè)事件到來熟掂,正常的話會(huì)在回調(diào)onNext()
中響應(yīng)。若所有事件都執(zhí)行完畢扎拣,則會(huì)調(diào)用onCompleted()
赴肚。
2) 創(chuàng)建Observable
Observable
被觀察者,它將決定觸發(fā)什么事件以及事件觸發(fā)的規(guī)則二蓝。
var observable = Observable.create<String> {
it.onStart()
it.onNext("jiangyu")
it.onNext("jy")
it.onNext("god")
it.onCompleted()
}
這里誉券,我創(chuàng)建了三個(gè)事件,依次是發(fā)送"jiangyu","jy","god"刊愚,這三個(gè)字符串踊跟,然后調(diào)用onCompleted()
作為事件發(fā)送完畢標(biāo)記。
3)Subscribe 訂閱
有了觀察者和被觀察者鸥诽,使用subscribe
訂閱讓二者連接起來商玫。
即:
observable.subscribe(observer)
發(fā)現(xiàn)沒有,這里邏輯上是反的牡借,可能會(huì)和思維上有些出入拳昌。明顯這里的邏輯變成了被觀察者去訂閱觀察者,為什么要和人們的慣性思維背道而馳呢钠龙?下面來講解一下這流式API的工作方式
3. API工作原理
首先放上一張剛才的程序運(yùn)行截圖
仔細(xì)回想一下剛才所創(chuàng)建的觀察者和被觀察者炬藤,看看他們都做了些什么事御铃。我總結(jié)性的概括一下 :1.首先被觀察者定義了發(fā)生的事件以及事件發(fā)生的順序(就是這里的發(fā)送字符串的順序)2.每一個(gè)事件到來時(shí),觀察者對(duì)到來的事件進(jìn)行處理(這里為打印日志)
以上代碼和邏輯分析又可以寫成這樣一個(gè)等價(jià)的代碼段(除了沒有重寫onCompleted
)刻像,更能方便對(duì)于整個(gè)過程的理解:
Observable
.just("jiangyu", "jy", "god")
.subscribe {
Log.e("name",it)
}
這是一種API提供的更加簡潔的寫法畅买,網(wǎng)上對(duì)于這種流式操作有著各種理解,有人說這就像是一個(gè)發(fā)射器细睡,將事件一個(gè)一個(gè)的發(fā)射然后處理谷羞。
為了更進(jìn)一步展示工作原理,我對(duì)代碼做了如下的變形:
Observable
.just("jiangyu", "jy", "god","000")
.filter {
Log.e("start with j", it)
it.startsWith("j")
}
.map {
Log.e("name", it)
it.toUpperCase()
}
.subscribe {
Log.e("after map name", it.toString())
}
結(jié)果表明溜徙,不是說所有元素執(zhí)行完filter
過濾再執(zhí)行map
映射的湃缎,意思是流上的事件或元素都沿著鏈垂直移動(dòng)。這正是印證了之前所說的蠢壹,這就好比發(fā)射器嗓违,將事件一個(gè)一個(gè)發(fā)射出去并依次處理。
線程控制
終于图贸,講了辣磨多觀察者模式的原理蹂季,現(xiàn)在正是進(jìn)入正題!一起來研究rxKotlin響應(yīng)式編程在Andorid中的使用方式疏日。在Android開發(fā)過程中偿洁,最重要的無非就是線程間的切換。因此沟优,掌握Scheduler
調(diào)度器(線程控制器)的工作方式和使用方法涕滋,我認(rèn)為大概率就可以在Android開發(fā)過程中使用rxKotlin掌控雷電了??
先介紹幾個(gè)常用的API自帶的Scheduler:
- Schedulers.newThread(): 總是啟用新線程并執(zhí)行操作
- Schedulers.io(): 主要用于讀寫文件、網(wǎng)絡(luò)請(qǐng)求等挠阁,功能上和newThread()差不多宾肺,但是他是用了無上限線程池,并能夠復(fù)用空閑的線程侵俗。
- AndroidSchedulers.mainThread(): 指定在主線程運(yùn)行
那我們?cè)趺磥韺?duì)線程進(jìn)行控制呢锨用?先來看如下代碼:
@GET("getUsers")
fun getUsers(@Query("token") token: String):Array<User>
val retrofit = Retrofit
.Builder()
.client(OkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
val userService = retrofit.create(UserService::class.java)
Observable.from(userService.getUsers("token"))// io 線程
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.filter {
it.age < 18
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
// 在UI主線程上操作
}
這里結(jié)合了使用Retrofit網(wǎng)絡(luò)框架,舉了一個(gè)線程切換的栗子坡慌,可能這個(gè)栗子有點(diǎn)爛....主要操作就是先獲取用戶信息黔酥,然后過濾得到年齡小于18歲的用戶,最后在UI上完成操作洪橘。
我將subsribeOn
和 observeOn
的作用域簡單的標(biāo)注了一下:
簡單總結(jié)一下的話,可以分為如下三點(diǎn):
- subscribeOn 控制其之前的語句所處線程
- observeOn 控制其之后語句棵帽,到下一個(gè)observeOn之前的語句所處線程
- observeOn 之后如果再添加subscribeOn會(huì)沒有任何作用
線程間的切換游刃有余熄求,是什么樣的強(qiáng)大原理支撐這樣的操作呢?讓我們來慢慢研究逗概。首先來普及并解釋一下我認(rèn)為rxKotlin中比較關(guān)鍵的操作 - 變換弟晚。
map & flatMap
前面的代碼中其實(shí)我已經(jīng)用到很多了,個(gè)人呢認(rèn)為這兩兄弟在rxKotlin中是相當(dāng)重要的。
map:
這里呢我們可以認(rèn)為他相當(dāng)于映射卿城,也有那么點(diǎn)意思在里面枚钓,但是也不完全是吧。我認(rèn)為更好的解釋是 對(duì)于事件對(duì)象的變化操作吧瑟押,即由一個(gè)事件對(duì)象轉(zhuǎn)變?yōu)榱硪粋€(gè)事件對(duì)象搀捷。
Observable
.just(user1, user2, user3)
.map {
it.name
}
.subscribe {
// print 這里打印的是每個(gè)user的名字
}
這很好理解,我傳入三個(gè)user
多望,對(duì)于每一個(gè)user
我通過map
來返回他們對(duì)應(yīng)的名字嫩舟,然后再打印出來。
flatMap:
這個(gè)的概念比較難去理解怀偷,我把它理解為"鋪平"操作家厌。何謂"鋪平"操作呢?再來看一個(gè)栗子??
// User的數(shù)據(jù)結(jié)構(gòu)
data class User(val name: String, val age: Int, val courses : Array<Course>)
Observable
.from(arrayOf(user1,user2,user3))
.flatMap {
Observable.from(it.courses)
}
.filter {
it.classRoom.equals("528")
}
首先還是將三個(gè)user
作為輸入并依次處理椎工,對(duì)于每一個(gè)user
饭于,在flatMap
操作中取出它的courses
,此時(shí)并不發(fā)送它們维蒙,而是激活掰吕。接著再初始化一個(gè)新的Observable
,相當(dāng)于將每一個(gè)user
對(duì)應(yīng)的courses
這一堆課程再次分發(fā)下去木西。 很難理解......用心體會(huì)??
為了更好的去理解這些操作的原理畴栖,先來看看map
的API:
public final <R> Observable<R> map(Func1<? super T, ? extends R> func) {
return lift(new OperatorMap<T, R>(func));
}
map
中傳入的是一個(gè)function
(將T 轉(zhuǎn)換為 R),再通過lift()
方法返回一個(gè)Observable<R>
八千。沒錯(cuò)吗讶,傳入的是一個(gè)函數(shù),這屬于高階函數(shù)恋捆,即傳入的參數(shù)或返回的參數(shù)是一個(gè)函數(shù)照皆。
然后我們?cè)賮砜纯?code>lift()的API:
public final <R> Observable<R> lift(final Operator<? extends R, ? super T> operator) {
return new Observable<R>(new OnSubscribe<R>() {
@Override
public void call(Subscriber<? super R> o) {
try {
Subscriber<? super T> st = hook.onLift(operator).call(o);
try {
// new Subscriber created and being subscribed with so 'onStart' it
st.onStart();
onSubscribe.call(st);
} catch (Throwable e) {
// localized capture of errors rather than it skipping all operators
// and ending up in the try/catch of the subscribe method which then
// prevents onErrorResumeNext and other similar approaches to error handling
Exceptions.throwIfFatal(e);
st.onError(e);
}
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
// if the lift function failed all we can do is pass the error to the final Subscriber
// as we don't have the operator available to us
o.onError(e);
}
}
});
}
很有意思的是他在返回的Observable<R>
中有一個(gè)OnSubscribe<R>
的一個(gè)監(jiān)聽,是對(duì)該Observable<R>
的subscibe
的一個(gè)監(jiān)聽沸停,只有當(dāng)發(fā)生該事件的subscribe時(shí)
膜毁,才會(huì)觸發(fā)上述代碼中call()
方法里面的操作。仔細(xì)回想一下之前我們提到的愤钾,為什么事件的處理順序是一個(gè)垂直的方向進(jìn)行的瘟滨,這就很好的解釋了這個(gè)疑惑點(diǎn)。像map
這樣的中間操作過程能颁,都相當(dāng)于是一個(gè)模板杂瘸,它會(huì)定義一個(gè)事件會(huì)進(jìn)行怎樣的變換操作,但是卻不會(huì)立刻執(zhí)行伙菊,只有等到其監(jiān)聽到subscribe()
方法時(shí)才會(huì)觸發(fā)變換败玉。
線程切換的原理也大同小異敌土,同樣是用到這個(gè)核心的lift()
方法,具體可以自己去參見API中的源碼运翼。
總結(jié)
研究Kotlin語言 和 rxKotlin返干、rxJava 我也處于一個(gè)起步的階段,也算是發(fā)現(xiàn)了這些新鮮東西的一些亮點(diǎn)之處吧血淌,自己研究了一番然后把研究出來的一些東西總結(jié)與分享了出來矩欠。希望可以在以后的項(xiàng)目和學(xué)習(xí)過程中更深入地去理解它的原理。
By the way , 不管是看到第一段就直接跳到這兒的六剥,還是中途睡著的無意間手抖滑到這兒的...
↙?↙?↙?左下角 ?? 謝謝
????????????????