寫在前面
RxJava我一直是很想用的,扔物線老師的文章我也看了一點,但是說實話,其中很多東西交錯在一起,對於我來說有點難以理解。而且看很多文章總是看了前面忘後面,還有一些結合lambda講的,說實話,我是懵逼的。在這裡把我自己對於RxJava的一些理解,看到的一些好文記錄下來。
RxJava是啥
Rx是一個函式庫,讓開發者可以利用可觀察序列和LINQ風格查詢操作符來編寫非同步和基於事件的程式。
好,對於C#不怎麼了解的人一般不會知道LINQ是啥東東吧……這個介紹我們先選擇略過。看看github上RxJava是怎樣描述自己的。
** a library for composing asynchronous and event-based programs by using observable sequences. ** 一個在Java VM上使用可觀測的序列來組成非同步的、基於事件的程式的庫。
這句話對於還未用過、看過RxJava的人來說是比較難理解的,好在關於RxJava的資料非常多,我們可以站在巨人的肩膀上來總結。首先扔物線對於RxJava給出的關鍵詞就是** 非同步 **,歸根到底它就是一個實現非同步操作的庫。而回過頭來,再看一遍這個定義,我們可以看出另外兩個關鍵詞:可觀測的序列、基於事件,你可能會說這不廢話嗎,這句話一共才幾個詞,都快給我說完了。沒錯,因為這句話概括的非常精準,讓人難以再精簡了。
為啥要用RxJava
Android中實現非同步的工具還是有的,那麼問題來了,對於我們Android開發者來說,為什麼要用RxJava而不是本來的工具?
Talk is cheap,下面選取部分我以前寫的程式碼,用AsyncTask實現的載入資料的類:
class DownloadTask extends AsyncTask<String, Integer, ArrayList<ImageBean>> {
private ObjectOutputStream oos;
@Override
protected ArrayList<ImageBean> doInBackground(final String... params) {
try {
String imageUrl = params[0];
HttpUtils.getJsonString(imageUrl, new HttpUtils.HttpCallbackListener() {
@Override
public void onFinish(String response) {
if (JsonUtils.readJsonImageBean(response) != null) {
imageList = JsonUtils.readJsonImageBean(response);
// memoryCache.addArrayListToMemory("imageList", imageList);
if (count == 0) {
//序列化imageList
if (getActivity() != null) {
File imageCache = FileUtils.getDisCacheDir(getActivity(), "ImageBean");
try {
oos = new ObjectOutputStream(new FileOutputStream(imageCache));
oos.writeObject(imageList);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
count++;
}
}
}
@Override
public void onError(Exception e) {
e.printStackTrace();
}
});
return imageList;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
複製程式碼
上面那段程式碼表示先從檔案中讀取list,然後再從網路獲取資料(初學時的程式碼,沒有考慮好一些邏輯關係)。當我打算重構程式碼,看到這一段的時候,我的內心是崩潰的。雖然邏輯並不複雜,但是這些迷之縮排實在是看的蛋疼。那麼如果我用RxJava重寫一下上面的邏輯,會是怎樣的呢?
Observable.just(0, 1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(new Func1<Integer, String>() {
@Override
public String call(Integer integer) {
if (integer == TYPE_NETWORK) {
return getUrl(pageIndex, type);
}
return "cache";
}
}).subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(String s) {
if (s.equals("cache")) {
//載入快取
} else {
//從網路獲取資料
onCompleted();
};
}
}
});
複製程式碼
以上程式碼實現的非常不科學,非常的不RxJava,但是在這裡僅僅是作為一個示例,讓你感受一下RxJava的特性:簡潔。你可能會說這哪裡簡潔了啊?程式碼量不跟以前差不多嗎,是的,甚至有的時候程式碼量還會增加一點,但是這樣的程式碼能讓你感覺到清晰的邏輯。一切邏輯都在鏈子裡了,而且如果你使用lambda會得到更加簡潔的程式碼。。
這就是我們要用RxJava的原因之一了:** 簡潔 **
這裡的簡潔不是指程式碼量的少,而是指程式碼邏輯的簡潔。而且這種優勢隨著邏輯的複雜而更加明顯。在這裡我並不會將lambda和RxJava結合在一起,一是因為自己的確不熟,二也是因為自己初接觸RxJava,對於我這種入門級選手還是要先排除一些干擾項
RxJava核心&基礎
在開始擼RxJava的程式碼之前,我們首先要弄清楚RxJava中的三個基本也是核心的概念:觀察者(Observer)、訂閱(Subscribe())和被觀察者(Observable)。熟悉設計模式的你可能會立刻想到,這不就是觀察者模式嗎。是的,就是觀察者模式。
觀察者模式定義了物件間一種一對多的依賴關係,每當一個物件狀態發生改變,所有依賴於它的物件都會得到通知並被自動更新。在Android中比較經典的例子有Button的點選,只有當Button被點選的時候,觀察者OnClickListener在Button的點選狀態發生改變時將點選事件傳送給註冊的OnClickListener。而對於RxJava來說也是如此,接下來我將換一種我喜歡的描述來講解我所理解的RxJava。RxJava中Observable是發射資料的源,無論他是“熱”啟動還是“冷”啟動,總之,他最終都是用來發射資料的。Observer則是資料接收者,而Observer和Observable則通過subscribe()(訂閱)結合在一起,從而達到Observer接收Observable發射的資料的目的。
在講解完了RxJava的核心之後,還需要注意一些細節: 在RxJava的文件中指出,無論哪種語言,你的觀察者(Observer)需要實現以下方法的子集:
-
onNext(T item) Observable呼叫這個方法發射資料,方法的引數就是Observable發射的資料,這個方法呼叫次數取決於實現。
-
onError(Exception e) Observable遇到錯誤時會呼叫這個方法,這個呼叫會終止Observable,onError和以下將要介紹的onComplete是互斥的,即同一個事件序列中二者只能有一個被呼叫。
-
onComplete() 正常終止
好了,煩人的概念時間終於過去了,讓我們開始愉悅的Hello World時間!
Hello World!
打碼之前記得加上依賴:
compile 'io.reactivex:rxjava:1.0.14' compile 'io.reactivex:rxandroid:1.0.1'
我這的依賴好像還是看扔物線文的時候新增的……應該比較老了……
話不多說直接上碼:
//建立被觀察者
Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
//回撥
subscriber.onNext("Hello");
subscriber.onNext("World");
subscriber.onNext("!");
}
}).subscribe(new Subscriber<String>() {//被訂閱
@Override
public void onCompleted() {
Log.e(TAG, "onCompleted");
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError");
}
@Override
public void onNext(String s) {
Log.e(TAG, s);
}
});
複製程式碼
剛接觸到這一坨程式碼你可能會說臥槽這什麼東西,大兄弟先別忙著走,我那麼寫只是為了把RxJava鏈式呼叫的特點展現在你面前,接下來讓我們從Observeable和Observer的建立開始。
- 建立Observable 建立Observable的方式非常的多,先介紹一下非常基本的** Create **
Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello");
subscriber.onNext("World");
subscriber.onNext("!");
}
})
複製程式碼
通過create()方法可以建立一個Observable物件我們是知道了,那麼create()方法中的引數是什麼呢?
/**
* Invoked when Observable.subscribe is called.
*/
public interface OnSubscribe<T> extends Action1<Subscriber<? super T>> {
// cover for generics insanity
}
複製程式碼
這個引數是個介面,那麼很明顯了——這個引數是用來幹回撥這事的,發射資料的時候將會用這個介面的實現類通過這個引數發射資料。而call方法則來源於其父介面Action1。call這個方法給出了一個subscriber引數,讓我們看一下這個subscriber究竟是誰。
可能你會說這不廢話嗎……閉著眼我都能知道這是炮姐……繼續我們的話題,在這個類實現的介面裡我們發現了一個看起來非常熟悉的東西** Observer **是了,這個subscriber就是一個訂閱者,一個訂閱者加強版。他相對於Observer主要有兩點區別,一個onStart()會在訂閱開始,事件傳送前執行,所以你可以在這個方法裡做一些準備工作。另一點是實現的另外一個介面提供的unsubscribe(),取消訂閱。據扔物線的文章說,不取消訂閱可能會有記憶體洩露的風險,關於這一點很容易理解,非同步可能會由於生命週期長短問題引發記憶體洩漏,在這裡就不多加贅述了。
- 建立Observer 看完了Observable的建立,我們再來看一下Observer的建立
Observer<String> observer = new Observer<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String s) {
Log.e(TAG,s);
}
};
複製程式碼
按照老樣子,點進Observer發現這個也是個介面,那麼我們在使用多型建立這個方法的時候必須要實現他的三個方法。
- 訂閱
observable.subscribe(observer);
複製程式碼
上面的程式碼看起來像是observable訂閱了observer,但事實上這種設計是出於流式api的考慮,什麼是流式api?看看我的hello world例項程式碼是怎麼寫的,那就是流式api設計的好處。整個程式碼看起來像是一個鏈子,優雅而簡潔。
好了,最基本的介紹完了,你現在可以去嘗試一下你的Hello World了。不過在嘗試之前,我需要糾正我上述Hello World示例程式碼的一個錯誤:RxJava文件中對於Observable的描述有這麼一段話,一個形式正確的Observable必須嘗試呼叫一次onCompleted或者呼叫一次onError方法。很明顯,我的demo是一個使用方法錯誤的例子。此處對Observable和Observer的api介紹非常的少,因為我覺得一次性把文件上的方法全給你搬上來並不明智,一是用不上那麼多,二是容易混淆。
執行緒控制——Scheduler###
終於要到重點了,執行緒控制絕對是RxJava的重點之一。在不指定執行緒的情況下,RxJava遵循的是執行緒不變的原則,在哪個執行緒呼叫subscribe(),就在哪個執行緒生產、消費事件。這對於大部分開發人員來說都是難以接受的事,因為如果是耗時操作可能會阻塞當前執行緒,這是開發者不想看到的,好在我們是可以切換執行緒的。下面同樣是摘自的描述:
-
Schedulers.computation( ):用於計算任務,如事件迴圈或回撥處理,不要用於IO操作,預設執行緒數等於處理器的數量。
-
Schedulers.from(executor):使用指定的Executor作為排程器
-
Schedulers.immediate( ):在當前執行緒立即開始執行任務
-
Schedulers.io( ):用於IO密集型任務,如非同步阻塞IO操作,這個排程器的執行緒池會根據需要增長;Schedulers.io()預設是一個CachedThreadScheduler,很像一個有執行緒快取的新執行緒排程器。
-
Schedulers.newThread( ):為每個任務建立一個新執行緒
-
Schedulers.trampoline( ):當其它排隊的任務完成後,在當前執行緒排隊開始執行
這裡只是初步的瞭解一下,畢竟本文定位是一篇基礎級的文,以下給出一個簡單的載入圖片的例子。
簡單的例子
首先明確我們要乾的事:通過一個url載入一張圖,恩為了演示RxJava和執行緒控制,我用HttpUrlConnection來做一個例項。
Observable.create(new Observable.OnSubscribe<Bitmap>() {
@Override
public void call(Subscriber<? super Bitmap> subscriber) {
try {
URL url = new URL("http://img4.imgtn.bdimg.com/it/u=815679381,647288773&fm=21&gp=0.jpg");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
InputStream in = connection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(in);
subscriber.onNext(bitmap);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
})
.observeOn(AndroidSchedulers.mainThread()) //指定subscriber的回撥發生在UI執行緒
.subscribeOn(Schedulers.newThread()) //指定subscribe()發生在新執行緒
.subscribe(new Subscriber<Bitmap>() {
@Override
public void onCompleted() {
plan.setVisibility(View.GONE);
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(Bitmap bitmap) {
if (bitmap != null) {
image.setImageBitmap(bitmap);
onCompleted();
}
}
});
}
複製程式碼
上面的程式碼寫的很清楚了,我是通過observeOn(AndroidSchedulers.mainThread())指定訂閱者的回撥發生在主執行緒,因為這裡給ImageView設定圖片需要在主執行緒進行,通過subscribeOn(Schedulers.newThread())指定subscribe()發生在新執行緒。
最後請忽略最後幾秒的蜜汁小圓點,因為我不摸螢幕AndroidStudio的錄製就會停留在載入出圖片後的那一段時間,錄製出來的效果非常差。我載入的這張圖是非常小的,我通過限制wifi網速為5k/s來達到“載入”這個目的。
一些反思
本文說的並不深入,只是一篇基礎,看完了這篇可能你只能寫兩個小demo。但是就如我上文所說的,我認為學一個東西,基礎是十分重要的,只要你梳理清楚基礎和關鍵,學習起來無疑是事半功倍的。
我在文章最開頭寫的demo我為什麼要說這很不“RxJava”?因為我只是傳遞了兩個Integer型別的數,之後通過map操作符將這兩個轉換為String,在訂閱的回撥裡處理這兩個字元,並執行相應的邏輯。這給我的感覺就和以前寫程式碼的感覺差不多,沒有一種鏈式呼叫的爽快感,反而有一種強行用RxJava的感覺。那麼RxJava的應用場景和操作符究竟有什麼玄機?我會繼續探索,繼續分享。請期待~
推薦資料
給Android開發者的RxJava詳解:http://gank.io/post/560e15be2dca930e00da1083#toc_14
ReactiveX/RxJava文件中文版:https://mcxiaoke.gitbooks.io/rxdocs/content/
大頭鬼深入淺出RxJava系列:http://blog.csdn.net/lzyzsd/article/details/44094895
iamxiarui探索RxJava系列,也是我比較推薦的入門文,我這同學總結和配圖都是一流的:http://www.jianshu.com/p/856297523728