初識Android的ReactiveX
開發一個復雜一點的Android應用都會用到網絡請求,交互和動畫。這些都意味着
要寫很多的回調嵌套。這樣的代碼也被稱為callback hell(回調地獄)。這樣的
代碼不僅長,很難理解,而且也是錯誤高發的地方。ReactiveX
提供了一個清晰、准確處理異步問題和事件的方法。
RxJava是一個ReactiveX在JVM上的實現,由NetFlix開發。這個庫在Java開發者中
廣為流傳。這個教程中你會學到如何在Android應用開發中使用RxJava。這里Android中的RxJava
可以簡稱為RxAndroid。
1. 配置RxAndroid
要在android中使用RxAndroid,需要在build.gradle里添加compile依賴項。
compile 'io.reactivex:rxjava:1.1.1'
2. Oberser和Observable的基礎
在使用RxJava的時候,你會經常遇到Observer和Observable的概念。
你可以把Observable理解為數據的生產者,而Observer則可以理解為數據的消費者。在RxJava里
數據的生產者都是Observable類的實例,消費者都是接口Observer的實例。
類Observable有很多靜態方法,稱為operator。來創建類Observable的實例。以下
代碼演示了如何使用方法just方法(這就是上文說到的operator)來穿件一個非常簡單
的實例,並提供一個String數據:
Observable<String> theObservable = Observable.just("hello world!");
我們剛剛創建的實例會在用有至少一個觀察者的時候發出數據。要創建一個觀察者,就需要創建
一個實現了接口Observer的類。接口Observer里的方法可以處理從Observable實例發出的通知。
下面的觀察者可以把Observable實例發出的數據打印出來。
private final static String TAG = RxJavaActivity.class.getSimpleName();
Observer<String> theObserver = new Observer<String>() {
@Override
public void onCompleted() {
Log.d(TAG, "completed");
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "error");
}
@Override
public void onNext(String s) {
Log.d(TAG, "data:- " + s);
}
};
- 在Observable實例沒有數據在發出的時候調用。
- 在Observable實例遇到錯誤的時候調用。
- 在Observable實例每次發出數據的時候調用。
要給觀察者指定一個observable的實例,我們需要調用subscribe方法。這個方法會返回一個Subscription
實例。下面的代碼讓theObserver觀察者觀察theObservable。
Subscription theSubscription = theObservable.subscribe(theObserver);
當觀察者添加到了observable實例中,observable實例就發出數據。因此,如果運行上面的代碼你會
看到hello world!被打印出來。具體如下:
okhttp.demo.com.okhttpdemo D/RxJavaActivity: data:- hello world!
okhttp.demo.com.okhttpdemo D/RxJavaActivity: completed
一般來說方法onCompleted和onError不會用到,不過某些特殊情況也會用到,不過這里不多敘述。
所以,有更加簡潔的可以用到Action1接口,這個接口只有一個方法。
Action1<String> theAction = new Action1<String>() {
@Override
public void call(String s) {
Log.d(TAG, "action1 data:- " + s);
}
};
Subscription theSubscription = theObservable.subscribe(theAction);
這樣提交了一個接口action1的實例之后,Observable的實例就發出數據。
要從observable中去除一個觀察者只需要在Subscription實例上調用方法unsubscribe。
theSubscription.unsubscribe();
3. 使用Operator
現在你已經知道如何創建觀察者和observable(可觀察對象)。下面來看看如何使用RxJava的operator(操作符)
來創建、轉換observable的,當然還有其他的一些操作。現在創建一個復雜一點的Observable對象。
這個對象發出一個Integer數組數據。方法from可以這個功能。
Observable<Integer> listObservable = Observable.from(new Integer[]{1, 2, 3, 4, 5});
listObservable.subscribe(new Action1<Integer>() {
@Override
public void call(Integer integer) {
Log.d(TAG, "data:- " + String.valueOf(integer));
}
});
運行這段代碼你會發現數組中的數據一個接一個的打印了出來。
okhttp.demo.com.okhttpdemo D/RxJavaActivity: data:- 1
okhttp.demo.com.okhttpdemo D/RxJavaActivity: data:- 2
okhttp.demo.com.okhttpdemo D/RxJavaActivity: data:- 3
okhttp.demo.com.okhttpdemo D/RxJavaActivity: data:- 4
okhttp.demo.com.okhttpdemo D/RxJavaActivity: data:- 5
如果你熟悉javascript或者kotlin, 你就會對map和filter方法如何在數組中使用有一定的認識。
RxJava也有類似的operator來處理observable(可觀察對象)。由於java 7沒有lambda表達式,我們需要
模擬一下這個lambda表達式。要模擬只接受一個參數的lambda表達式只要創建一個實現了接口Func1的實例。
這里你可以用map方法來遍歷listObservable的每一個元素。這個例子會遍歷observable每個整數並輸出這個數字的平方值。
listObservable.map(new Func1<Integer, Integer>() { // 1
@Override
public Integer call(Integer integer) {
return integer * integer; // 2
}
});
- 輸入和輸出的值都是
Integer類型的。 - 輸出數字的平方值。
這里有一點需要注意的地方。map方法返回的是一個新的Observable對象,這個方法本身並不改變
原本的Observable的實例。如果給listObsevable添加了觀察者,就會返回這些數字的平方值。
Operator(操作符)可以成鏈式調用。比如,以下代碼使用了skip方法跳過前兩個數字,並使用filter方法
來忽略奇數:
// skip & filter
listObservable
.skip(2) // 1
.filter(new Func1<Integer, Boolean>(){
@Override
public Boolean call(Integer integer) {
return integer % 2 == 0; // 2
}
});
輸出:
okhttp.demo.com.okhttpdemo D/RxJavaActivity: skip & filter data:- 4
4. 該處理異步問題了
我們前面創建的observer(觀察者)和observable(可觀察對象)都是在單個的線程中運行的,都在Android的UI線程中。
這里,我們要用RxJava來處理一些多線程的問題。同時,演示解決callback hell(回調地獄)的問題。
假設我們有一個叫做fetchData的方法,這個方法被用來從API上獲取數據。這個方法接受一個URL字符串
為參數,並返回一個字符串。這個方法可以這么使用:
String content = fetchData("https://api.github.com/orgs/octokit/repos");
如果你執行命令curl -i https://api.github.com/orgs/octokit/repos是會返回一個json字符串的。
是的,這個URL是github的API的一個例子。在這里用正合適。
在Android里,網絡請求不能在UI線程中,只能另外開辟一個線程或者使用AsyncTask。有了RxJava之后
你就多了一個選項。是用subscribeOn和observeOn操作符(operator),你可以顯示的指出后台任務在哪個線程
運行,更新界面的任務在哪個線程(當然這必須是在UI線程)。
不過在繼續往下之前,我們需要把RxAndroid的擴展添加到項目依賴里。這個庫是RxJava在Android上的擴展。
compile 'io.reactivex:rxandroid:1.1.0'
下面的代碼演示如何使用create操作符(operator)創建一個Observable對象。使用這個方法創建的
Observable對象必須實現Observable.OnSubscribe接口,並調用onNext, onError和onComplete
來控制發出什么數據等操作。
Observable<String> asyncObservable = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
String content = fetchData("https://api.github.com/orgs/octokit/repos");
subscriber.onStart();
// 1
subscriber.onNext(content);
// 2
subscriber.onCompleted();
} catch (Exception e) {
// 3
subscriber.onError(e);
}
}
});
- 把方法
fetchData獲取的數據發出去。 - 這里沒有什么要做的。
- 如果出現了錯誤,那么就發出錯誤信息。
然后就可以給這個asyncObservable, Observable對象來調用subscribeOn和observeOn兩個方法來指定執行的
線程。
Observable<String> asyncObservable = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
String content = fetchData("https://api.github.com/orgs/octokit/repos");
subscriber.onStart();
subscriber.onNext(content);
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
});
asyncObservable
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Log.d(TAG, "async data:- " + s);
Toast.makeText(RxJavaActivity.this, "async data:- " + s, Toast.LENGTH_SHORT).show();
}
});
你也許覺得這個沒見的比AsyncTask或者Thread+Handler的組合好。是的,如果你只是需要簡單的需要一個
后台運行的線程,不用RxJava也可以。
那么我們考慮一個復雜一點的場景,你需要從兩個或以上不同的API並行的獲取數據,並且只有在這些API的數據全部都返回回來之后才
才更新一個view。如果你使用傳統的方式來處理,你需要些很多不必要的代碼以確保這些請求都完成而且沒有什么錯誤。
再考慮另外一個場景,一個后台任務需要在前一個后台任務成功執行之后開始執行。如果用傳統的方法,這
將會走進回調地獄(callback hell)。
使用RxJava的operator(操作符),兩個場景都可以優雅的解決。比如,如果用fetchData方法從兩個不同的站點獲取數據,假設這兩個站點是
Yahoo和Google。這就需要創建兩個Observable對象,並使用subscribeOn方法讓他們裕興在不同的線程上。
yahooObservable.subscribeOn(Schedulers.newThread());
googleObservable.subscribeOn(Schedulers.newThread());
要處理第一個場景,兩個請求需要並行的運行。我們可以使用zip操作符(operator)然后添加觀察者。
yahooObservable.subscribeOn(Schedulers.newThread());
googleObservable.subscribeOn(Schedulers.newThread());
Observable<String> zippedObservable = Observable.zip(yahooObservable, googleObservable, new Func2<String, String, String>() {
@Override
public String call(String yahooString, String appleString) {
String result = yahooString + "\n" + appleString;
Log.d(TAG, result);
return result;
}
});
要處理后面一種場景的問題,可以使用方法concat操作符(operator)來運行有依賴關系的兩個線程。
Observable.concat(yahooObservable, googleObservable)
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Log.d(TAG, "concat data: " + s);
Toast.makeText(RxJavaActivity.this, "concat data: " + s, Toast.LENGTH_SHORT).show();
}
});
5. 處理事件
RxAndroid有一個類叫做ViewObservable。專門用來方便處理view對象的各種相關事件。下面的
代碼將演示如何使用ViewObservable對象來處理Button的點擊事件。
由於RxAndroid的改動,你需要給項目的依賴項里添加新的庫:
compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'
Button eventButton = (Button) findViewById(R.id.event_button);
RxView.clicks(eventButton).subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
Toast.makeText(RxJavaActivity.this, "Clicked", Toast.LENGTH_SHORT).show();
}
});
RxView.clicks(eventButton)返回一個Observable的對象。我們可以給這個對象調用各種前文
學到的操作符(operator)。比如,我們要按鈕跳過前三次點擊,可以這么做:
RxView.clicks(eventButton)
.skip(3)
.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
Toast.makeText(RxJavaActivity.this, "Clicked", Toast.LENGTH_SHORT).show();
}
});
最后
這里用到了RxJava的觀察者Observer、Observable還有相關的操作符(operator)來處理異步操作和
事件。使用Rxjava會涉及到函數式編程、響應式編程等一些Android開發者幾乎不會涉及到的編程模式。
第一次接觸難免會遇到一些困難,這都無所謂。只要繼續學習相關內容,都會習以為常。
原文中的內容很多已經不再可用。不過這里已經補齊了各種庫修改之后的依賴項並按照修改之后的API重寫了全部相關的示例代碼。
相關代碼都在這里。
