這篇文章是根據Jake Wharton在GOTO CopenHagen 2016上的講話整理的。
下一個版本(2.0)的RxJava還在開發中。雖然observable、訂閱管理和背壓(backpressure)都完全重寫了,但是operator基本沒有任何的變化。在本文中你將學到如何讓你的庫和應用遷移到RxJava 2上,以及如何處理RxJava的兩個版本。
為什么要用響應式編程
為什么響應式編程突然間流行起來。除非你可以一開始就把app定義為同步的模式,否則的話只要有一個異步的處理就會把你習慣的傳統的編程方式打破。“打破”并不是說你的app運行不了,而是說你會面對異步帶來的復雜度增加。
下面的一個例子可以很好的說明這個問題:
interface UserManager {
User getUser();
void setName(String name);
void setAge(int age);
}
UserManager um = new UserManager();
System.out.println(um.getUser());
um.setName("Jane Doe");
System.out.println(um.getUser());
這個簡單的例子里包含一個user對象,且可以調用setter來改變它。如果我們只用同步、單線程的方式來處理我們可以信賴我們要得到的結果:創建一個實例,打印user,修改它的屬性,再次打印user。
當需要用異步的方式來處理的時候。比如,我們要顯示的是server端對user的修改。上例中后面的兩個方法就需要異步處理。我們該如何修改代碼來適應這個改變呢?
你可以做的就是什么都不做。你可以假設異步調用server端的修改是成功的,并且你可以在本地修改user。這樣打印出來的就是server端的修改。很明顯這樣的修改不是好主意。網絡是不穩定的,server端也可能返回一個錯誤,這個時候你就需要在本地處理這些問題了。
我們可以很簡單的處理這個問題。比如,在server端的異步請求成功后調用一個runnable。現在就是響應式編程了。
interface UserManager {
User getUser();
void setName(String name, Runnable callback);A
void setAge(int age, Runnable callback);B
}
UserManager um = new UserManager();
System.out.println(um.getUser());
um.setName("Jane Doe", new Runnable() {
@Override public void run() {
System.out.println(um.getUser());
}
});
然而我們并沒有處理可能發生的問題,比如網絡請求失敗的時候。也許我們創建自己的監聽器,這樣當一個錯誤發生的時候我們就可以處理這個錯誤。
UserManager um = new UserManager();
System.out.println(um.getUser());
um.setName("Jane Doe", new UserManager.Listener() {
@Override public void success() {
System.out.println(um.getUser());
}
@Override public void failure(IOException e) {
// TODO show the error...
}
});
我們可以把錯誤通知給user。我們可以自動重試。這些辦法都可以用,并且這是大多數人處理異步問題的方式。
問題出現的地方是,如果你要處理更多的時候。你需要支持多個調用:比如在app里填寫一個form的時候,需要改變user的多個屬性。或者有多個異步的調用,如一個異步調用成功之后需要調用另外的一個的時候。
public final class UserActivity extends Activity {
private final UserManager um = new UserManager();
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.user);
TextView tv = (TextView) findViewById(R.id.user_name);
tv.setText(um.getUser().toString());
um.setName("Jane Doe", new UserManager.Listener() {
@Override public void success() {
tv.setText(um.getUser().toString());
}
@Override public void failure(IOException e) {
// TODO show the error...
}
});
}
}
有一點需要注意,我們是在Android的范圍內討論問題。所以還有很多其他的問題需要考慮。比如,在我們success
的回調里我們還要把數據傳遞到UI里。但是Android的Activity是有生命周期的,隨時可能被回收。如果這個異步調用返回的時候UI已經銷毀了,你就會遇到問題。
有一些其他的方式來處理上面的問題。我們可以在修改視圖之前檢查一下視圖的狀態。我們也可以創建一個匿名類型,雖然這回短時的造成內存泄漏,因為它要持有Activity的引用。如果Activity已經消失,那么這個回調還是會在background發生。
最后一件事是我們還沒有定義這些回調運行在哪個線程上。也許我們的回調運行在子線程上,那么我們就有責任在主線程上修改UI。
public final class UserActivity extends Activity {
private final UserManager um = new UserManager();
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.user);
TextView tv = (TextView) findViewById(R.id.user_name);
tv.setText(um.getUser().toString());
um.setName("Jane Doe", new UserManager.Listener() {
@Override public void success() {
runOnUiThread(new Runnable() {
@Override public void run() {
if (isDestroyed()) {
tv.setText(um.getUser().toString());
}
}
});
}
@Override public void failure(IOException e) {
// TODO show the error...
}
});
}
}
我們在activity中添加了很多和這段代碼的初衷不相關的其他代碼,比如開始一個異步的任務,并處理這個回調的結果。我們還沒有處理用戶的輸入,處理按鈕點擊和多字段輸入等情況。開始的代碼只是一個情況的說明,當你面對一個真正的app的時候,所有的這些問題是疊加在一起的。
響應式思考
從某種方式來說,app里的任何事都是異步的。我們有網絡請求,發送一個請求,過很久才會返回。我們不能因此而阻塞了UI線程。因此只能在后臺的線程來處理它。我們有文件系統,還包括數據庫的讀寫,甚至于SharedPreferences
也會阻塞主線程。
user也是一個異步的數據。我們在UI里顯示數據,并且UI會用按鈕點擊或者修改輸入框的方式做出響應。這些都是異步方式發生的。我們不能同步的拉去user數據,只能等待數據返回。
很多人認為可以寫一個單線程的App,任何的task都運行在主線程上。但是,UI本身就是異步的。處理時不時接受的異步數據,同時還要處理app的各種不同狀態。做這些的時候還不能阻塞主線程。這就是app更加復雜的根源。
網絡請求
很難找到一個app沒有網絡請求的。你還要處理文件和數據庫,這些也是異步的。最后UI也成為了一個異步的源。所以,Android的每一個方面都是異步的,還是堅持以往的單線程同步的編程方式上最后傷害的就是你自己。
我們應該考慮一種模型,我們的代碼處在中間層,作為各種狀態的仲裁人。而不是理順全部的異步處理。我們可以讓UI直接訂閱數據庫,并在數據發生更改的時候做出相應。我們可以在按鈕點擊的時候來修改網絡請求或者數據庫,而不是收到一個按鈕點擊之后分發點擊事件。
類似的,當網絡請求返回的時候,要是能更新數據就更好了。我們知道當數據更新的時候,UI也跟著自動更新,這樣就可以刪除被動更新界面的代碼。如果Android做某些異步task的時候,比如旋轉,UI可以自動更新就好了,最好是可以自動可以開始一些background任務。
最后,我們就可以刪除很多維護各種狀態的代碼。
RxJava
這就是為什么我們要開發RxJava。而且它也是Android上實際的響應式庫。它也是Android上第一個可以用于響應式編程的庫。RxJava 2支持Android所支持的我全部版本的java。
它主要做三個方面的事:
- 代表了數據源的類集合
- 監聽數據源的類集合
- 修改和組織數據的方法集合
這些數據源只有在開始監聽的時候才開始執行,如果你取消監聽則會同時取消正在執行的任務。網絡請求可以抽象為一個單個的任務,但是一個按鈕點擊流去可以是無限的,只要你的UI在那,即使你只訂閱了按鈕的點擊事件。而且,按鈕的點擊流也可以是空的,也可以是永不停止的。這些都對傳統的觀察者模式造成了沖擊。我們有生成數據的,有一個數據是如何定義的約定,我們想做的只是監聽它。我們想要加一個監聽器,在數據發生改變的時候得到通知。
Flowable于Observable
這兩個主要的類型都會出現在RxJava 2中。這兩個類型都會用于給同樣的類型(可以包含0到n個元素)建模。為什么同一個數據類型需要兩種類型呢?
這是因為背壓(backpressure)這個問題。“我”(作者)并不想闡述太多背壓的內容,雖然它可以讓數據變慢。我們所處的系統只含有有限的資源,但是如果你不能快速的處理背壓的話,它會導致就給你發送數據的數據源變慢。
在RxJava 1中,系統的每一個類型都有背壓,但是在RxJava 2,我們有兩種類型。由于每一個類型都有也要處理的背壓,但是不是所有的數據源都實現了他,所以你最終會遇到crash。這是因為背壓必須處理。在RxJava 2中,你可以知道你所采用的類型系統是否支持背壓。
比如,如果我們有一個touch event作為數據源,這個是我們無法變慢的,我們不能告訴用戶說慢些點,知道我們把上次事件獲得的變化在界面上繪制完成之后再點擊第二次。我們只能用其他的方式來處理,比如disable按鈕或者顯示其他的界面來讓它變慢,但是點擊之后的事件發送并不能變慢。
你可以和數據庫做對比。比如我們要獲得一個大是結果集,我們也許只想在某個時候獲取這個結果集的某些row。一個數據庫可以很好的體現一個問題:它擁有游標的概念。但是touch event流和數據庫的概念不同。因為,并沒有什么方法可以減慢用戶的手指。在RxJava 1中,你可以看到這兩種類型都做為Observable
使用,所以在運行時你要處理背壓的問題,最終會得到一個異常或者你的app崩潰。
Observable<MotionEvent> events
= RxView.touches(paintView);
Observable<Row> rows
= db.createQuery("SELECT * …");
MissingBackpressureException
因為兩個不同的類型,所以他們暴露了背壓。因為他們對同樣的數據類型建模,所以在把數據傳入回調的問題上使用的是同樣的方式。兩個數據源的監聽接口看起來也很接近。第一個方法叫做onNext
,這個方法也是數據發送的方法。只要Observable
后者Flowable
生成的數據不止一個,那么生成一個數據就會調用一次這個方法。這樣你就可以處理每個元素。
Observable<MotionEvent> Flowable<Row>
interface Subscriber<T> {
void onNext(T t);
void onComplete();
void onError(Throwable t);
void onSubscribe(Subscription s);
}
interface Disposable {
void dispose();
}
interface Observer<T> {
void onNext(T t);
void onComplete();
void onError(Throwable t);
void onSubscribe(Disposable d);
}
interface Subscription {
void cancel();
void request(long r);
}
這可以使無窮的。乳溝你監聽了一個按鈕的點擊,那么onNext
方法在每次用戶的點擊下都會被調用。對于有限的數據源來說,我們會有兩個終結事件,一個是complete
,一個是error
, complete表示成功,error表示錯誤。onComplete
和onError
被叫做終結事件是因為這兩個方法被調用后onNext
方法將不會再被調用。有一點不同的地方放就在于方法onSubscribe
。
如果你知道RxJava 1,那么下面將的就是你需要注意的了:當你訂閱了一個Observable或者一個Flowable,那么你就創建了一個資源。而資源都是需要在使用之后回收的。這個onSubscribe
回調會在開始監聽一個Observable或Flowable的時候立刻被調用,并且會根據訂閱的類型返回給你兩個類型中的一個類型。
對于Observable來說,你可以調用dispose
方法,也就是說我已經用完了這個資源,我不需要任何回調了。如果我們有一個網絡請求呢?這會取消網絡請求。如果你監聽了一個按鈕點擊的無窮流,那么調用dispose
方法就是說你不想再接受點擊事件。并且會onSet
視圖的監聽器。這些對于Flowable類型也是一樣的。雖然它有一個不同的名字,使用上也是一樣的。它有一個cancel
方法和dispose
方法的作用是一樣的。不同之處它有另外一個叫做request
的方法,而且這也是背壓在API中現身的地方。這個request方法告訴Flowable類型你需要更多的元素。
響應流
...是一個提供了一個標準的,沒有阻塞背壓的異步流處理措施。
我準備講一下為什么disposable和subscription類型的命名如此不同,為什么他們的方法,一個叫做dispose;一個叫做cancel,而不是一個繼承另一個再加上reqeust方法。原因是有這么一個叫做響應流(reactive stream)的概念。它是其他附屬出現的源頭。
我們會使用subscriber類型和subscription類型作為中間人。這些其實是響應流的一部分,并且這也是為什么他們有不同的名字。
interface Publisher<T> {
void subscribe(Subscriber<? super T> s);
}
interface Subscriber<T> {
void onNext(T t);
void onComplete();
void onError(Throwable t);
void onSubscribe(Subscription s);
}
interface Subscription {
void request(long n);
void cancel();
}
interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
下面有一個例子:
interface UserManager {
User getUser();
void setName(String name);
void setAge(int age);
}
interface UserManager {
Observable<User> getUser();
void setName(String name);
void setAge(int age);
}
現在來看一個例子,里面有user manager的兩個定義。之前我們從類里獲取用戶并顯示在界面上是再不能正確了。現在我們把這個模型考慮成用戶的一個觀察者,一個user對象的數據源。只要user發生改變就會發出通知,接著就可以響應變化并把變化的數據顯示出來。這個時候也不用考慮系統里發生的各種事件里什么時候才是顯示user改變的最佳時機。
RxJava里的observable有三種子集。一個是Single,這個類型包含一個item,或者包含一個error。這個不像是一個流,而更像是單個的異步源。而且它不包含背壓。比如你調用一個方法,返回一個類型的實例或者拋出一個異常。Single也是同樣的概念。你訂閱了一個Single,要不返回一個數據,要不接受到一個error。不同之處在于它是響應式的。
第二個是Completable。他就像是一個返回為void的方法。他會成功完成,或者拋出一個異常。這就好比是一個響應式的Runnable。Completable包含了一組可以運行的代碼,要么成功要么失敗。
最后一個是Maybe,這個類型在RxJava 2里才有。他要么有一個item,errors或者也可能一個item都沒有。它可以被認為是一個optional。一個返回optional值的方法,如果不拋出異常的話它總會返回什么。但是返回的optional值可能有值也可能沒有值。
interface UserManager {
Observable<User> getUser();
void setName(String name);
void setAge(int age);
}
interface UserManager {
Observable<User> getUser();
Completable setName(String name);
Completable setAge(int age);
}
如果setName
方法和setAge
方法的調用是異步的,他們要不成功要么失敗,他們并不真的返回數據。所以completable類型最適合他們。
創建源(Source)
這個例子用來顯示如何創建源,如何把已經存在的響應式源整合到一起。
Flowable.just("Hello");
Flowable.just("Hello", "World");
Observable.just("Hello");
Observable.just("Hello", "World");
Maybe.just("Hello");
Single.just("Hello");
這些類型都有靜態方法可以用來創建。你也可以從單值上創建,也可以從一個數組或者任何可遍歷的類型上創建。但是有兩個方法估計會是最常用的,不管是同步的還是異步的。
OkHttpClient client = // …
Request request = // …
Observable.fromCallable(new Callable<String>() {
@Override public String call() throws Exception {Y
return client.newCall(request).execute();Z
}
});
第一個是fromCallable
.這個方法用來處理只返回一個值的同步行為。這個方法的一大好處是允許你從callable上拋出一個異常。如果有個網絡請求,并且它會拋出一個I/O異常。如果它拋出一個異常的話我們就可以捕獲一個error。如果這個請求成功的話我們就可以從onNext
方法上獲得想要的結果。
Flowable.fromCallable(() -> "Hello");
Observable.fromCallable(() -> "Hello");
Maybe.fromCallable(() -> "Hello");
Maybe.fromAction(() -> System.out.println("Hello"));
Maybe.fromRunnable(() -> System.out.println("Hello"))
Single.fromCallable(() -> "Hello");
Completable.fromCallable(() -> "Ignored!");
Completable.fromAction(() -> System.out.println("Hello"));
Completable.fromRunnable(() -> System.out.println("Hello"));
在全部的五個類型上都可以調用fromCallable
方法。
在Maybe
類型和Completable
類型上有兩個另外的方法會經常用到。這兩個類型都不返回值。只是一個runnable,只是這個runnable是響應式的。
Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> e) throws Exception {
e.onNext("Hello");
e.onComplete();
}
});
創建observable最好用的方法是create
。我們會傳入一個回調,它會在有一個新的subscriber的時候調用。
Observable.create(e -> {
e.onNext("Hello");
e.onNext("World");
e.onComplete();
});
不同于fromCallable
,create
方法可以多次調用onNext
。另外一個好處是我們現在可以對異步數據建模。如果我發出一個http請求,可以調用onNext
方法來完成異步調用。另外的一個好處是你可以處理unscubscribe的情況。
OkHttpClient client = // …
Request request = // …
Observable.create(e -> {
Call call = client.newCall(request);
e.setCancelation(() -> call.cancel());
call.enqueue(new Callback() {
@Override public void onResponse(Response r) throws IOException {
e.onNext(r.body().string());
e.onComplete();
}A
@Override public void onFailure(IOException e) {
e.onError(e);
}
});
});
如果停止監聽http請求的話,那就沒有什么理由繼續執行了。我們就可以添加一個取消的操作來取消http請求并且回收資源。
Flowable.create(e -> { … });
Observable.create(e -> { … });
Maybe.create(e -> { … });
Single.create(e -> { … });
Completable.create(e -> { … });
監聽源
Flowable<String>
interface Subscriber<T> {
void onNext(T t);
void onComplete();
void onError(Throwable t);
void onSubscribe(Subscription s);
}
interface Subscription {
void cancel();
void request(long r);
}
Observable<String>
interface Observer<T> {
void onNext(T t);
void onComplete();
void onError(Throwable t);
void onSubscribe(Disposable d);
}
interface Disposable {
void dispose();
}
當你訂閱一個Observable的時候你不會直接使用這些接口,subscribe
方法調用后開始監聽。那么如何unsubscribe呢?
Observable<String> o = Observable.just("Hello");
o.subscribe(new DisposableObserver<String>() {
@Override public void onNext(String s) { … }
@Override public void onComplete() { … }
@Override public void onError(Throwable t) { … }
});
我們有一個類型叫做DisposableObserver
,這個類型自動處理了很多事,你只需要關心Observable本身。那么應該如何dispose呢?
Observable<String> o = Observable.just("Hello");
o.subscribe(new DisposableObserver<String>() {
@Override public void onNext(String s) { … }
@Override public void onComplete() { … }
@Override public void onError(Throwable t) { … }
});
它實現了disposable
,所以你可以調用dispose方法,并且它會將這個方法沿著調用鏈一直上傳。在RxJava 2有一個新的方法叫做subscribeWith
。它返回一個對象,你可以在它上面調用dispose方法。
Observable<String> o = Observable.just("Hello");
o.subscribeWith(new DisposableObserver<String>() { … });
Maybe<String> m = Maybe.just("Hello");
m.subscribeWith(new DisposableMaybeObserver<String>() { … });
Single<String> s = Single.just("Hello");
s.subscribeWith(new DisposableSingleObserver<String>() { … });
Completable c = Completable.completed();
c.subscribeWith(new DisposableCompletableObserver<String>() { … });
上面的四個類型都是非背壓的。那么對于背壓類型如何處理呢:
Flowable<String> f = Flowable.just("Hello");
Disposable d1 = f.subscribeWith(new DisposableSubscriber<String>() { … });
Observable<String> o = Observable.just("Hello");
Disposable d2 = o.subscribeWith(new DisposableObserver<String>() { … });
Maybe<String> m = Maybe.just("Hello");
Disposable d3 = m.subscribeWith(new DisposableMaybeObserver<String>() { … });
Single<String> s = Single.just("Hello");
Disposable d4 = s.subscribeWith(new DisposableSingleObserver<String>() { … });
Completable c = Completable.completed();
Disposable d5 = c.subscribeWith(new DisposableCompletableObserver<String>() { … });
背壓類型有些不同。你可以這么類比,比如你不會打開一個沒有關閉方法的文件,獲取一個沒有關閉方法的游標。
操作符
操作符做以下三件事:
- 操作或組合數據
- 操作線程
- 操作數據的發射
比如:
Observable<String> greeting = Observable.just("Hello");
Observable<String> yelling = greeting.map(s -> s.toUppercase());
發出一個string,得到一個新的string。
在響應式的世界里,我們有一個Observable并且通過operator來實現一個操作。在這個例子里,map
是一個操作符,我們可以獲取到發射的數據并在上面做某些處理來創建一個新的數據。
Observable<User> user = um.getUser();
Observable<User> mainThreadUser =
user.observeOn(AndroidSchedulers.mainThread());
我想要在另外的一個線程上監聽數據的發射。這樣user數據會在后臺線程里,但是要在主線程上處理user數據。那么observeOn
就是我們需要的操作符了。因為我們改變了線程,那么你應用這些操作符的順序就很關鍵了。
OkHttpClient client = // …
Request request = // …
Observable<Response> response = Observable.fromCallable(() -> {
return client.newCall(request).execute();
});
Observable<Response> backgroundResponse =
response.subscribeOn(Schedulers.io());
如果我們發出了一個網絡請求,而且這個網絡請求會同步的完成。那么,我們肯定不希望它發生在主線程上。我們可以使用操作符來修改我們在哪里訂閱Observable,也就是請求最終在哪里執行。當我們訂閱了后臺的返回,他就會改在后臺線程里執行。I/O只是一個你可以使用的線程池,它會在線程池里執行并最終通知監聽者發生的改變。
OkHttpClient client = // …
Request request = // …
Observable<Response> response = Observable.fromCallable(() -> {
return client.newCall(request).execute();
})
.subscribeOn(Schedulers.io())
.map(response -> response.body().string()) // Ok!
.observeOn(AndroidSchedulers.mainThread());
由于我們在observeOn
后面使用了map
操作符,它就會在Android的主線程上執行。我們不希望在主線程上處理http返回,我們希望處理玩http response之后再返回到主線程上。
其他操作符
還有其他的操作符可以處理Observable并返回一個其他的類型。一個操作符是first()
,它會返回一個list的第一個元素,一個Single類型的對象。在RxJava 1中這個方法會返回一個只發射一個元素的Observable。如果這個Observable為空的話,會返回一個錯誤。因為我們知道一個single要不有一個元素要么就是error。
其他的操作符比如firstElement()
會返回一個Maybe類型的對象。當observable為空的時候,maybe類型的對象可以完成而不會拋出異常。如果你只關心成功或者失敗的話,那么completable類型的對象絕對是你想要的。這些flowable也都存在。
開始使用響應式編程
如果我們想要讓我們最開始的例子變成響應式的,我們可以訂閱user并且說:“我們想要在主線程上得到通知,然后我想要在UI里顯示出來”。任何時候user發生了改變,這個代買就會自動執行,我們可以自動在界面上看到更新。我們不用在擔心管理我們自己了。
// onCreate
disposables.add(um.getUser()
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<User>() {
@Override public void onNext(User user) {
tv.setText(user.toString());
}
@Override public void onComplete() { /* ignored */ }
@Override public void onError(Throwable t) { /* crash or show */ }
}));
// onDestroy
disposables.dispose();
然而,我們一定要記住管理返回的disposable,因為我們在Android的世界里,當Activity消失的時候我們想要這些代碼也停止運行。在onDestroy
方法里,我們dispose這些disposable。
disposables.add(um.setName("Jane Doe")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableCompletableObserver() {
@Override public void onComplete() {
// success! re-enable editing
}
@Override public void onError(Throwable t) {
// retry or show
}
}));
結論
RxJava 2要處理的是Android里的異步問題。無論是網絡請求還是Android本身,一個數據庫或者甚至于是一個事件。并且編寫響應這些源的改變的代碼,而不是編寫應對改變,管理狀態的代碼。
class RxJavaInterop {
static <T> Flowable<T> toV2Flowable(rx.Observable<T> o) { … }
static <T> Observable<T> toV2Observable(rx.Observable<T> o) { … }
static <T> Maybe<T> toV2Maybe(rx.Single<T> s) { … }
static <T> Maybe<T> toV2Maybe(rx.Completable c) { … }
static <T> Single<T> toV2Single(rx.Single<T> s) { … }
static Completable toV2Completable(rx.Completable c) { … }
static <T> rx.Observable<T> toV1Observable(Publisher<T> p) { … }
static <T> rx.Observable<T> toV1Observable(Observable<T> o, …) { … }
static <T> rx.Single<T> toV1Single(Single<T> o) { … }
static <T> rx.Single<T> toV1Single(Maybe<T> m) { … }
static rx.Completable toV1Completable(Completable c) { … }
static rx.Completable toV1Completable(Maybe<T> m) { … }
}
如果你使用的是RxJava 1,有一個interop對象可以使用。你可以在兩個版本的類型之間轉換。
RxJava 2并不是什么新的知識。響應式編程在哪個維度上來說并不算新。Android本身就是一個高度響應式的世界,知識我們被教育成使用一些順序執行的,更加容易描述的方式來編寫代碼。
響應式編程允許我們來用一個更加合理的、異步的方式來對Android開發建模。擁抱源的異步,而不是我們自己去維護各種狀態。讓Android app的開發真正的響應式起來吧。
原文:https://news.realm.io/news/gotocph-jake-wharton-exploring-rxjava2-android/
視頻:https://www.youtube.com/watch?v=htIXKI5gOQU&feature=youtu.be