RxJava

什麼是 RxJava?

a library for composing asynchronous and event-based programs by using observable sequences.

從 RxJava Github 的介紹,可以很清楚地知道 RxJava 的用途是什麼。
一個利用 Observable Sequences 來達成「非同步」與「事件導向」設計的套件。
先來簡單描述一下 Observable Sequences、非同步、事件導向,這三個專有名詞。

Observable Sequence

首先要先了解什麼是 Observable Pattern(觀察者模式) ,就像我們平常燒開水一樣,打水壺放上瓦斯爐後,我們就會先去做其它事情,等水滾的時候水壺在叫了,才會再回去火關掉,拿煮好的水去使用。

以下的程式是表達人與水壺的關係,重點在於人並沒有一直去檢查水滾了沒有,而是水滾的時候,水壺會自動跟人產生互動。

這就是 Observable Pattern(觀察者模式)。

class Jug {
    private Human mHuman;

    // 加入要通知的人
    public void add(Human human) {
	    this.mHuman = human;
    }

    // 水滾了
    public void fired() {
        this.mHuman.listened();
    }
}

class Human {
    // 有沒有聽到水滾了
    public void listened() {
        System.out.print("可以洗澡了!!!");
    }
}

Jug jug = new Jub();
Human human = new Human();
jug.add(human);
jug.fired(); // 這時 Human 就知道可以洗澡了!

而 Observable Sequence 的意思就是把我們在寫的程式流程轉化成 Observable Pattern。

非同步

非同步其實是 Observable Pattern 的一個特性,是表示人不用一直在停留在「等水滾,然後去洗澡」的狀態,他可以先去做「別的事情」,等「水滾了」就「可以去洗澡」。
非同步的意思就是「人」跟「水壺」的狀態是處於沒有相關性的情況,可以各自去做自己的事情。

事件導向

事件導向也是 Observable Pattern 的一個特性,因為「人」跟「水壺」的狀態是處於沒有相關性的情況,那到底什麼時候「人」跟「水壺」又開始產生影響了,就是「事件」發生的時候,「水滾了」之後,「人就會聽到」,然後「人就可以去洗澡了」。

為什麼要用 RxJava?

所以 RxJava 就是把你的程式流程做了一些改變,改變成「非同步」與「事件導向」。

那做了這些改變的好處是什麼呢?

當你需要做一些要花一些時間的事情時,不會讓使用者的操作卡在 Loading 的畫面,可以讓使用者先進行其它的操作,當事情作完的時候,再通知使用者讓他能夠進行原本的操作。

其實在使用 RxJava 之前,大家應該都有做過類似的事情,只是使用的方法不一樣。
像是「建立 Thread 來處理事情」,「使用 AsyncTask」,「Handler的postDelayed」,在理論上都是同樣的東西。

那什麼還要使用 RxJava 呢?

懶啊!!!!!

懶得去建立 Thread , 懶得去 new AsyncTask , 還要去 try-catch 抓 exception , 一下要轉到 uiThread , 一下又要轉到 workThread , 煩不煩啊!!!!
更不要說還有不同 AsyncTask 的 doBackground 都跑在同一個 Thread 上,會彼此卡住的同題要處理。

RxJava提供更簡單的方式(?,其實這個看個人),來幫你把這些雜七雜八的事情處理掉。讓你可以更專心地寫你要的功能。

RxJava 怎麼用?

重點就是這個了,好用才要用,不好用就乖乖回去用 Handler / AsyncTask …
在 Github 中 RxJava 提供了很多 method 可以用,但是一般使用上,大部份都是以切換 Thread 跟 轉換資料型態為主,所以會以這個部份做說明。

Hello World!

如果把上面的「水滾了」的程式改成 RxJava 應該會是以下這樣(程式有一點點差異,不過不影響要表達的意思)

Subscriber<String> human = new Subscriber<String>() {
    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onNext(String message) {
        Log.i(TAG, message);
        if (message.contains("水滾了")) {
            Log.i(TAG, "可以洗澡了!!!");
        }
    }
};

Observable<String> jug = Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        subscriber.onNext("60度了");
        subscriber.onNext("70度了");
        subscriber.onNext("80度了");
        subscriber.onNext("90度了");
        subscriber.onNext("100度水滾了!!!!");
    }
});

jug.subscribe(human);

不過以 RxJava 的寫法,通常會建議改成 Chain 的寫法會更簡潔清楚

Observable.just("60度了", "70度了", "80度了", "90度了", "100度水滾了!!!!")
    .subscribe(new Action1<String>() {
        @Override
        public void call(String message) {
            Log.i(TAG, message);
            if (message.contains("水滾了")) {
                Log.i(TAG, "可以洗澡了!!!");
            }
        }
    });

這樣就是「水滾了」的 RxJava 版本了。

切換 Thread

不過剛剛寫好的「水滾了」,其實並沒有「非同步」的特性。

因為「人」與「水壺」都在同一個 Thread 底下工作,所以「水壺」在「滾水」的時候,「人」其實不能去做其它事情。

因此我們需要把「人」與「水壺」放到不同的 Thread 底下工作,才能達到「非同步」的效果。
這也是 RxJava 被人推薦的原因,要做到這件事,只要加入短短 2 行 code 就完成了,如果是使用「AsyncTask」或「Handler」,大概都要重新順一次流程了。

以下是把「水壺」放到 IO Thread,「人」放到 UI Thread 的版本

Observable.just("60度了", "70度了", "80度了", "90度了", "100度水滾了!!!!")
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<String>() {
        @Override
        public void call(String message) {
            Log.i(TAG, message);
            if (message.contains("水滾了")) {
                Log.i(TAG, "可以洗澡了!!!");
            }
        }
    });

轉換資料型態

有時候就是這樣,程式都寫好了,才發現需求有修改,或是文件有錯,本來以為應該是拿到 String ,結果郤是拿到 Integer ,難道程式又要重寫了嗎?

這也是 RxJava 好用的原因,對於修改原本的程式相對好改許多,把每個動作都仔細的切成一個 method ,再像堆積木一樣,積成一個流程。

Observable.just(60, 70, 80, 90, 100)
    .map(new Func1<Integer, String>() {
        @Override
        public String call(Integer temperature) {
            return (temperature == 100) ? "100度水滾了!!!!" : temperature + "度了";
        }
    })
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<String>() {
        @Override
        public void call(String message) {
            Log.i(TAG, message);
            if (message.contains("水滾了")) {
                Log.i(TAG, "可以洗澡了!!!");
            }
        }
    });

過濾資料

再次遇到需求調整,希望80度以上再跟「人」通知就好了!
沒問題,還好我有用 RxJava !

Observable.just(60, 70, 80, 90, 100)
    .filter(new Func1<Integer, Boolean>() {
        @Override
        public Boolean call(Integer integer) {
            return (integer > 80);
        }
    })
    .map(new Func1<Integer, String>() {
        @Override
        public String call(Integer temperature) {
            return (temperature == 100) ? "100度水滾了!!!!" : temperature + "度了";
        }
    })
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<String>() {
        @Override
        public void call(String message) {
            Log.i(TAG, message);
            if (message.contains("水滾了")) {
                Log.i(TAG, "可以洗澡了!!!");
            }
        }
    });

加個 filter 就搞定了,是不是真的還好有用 RxJava … XD

結論

RxJava 的功能還很多,感覺 RxJava 無比強大,好像幾乎可以改變原本寫程式的觀念。
這種感覺,在學 Node.js® 的時候也有過一次,就連 PHP 也開始有 ReactPHP 了。

現在 Realtime 的系統越來越常見了,或許接下來會開始越來越接觸到這種 Reactive 的寫法,自己寫程式的邏輯也要再重新適應一次了。


參考網頁

RxJava Github Project Page
给 Android 开发者的 RxJava 详解
RxJava@Open Android

0 意見:

張貼留言