Android訊息機制(七) Rxjava

weixin_33890499發表於2016-08-15

參考
學習資料彙總
深入淺出RxJava(一:基礎篇)作者:大頭鬼
給 Android 開發者的 RxJava 詳解 作者:扔物線
BaronZhang RxJava系列
可能是東半球最全的RxJava使用場景小結

一、基本概念

RxJava最核心的兩個東西是Observables(被觀察者,事件源)和Subscribers(觀察者)。Observables發出一系列事件,Subscribers處理這些事件。這裡的事件可以是任何你感興趣的東西(觸控事件,web介面呼叫返回的資料。。。)

一個Observable可以發出零個或者多個事件,直到結束或者出錯。每發出一個事件,就會呼叫它的Subscriber的onNext方法,最後呼叫Subscriber.onNext()或者Subscriber.onError()結束。

Rxjava的看起來很想設計模式中的觀察者模式,但是有一點明顯不同,那就是如果一個Observerble沒有任何的的Subscriber,那麼這個Observable是不會發出任何事件的。

Hello World
建立一個Observable物件很簡單,直接呼叫Observable.create即可

Observable<String> myObservable = Observable.create(
    new Observable.OnSubscribe<String>() {
        @Override
        public void call(Subscriber<? super String> sub) {
            sub.onNext("Hello, world!");
            sub.onCompleted();
        }
    }
);

這裡定義的Observable物件僅僅發出一個Hello World字串,然後就結束了。接著我們建立一個Subscriber來處理Observable物件發出的字串。

Subscriber<String> mySubscriber = new Subscriber<String>() {
    @Override
    public void onNext(String s) { System.out.println(s); }

    @Override
    public void onCompleted() { }

    @Override
    public void onError(Throwable e) { }
};

這裡subscriber僅僅就是列印observable發出的字串。通過subscribe函式就可以將我們定義的myObservable物件和mySubscriber物件關聯起來,這樣就完成了subscriber對observable的訂閱。
myObservable.subscribe(mySubscriber);
一旦mySubscriber訂閱了myObservable,myObservable就是呼叫mySubscriber物件的onNext和onComplete方法,mySubscriber就會列印出Hello World!

1.Observable (可觀察者,即被觀察者)
使用create來建立

Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        subscriber.onNext("Hello");
        subscriber.onNext("Hi");
        subscriber.onNext("Aloha");
        subscriber.onCompleted();
    }
});

這裡傳入了一個 OnSubscribe 物件作為引數。OnSubscribe 會被儲存在返回的 Observable 物件中,它的作用相當於一個計劃表,當 Observable 被訂閱的時候,OnSubscribe 的 call() 方法會自動被呼叫,事件序列就會依照設定依次觸發(對於上面的程式碼,就是觀察者Subscriber 將會被呼叫三次 onNext() 和一次 onCompleted())。

create方法寫起來太長,也有等價的簡化寫法just和from:
just(T...): 將傳入的引數依次傳送出來。
from(T[]) / from(Iterable<? extends T>) : 將傳入的陣列或 Iterable 拆分成具體物件後,依次傳送出來。

Observable observable = Observable.just("Hello", "Hi", "Aloha");
// 將會依次呼叫:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();
String[] words = {"Hello", "Hi", "Aloha"};
Observable observable = Observable.from(words);
// 將會依次呼叫:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();

2.Observer (觀察者)
onNext()
onCompleted()
onError()

**3.Observer 的抽象類:Subscriber。 **

Subscriber<String> mySubscriber = new Subscriber<String>() {
    @Override
    public void onNext(String s) { System.out.println(s); }

    @Override
    public void onCompleted() { }

    @Override
    public void onError(Throwable e) { }
};

Subscriber 對 Observer 介面進行了一些擴充套件,但他們的基本使用方式是完全一樣的.實質上,在 RxJava 的 subscribe 過程中,Observer 也總是會先被轉換成一個 Subscriber 再使用。所以如果你只想使用基本功能,選擇 Observer 和 Subscriber 是完全一樣的。它們的區別對於使用者來說主要有兩點:

onStart(): 這是 Subscriber 增加的方法。它會在 subscribe 剛開始,而事件還未傳送之前被呼叫,可以用於做一些準備工作,例如資料的清零或重置。這是一個可選方法,預設情況下它的實現為空。需要注意的是,如果對準備工作的執行緒有要求(例如彈出一個顯示進度的對話方塊,這必須在主執行緒執行), onStart() 就不適用了,因為它總是在 subscribe 所發生的執行緒被呼叫,而不能指定執行緒。要在指定的執行緒來做準備工作,可以使用 doOnSubscribe() 方法,具體可以在後面的文中看到。
unsubscribe(): 這是 Subscriber 所實現的另一個介面 Subscription 的方法,用於取消訂閱。在這個方法被呼叫後,Subscriber 將不再接收事件。一般在這個方法呼叫前,可以使用 isUnsubscribed() 先判斷一下狀態。 unsubscribe() 這個方法很重要,因為在 subscribe() 之後, Observable 會持有 Subscriber 的引用,這個引用如果不能及時被釋放,將有記憶體洩露的風險。所以最好保持一個原則:要在不再使用的時候儘快在合適的地方(例如 onPause() onStop() 等方法中)呼叫 unsubscribe() 來解除引用關係,以避免記憶體洩露的發生。

4.Subscribe方法 (訂閱)
Observable.subscribe(observer);
Observable.subscribe(Subscriber) 的內部實現是這樣的(僅核心程式碼):

// 注意:這不是 subscribe() 的原始碼,而是將原始碼中
//與效能、相容性、擴充套件性有關的程式碼剔除後的核心程式碼。
// 如果需要看原始碼,可以去 RxJava 的 GitHub 倉庫下載。
public Subscription subscribe(Subscriber subscriber) {
    subscriber.onStart();
    onSubscribe.call(subscriber);
    return subscriber;
}

可以看到,subscriber() 做了3件事:

呼叫 Subscriber.onStart() 。這個方法在前面已經介紹過,是一個可選的準備方法。
呼叫 Observable 中的 OnSubscribe.call(Subscriber) 。在這裡,事件傳送的邏輯開始執行。從這也可以看出,在 RxJava 中, Observable 並不是在建立的時候就立即開始傳送事件,而是在它被訂閱的時候,即當 subscribe() 方法執行的時候。
將傳入的 Subscriber 作為 Subscription 返回。這是為了方便 unsubscribe().

5.Action

注:正如前面所提到的,Observer 和 Subscriber 具有相同的角色,而且 Observer 在 subscribe() 過程中最終會被轉換成 Subscriber 物件,因此,從這裡開始,後面的描述我將用 Subscriber 來代替 Observer ,這樣更加嚴謹。

上面提到了使用just,from來簡化next。現在說一下用Action來簡化 Subscriber。

上面的例子中,我們其實並不關心OnComplete和OnError,我們只需要在onNext的時候做一些處理,這時候就可以使用Action1類。

Observable.just("Hello, world!")  
    .subscribe(new Action1<String>() {  
        @Override  
        public void call(String s) {  
              System.out.println(s);  
        }  
    });  

Action系列是沒有返回值的,只有一個Call方法。call方法裡沒有引數就用action0,一個引數就用action1。注意Action中的call方法和observable的create方法中那個一訂閱就執行的call方法是有區別的。

Action0 onCompletedAction = new Action0() {
    // onCompleted()
    @Override
    public void call() {
        Log.d(tag, "completed");
    }
};

6.例子
a. 列印字串陣列
將字串陣列 names中的所有字串依次列印出來:
<pre>
String[] names = ...;
Observable.from(names)
.subscribe(new Action1<String>() {
@Override
public void call(String name) {
Log.d(tag, name);
}
});
</pre>

b. 由 id 取得圖片並顯示
由指定的一個 drawable 檔案 id drawableRes取得圖片,並顯示在 ImageView中,並在出現異常的時候列印 Toast 報錯:
<pre>
int drawableRes = ...;
ImageView imageView = ...;
Observable.create(new OnSubscribe<Drawable>() {
@Override
public void call(Subscriber<? super Drawable> subscriber) {
Drawable drawable = getTheme().getDrawable(drawableRes));
subscriber.onNext(drawable);
subscriber.onCompleted();
}
}).subscribe(new Observer<Drawable>() {
@Override
public void onNext(Drawable drawable) {
imageView.setImageDrawable(drawable);
}

@Override
public void onCompleted() {
}

@Override
public void onError(Throwable e) {
    Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
}

});
</pre>

總結:
根據訂閱者模式,被訂閱者使用事件或回撥,讓那個訂閱者去執行某些事情。Observable在執行subscribe時觸發了自己的call方法,去載入資源,然後在載入完成時去觸發訂閱者的onNext,onCompleted.顯然,這不應該都在一個執行緒,下面看一下切換執行緒。

二、切換執行緒Scheduler

1.subscribeOn(): 指定subscribe()所發生的執行緒,即 Observable.OnSubscribe被啟用時所處的執行緒。或者叫做事件產生的執行緒。
2.observeOn(): 指定Subscriber所執行在的執行緒。或者叫做事件消費的執行緒。
上面的例子可以改為,在IO執行緒載入圖片,在UI執行緒顯示圖片:
<pre>
int drawableRes = ...;
ImageView imageView = ...;
Observable.create(new OnSubscribe<Drawable>() {
@Override
public void call(Subscriber<? super Drawable> subscriber) {
Drawable drawable = getTheme().getDrawable(drawableRes));
subscriber.onNext(drawable);
subscriber.onCompleted();
}
})
.subscribeOn(Schedulers.io()) // 指定 subscribe() 發生在 IO 執行緒
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回撥發生在主執行緒
.subscribe(new Observer<Drawable>() {
@Override
public void onNext(Drawable drawable) {
imageView.setImageDrawable(drawable);
}

@Override
public void onCompleted() {
}

@Override
public void onError(Throwable e) {
    Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
}

});
</pre>

在RxJava 中,Scheduler ——排程器,相當於執行緒控制器,RxJava 通過它來指定每一段程式碼應該執行在什麼樣的執行緒。RxJava 已經內建了幾個 Scheduler ,它們已經適合大多數的使用場景:

  • Schedulers.immediate(): 直接在當前執行緒執行,相當於不指定執行緒。這是預設的 Scheduler。
  • Schedulers.newThread(): 總是啟用新執行緒,並在新執行緒執行操作。
  • Schedulers.io(): I/O 操作(讀寫檔案、讀寫資料庫、網路資訊互動等)所使用的 Scheduler。行為模式和 newThread() 差不多,區別在於 io() 的內部實現是是用一個無數量上限的執行緒池,可以重用空閒的執行緒,因此多數情況下 io() 比 newThread() 更有效率。不要把計算工作放在 io() 中,可以避免建立不必要的執行緒。
  • Schedulers.computation(): 計算所使用的 Scheduler。這個計算指的是 CPU 密集型計算,即不會被 I/O 等操作限制效能的操作,例如圖形的計算。這個 Scheduler 使用的固定的執行緒池,大小為 CPU 核數。不要把 I/O 操作放在 computation() 中,否則 I/O 操作的等待時間會浪費 CPU。
  • 另外, Android 還有一個專用的 AndroidSchedulers.mainThread(),它指定的操作將在 Android 主執行緒執行。
三、變換

為什麼要用變換,或者說哪些地方需要變換?

比如我想在hello world中加上我的簽名,你可能會想到去修改Observable物件:
Observable.just("Hello, world! -Dan").subscribe(s -> System.out.println(s));
如果你能夠改變Observable物件,這當然是可以的,但是如果你不能修改Observable物件呢?比如Observable物件是第三方庫提供的?比如我的Observable物件被多個Subscriber訂閱,但是我只想在對某個訂閱者做修改呢?
那麼在Subscriber中對事件進行修改怎麼樣呢?比如下面的程式碼:
Observable.just("Hello, world!").subscribe(s -> System.out.println(s + " -Dan"));
這種方式仍然不能讓人滿意,因為我希望我的Subscribers越輕量越好,因為我有可能會在mainThread中執行subscriber。另外,根據響應式函式程式設計的概念,Subscribers更應該做的事情是“響應”,響應Observable發出的事件,而不是去修改。如果我能在某些中間步驟中對“Hello World!”進行變換是不是很酷?
<pre>
Observable.just("images/logo.png") // 輸入型別 String
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String filePath) { // 引數型別 String
return getBitmapFromPath(filePath); // 返回型別 Bitmap
}
})
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) { // 引數型別 Bitmap
showBitmap(bitmap);
}
});
</pre>

Func系列和Action系列類似,不過他是要返回引數的。傳入的String型別,返回了bitmap型別,然後提供給subscriber.

再來看另一個例子:
列印出每個學生所需要修的所有課程的名稱
<pre>
Student[] students = ...;
Subscriber<Student> subscriber = new Subscriber<Student>() {
@Override
public void onNext(Student student) {
List<Course> courses = student.getCourses();
for (int i = 0; i < courses.size(); i++) {
Course course = courses.get(i);
Log.d(tag, course.getName());
}
}
...
};
Observable.from(students)
.subscribe(subscriber);
</pre>

如果不想在 Subscriber中使用 for 迴圈,而是希望 Subscriber中直接傳入單個的 Course物件呢(這對於程式碼複用很重要)
<pre>
Student[] students = ...;
Subscriber<Course> subscriber = new Subscriber<Course>() {
@Override
public void onNext(Course course) {
Log.d(tag, course.getName());
}
...
};
Observable.from(students)
.flatMap(new Func1<Student, Observable<Course>>() {
@Override
public Observable<Course> call(Student student) {
return Observable.from(student.getCourses());
}
})
.subscribe(subscriber);
</pre>

flatMap很奇怪,它確實返回了一個Observable,這一點和map不同。map返回的是一個subscriber能用的型別物件,但flatMap返回的Observable居然subscriber也是能用的,連型別都沒有問題,就像用Observable.from直接拆出來的一樣。下面這段話比較費解:
flatMap() 的原理是這樣的:1. 使用傳入的事件物件建立一個 Observable 物件;2. 並不傳送這個 Observable, 而是將它啟用,於是它開始傳送事件;3. 每一個建立出來的 Observable 傳送的事件,都被匯入同一個 Observable ,而這個 Observable 負責將這些事件統一交給 Subscriber 的回撥方法。這三個步驟,把事件拆成了兩級,通過一組新建立的 Observable 將初始的物件『鋪平』之後通過統一路徑分發了下去。而這個『鋪平』就是 flatMap() 所謂的 flat。

四、記憶體洩露

參考
在Android開發中使用RxJava
RxJava 和 RxAndroid 三(生命週期控制和記憶體優化)

我們之前提到,使用AsyncTask的一大缺點就是它可能會造成記憶體洩露。當它們持有的Activity/Fragment的引用沒有正確處理時就會這樣。不幸的是,RxJava並不會自動防止這種情況發生,好在它可以很容易地防止記憶體洩露。Observable.subscribe()方法會返回一個Subscription物件,這個物件僅僅有兩個方法:isSbscribed()與unsubscribe()。你可以在Activity/Fragment的onDestroy方法中呼叫Subscription.isSubscribed()檢測是否這個非同步任務仍在進行。如果它仍在進行,則呼叫unsubscribe()方法來結束任務,從而釋放其中的強引用,防止記憶體洩露。如果你使用了多個Observable與Subscriber,那麼你可以將它們新增到CompositeSubscription中,並呼叫CompositeSubscription.unsubscribe()結束所有的任務。

1、取消訂閱 subscription.unsubscribe() ;
2、執行緒排程
3、rxlifecycle 框架的使用

相關文章