rxKotlin 響應(yīng)式編程

前言

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)的例子說明。

按鈕點(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)行截圖

運(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())
                }
測(cè)試結(jié)果

結(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上完成操作洪橘。
我將subsribeOnobserveOn的作用域簡單的標(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 , 不管是看到第一段就直接跳到這兒的六剥,還是中途睡著的無意間手抖滑到這兒的...
↙?↙?↙?左下角 ?? 謝謝
????????????????

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末晚顷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子疗疟,更是在濱河造成了極大的恐慌该默,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件策彤,死亡現(xiàn)場(chǎng)離奇詭異栓袖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)店诗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門裹刮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人庞瘸,你說我怎么就攤上這事捧弃。” “怎么了擦囊?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵违霞,是天一觀的道長。 經(jīng)常有香客問我瞬场,道長买鸽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任贯被,我火速辦了婚禮眼五,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘彤灶。我一直安慰自己看幼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布幌陕。 她就那樣靜靜地躺著桌吃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苞轿。 梳的紋絲不亂的頭發(fā)上茅诱,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音搬卒,去河邊找鬼瑟俭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛契邀,可吹牛的內(nèi)容都是我干的摆寄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼坯门,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼微饥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起古戴,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤欠橘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后现恼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肃续,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年叉袍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了始锚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喳逛,死狀恐怖瞧捌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情润文,我是刑警寧澤姐呐,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站转唉,受9級(jí)特大地震影響皮钠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赠法,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一麦轰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砖织,春花似錦款侵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至眶熬,卻和暖如春妹笆,著一層夾襖步出監(jiān)牢的瞬間块请,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來泰國打工拳缠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留墩新,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓窟坐,卻偏偏與公主長得像佣盒,于是被迫代替她去往敵國和親璃弄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容