一 簡介
RxJava是實現異步操作的庫 那麼在有很多異步成熟實現的基礎上 我們爲什麼還要使用RxJava呢?
異步操作很關鍵的一點是程序的簡潔性,因爲在調度過程比較複雜的情況下,異步代碼經常會既難寫也難被讀懂。 Android 創造的 AsyncTask eventBus 和Handler ,其實都是爲了讓異步代碼更加簡潔。RxJava 的優勢也是簡潔,但它的簡潔的與衆不同之處在於,隨着程序邏輯變得越來越複雜,它依然能夠保持簡潔
二 原理
RxJava使用的是擴展的觀察者模式
傳統的觀察者模式:註冊觀察者 當被觀察者發生變化的時候(發生事件) 能夠及時的通知觀察者 觀察者做出自己的反應 也就是 被觀察者-->觀察者-->訂閱 /註冊-->事件 一般傳統的觀察者 只有點擊 和觸摸事件 也就是過程事件處理onNext (也就是 onclick()和onEvent())
擴展的觀察者模式:在傳統觀察者模式的基礎上 添加了完成事件 和錯誤事件處理(onComplete ()和onError()) RxJava不僅將事件進行獨立的處理 還可以將所有事件堪稱一個隊列 進行統一的管理 。
規定: 當沒有onNext事件進入隊列的時候 就觸發onComplete ()
當事件處理過程中發生異常的時候 就出觸發onError() 同時 事件隊列 不允許事件在進行進出 也就是隊列被終止了
具體實現原理在後面
三 實現
RxJava的使用和Builder很像 屬於鏈式編程 介紹幾個之後常見的名詞
Observable:被觀察者,也就是消息的發送者
Observer: 觀察者,消息的接收者
Subscriber:訂閱者,觀察者的另一種表示
Scheduler:調度器,進行線程切換
1.首先 搭建環境 添加Rxjava的依賴庫
compile 'io.reactivex:rxjava:1.2.1'
compile 'io.reactivex:rxandroid:1.2.1'
2.下面我們一起來做一個小demo
2.1 創建一個觀察者 (一般我們在考慮的時候 先找中介 告訴中介我們需要什麼 具體中介怎麼做事情我們是不管的 )
方式一:使用Observer
// 創建觀察者
Observer<String> observable=new Observer<String>() {
@Override
public void onCompleted() {
// 當被觀察者事件對列正常完成 沒有其他事件進入的時候
Log.d(tag, "onCompleted");
}
@Override
public void onError(Throwable e) {
// 當被觀察者隊列中事件執行過程中 出現錯誤的時候
Log.d(tag, "onError");
}
@Override
public void onNext(String s) {
// 事件的執行 就相當於我們在onClick方法中重寫的事件一樣
Log.d(tag, "Item: " + s);
}
};
方式二:使用Subscriber(Observer的子類)
Subscriber<String> subscriber=new Subscriber<String>() {
@Override
public void onCompleted() {
// 當被觀察者事件對列正常完成 沒有其他事件進入的時候
Log.d(tag, "onCompleted");
}
@Override
public void onError(Throwable e) {
// 當被觀察者隊列中事件執行過程中 出現錯誤的時候
Log.d(tag, "onError");
}
@Override
public void onNext(String s) {
// 事件的執行 就相當於我們在onClick方法中重寫的事件一樣
Log.d(tag, "Item: " + s);
}
@Override
public void onStart() {
super.onStart();
}
};
subscriber.unsubscribe();
我們可以看到 對於subscriber 屬於observer的子類(但是一般由observer聲明的訂閱者 也會轉成Subscriber) 相對於父類來說 他添加了onstart方法 該方法在觀察者被調用 但是 onnext()方法還沒有執行前調用 一般在事件沒有發送前 做一些準備工作 比如數據的清零或者重置 該方法並不是抽象方法 但是需要注意的是 該方法一般執行在訂閱所在的線程 並不能制定線程來執行 所以如果是對UI的操作(比如 進度條等操作) 一般不在該方法進行。如果需要指定的線程來做準備工作,可以使用
doOnSubscribe() 方法
兩者的區別:
Subscriber可以調用unSubscribe()方法 取消訂閱
2.2 創建被訂閱者(被觀察者)
// 創建被訂閱者 在傳入參數的時候就可以看出來 是將Observer轉化成對應的子類
Observable<String> observable= Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
// 調用訂閱者的方法
subscriber.onStart();
subscriber.onNext("hello world");
subscriber.onNext("hello world111111");
subscriber.onError(new Throwable("失敗了"));
subscriber.onCompleted();
}
});
}
當Observable被訂閱的時候 就會調用call方法 事件會被依次執行
// 訂閱observable.subscribe(subscriber);
除了上述的create方法 Observable 對象的時候 Rxjava海提供了快速創建的方式
Create — 通過調用觀察者的方法從頭創建一個Observable
Defer — 在觀察者訂閱之前不創建這個Observable,爲每一個觀察者創建一個新的Observable
Empty/Never/Throw — 創建行爲受限的特殊Observable
From — 將其它的對象或數據結構轉換爲Observable
Interval — 創建一個定時發射整數序列的Observable
Just — 將對象或者對象集合轉換爲一個會發射這些對象的Observable
Range — 創建發射指定範圍的整數序列的Observable
Repeat — 創建重複發射特定的數據或數據序列的Observable
Start — 創建發射一個函數的返回值的Observable
Timer — 創建在一個指定的延遲之後發射單個數據的Observable ..
一般根據需要進行選擇
2.3 訂閱
講了這麼多 還沒有講兩者鏈接起來 者怎麼整呢? 使用訂閱方法 講訂閱者何被訂閱者 連接起來 才能實現他們之間的互動
爲了練時編程的方便 將格式定義成了
// 訂閱
observable.subscribe(subscriber); 這麼看來 好像反了 被訂閱者訂閱了訂閱者 但是爲了連式編程 的方便 只能定義成這樣 那麼我們就跑跑吧
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().
10-13 16:34:52.043 6025-6025/? D/MainActivity: Item: hello world
10-13 16:34:52.043 6025-6025/? D/MainActivity: onCompleted
ok
除了完整定義 還有不完整定義
除了 subscribe(Observer) 和 subscribe(Subscriber) ,subscribe() 還支持不完整定義的回調,RxJava 會自動根據定義創建出Subscriber 。形式如下:
Action1<String> onNextAction = new Action1<String>() {
// onNext()
@Override
public void call(String s) {
Log.d(tag, s);
}
};
Action1<Throwable> onErrorAction = new Action1<Throwable>() {
// onError()
@Override
public void call(Throwable throwable) {
// Error handling
}
};
Action0 onCompletedAction = new Action0() {
// onCompleted()
@Override
public void call() {
Log.d(tag, "completed");
}
};
// 自動創建 Subscriber ,並使用 onNextAction 來定義 onNext()
observable.subscribe(onNextAction);
// 自動創建 Subscriber ,並使用 onNextAction 和 onErrorAction 來定義 onNext() 和 onError()
observable.subscribe(onNextAction, onErrorAction);
// 自動創建 Subscriber ,並使用 onNextAction、 onErrorAction 和 onCompletedAction 來定義 onNext()、 onError() 和 onCompleted()
observable.subscribe(onNextAction, onErrorAction, onCompletedAction);
簡單解釋一下這段代碼中出現的 Action1 和 Action0。 Action0 是 RxJava 的一個接口,它只有一個方法 call(),這個方法是無參無返回值的;由於 onCompleted() 方法也是無參無返回值的,因此 Action0 可以被當成一個包裝對象,將 onCompleted() 的內容打包起來將自己作爲一個參數傳入 subscribe() 以實現不完整定義的回調。這樣其實也可以看做將 onCompleted() 方法作爲參數傳進了subscribe(),相當於其他某些語言中的『閉包』。
Action1 也是一個接口,它同樣只有一個方法 call(T param),這個方法也無返回值,但有一個參數;與 Action0 同理,由於 onNext(T obj) 和 onError(Throwable error) 也是單參數無返回值的,因此 Action1可以將 onNext(obj) 和 onError(error) 打包起來傳入 subscribe() 以實現不完整定義的回調。事實上,雖然 Action0 和 Action1在 API 中使用最廣泛,但 RxJava 是提供了多個 ActionX
形式的接口 (例如 Action2, Action3) 的,它們可以被用以包裝不同的無返回值的方法。
就是使用Action 實現 將方法進行包裝 生成不完整的訂閱者
說了一大半天 練時編程呢 讓我們連起來 吧
String[] words={"aa","cc","bb"};
// 將數組轉化成被訂閱者對象 然後註冊 訂閱者 使用不完整定義定義訂閱者對象
Observable.from(words).subscribe(new Action1<String>() {
@Override
public void call(String s) {
Log.d(tag,s);
}
});
跑跑唄
10-13 16:46:51.213 18046-18046/com.example.rxdemo D/MainActivity: aa
10-13 16:46:51.213 18046-18046/com.example.rxdemo D/MainActivity: cc
10-13 16:46:51.213 18046-18046/com.example.rxdemo D/MainActivity: bb
10-13 16:46:51.233 18046-18046/com.example.rxdemo D/Atlas: Validating map...
這個結果真的是運行出來的 不是我自己瞎編的 然而 並沒有什麼卵用
四 異步的實現(線程控制 Scheduler)
4.1 在 RxJava 的默認規則中,事件的發出和消費都是在同一個線程的。也就是說,如果只用上面的方法,實現出來的只是一個同步的觀察者模式。觀察者模式本身的目的就是『後臺處理,前臺回調』的異步機制,因此異步對於 RxJava 是至關重要的。而要實現異步,則需要用到 RxJava 的另一個概念: Scheduler 。
Scheduler 我在前面大概提了一下 是調度器 實現線程的切換 總而言之 就是完成線程的控制 就成了
RxJava的線程原則:
在不制定線程的情況下 RxJava遵循的是線程不變的原則 也就是 訂閱在那個線程 那麼就在那個線程執行 如果需要切換線程 就需要使用Scheduler 進行線程的切換
Scheduler的使用:
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 主線程運行。
有了這幾個 Scheduler ,就可以使用 subscribeOn() 和 observeOn() 兩個方法來對線程進行控制了。
subscribeOn(): 指定 subscribe() 所發生的線程,即 Observable.OnSubscribe 被激活時所處的線程。或者叫做事件產生的線程。
observeOn(): 指定 Subscriber 所運行.observeOn(AndroidSchedulers.mainThread()) // 指定事件的回調發生在主線程的線程。或者叫做事件消費的線程。
/**
* 指定線程運行
*/
private void demo2() {
Observable.just(1,2,3,4) //將整形數據轉化成被訂閱者對象
.subscribeOn(Schedulers.io()) // 註冊線程控制發生在io線程 (使用線程控制 調用io進行輸入輸出)
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回調發生在主線程
.subscribe(new Action1<Integer>() { //創建訂閱者 使用不完整定義(有一個參數 沒有返回值 )
@Override
public void call(Integer integer) {
Log.d("demo2","num="+integer);
}
});
}
結果我就不說了 註釋寫得很清楚
上面這段代碼中,由於 subscribeOn(Schedulers.io()) 的指定,被創建的事件的內容 1、2、3、4 將會在 IO 線程發出;而由於observeOn(AndroidScheculers.mainThread()) 的指定,因此 subscriber 數字的打印將發生在主線程 。事實上,這種在subscribe() 之前寫上兩句 subscribeOn(Scheduler.io()) 和 observeOn(AndroidSchedulers.mainThread()) 的使用方式非常常見,它適用於多數的
『後臺線程取數據,主線程顯示』的程序策略。
不說扯淡的例子了 說個比較實用的例子 我們都知道圖片的加載比較好費時間 有人說了 不是有圖片加載框架嗎 什麼picasso啦 imageLoader了 等等 可以完全ok 自己用着玩吧 但是 底層他們都是需要請求網絡加載圖片的 一般使用的Asynctask進行實現的 那麼我之前也說個 Rxjava最大的用處就是異步 那麼 我們底層完全可以根據Rxjava來實現 來個小例子
Observable.create(new Observable.OnSubscribe<Drawable>() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void call(Subscriber<? super Drawable> subscriber) {
int drawableRes = R.mipmap.ic_launcher;
Drawable drawable = getTheme().getDrawable(drawableRes);
// 設置加載完成前的默認現實圖片
subscriber.onNext(drawable);
// 模擬加載圖片
SystemClock.sleep(2000);
drawable=getTheme().getDrawable(R.mipmap.icon_baoxiudan_wxdd);
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(MainActivity.this, "Error!", Toast.LENGTH_SHORT).show();
}
});
結果 我就不說了 要不會顯得太沒檔次
4.2 變換
--1. map() 將String類型轉化成Bitmap類型的對象然後將對象進行返回 (事務對象直接變換 最常見 也最容易 )
demo:
Observable.just(new File(Environment.getExternalStorageDirectory(),"Pictures/JPEG_20160803_003209.jpg").getAbsolutePath())
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String path) {
return getBitmapFromPath(path);//根據路徑獲取bitMap圖片
}
})
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) {
imageView.setImageBitmap(bitmap); //將圖片顯示在指定的控件中
}
});
關於返回值的問題:
ActionX 是沒有返回值的java的接口實現類 (Action0 沒有參數 Action1有一個參數 我前面已經說過了 不過我自己又忘記了)
FunX 是有返回值的java接口實現類
--2.flatMap()
需求:輸出一個數據中所有學生對應的課程信息
使用map()實現
ArrayList<String> course=new ArrayList<String>();
course.add("1");
course.add("2");
course.add("3");
Student student1=new Student(123,"zhang",18,course);
Student student2=new Student(124,"zhang1",19,course);
student=new Student[2];
student[0]=student1;
student[1]=student2;
Observable.from(student)
.map(new Func1<Student, Student>() {
@Override
public Student call(Student student) {
return student;
}
})
.subscribe(new Subscriber<Student>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Student s) {
// TODO 遍歷循環輸出對應課程
Log.d(tag,"===="+s);
}
});
遍歷 很麻煩 我們就像 能不能不這麼煩 直接傳遞給我課程信息 然後我們直接就輸出了 遍歷幹嘛
鄉親們 不要着急 我們使用flatmap()實現一下
Observable.from(student)
.flatMap(new Func1<Student, Observable<String>>() {
//第一個參數便是傳入的參數類型 第二個參數是想要得到的參數類型 在當前例子中我們需要得到的是各門學科的名稱
@Override
public Observable<String> call(Student student) {
return Observable.from(student.getCourses());
//獲取學科數據 轉化成被訂閱者類型 具體爲什麼能夠直接取出學科中的數據 請看原理
}
})
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String name) {
Log.d(tag,name);
}
});
上面例子使用起來是不是就簡單了 大兄弟 那下面我們看看原理
flatMap() 的原理是這樣的:
1. 使用傳入的事件對象創建一個 Observable 對象;
2. 並不發送這個 Observable, 而是將它激活,於是它開始發送事件;
3. 每一個創建出來的 Observable 發送的事件,都被匯入同一個 Observable ,而這個 Observable 負責將這些事件統一交給 Subscriber 的回調方法。這三個步驟,把事件拆成了兩級,通過一組新創建的 Observable 將初始的對象『鋪平』之後通過統一路徑分發了下去。而這個『鋪平』就是 flatMap() 所謂的 flat。
來點高級的:
一般可以在flatmap中進行異步操作 所以可以進行網絡請求吧 這就涉及到Retrofit了 我們可以使用retrofit+RxJava來代替ok和EventBus
具體代碼我站在下面了 從別的地方站過來的 因爲我還沒學習Retrofit 尷尬了
- networkClient.token() // 返回 Observable<String>,在訂閱時請求 token,並在響應後發送 token
- .flatMap(new Func1<String, Observable<Messages>>() {
- @Override
- public Observable<Messages> call(String token) {
- // 返回 Observable<Messages>,在訂閱時請求消息列表,並在響應後發送請求到的消息列表
- return networkClient.messages();
- }
- })
- .subscribe(new Action1<Messages>() {
- @Override
- public void call(Messages messages) {
- // 處理顯示消息列表
- showMessages(messages);
- }
- });
下面在看一個省事的throttleFirst():
RxView.clicks(imageView)
.throttleFirst(500, TimeUnit.MILLISECONDS)
.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
Log.d(tag,"間隔0.5s點擊有作用 用來過濾點擊的");
}
});
throttleFirst():是用來過濾事件的 在制定間隔時間內的事件時不給予處理的 常用來做都送處理 媽媽再也不用擔心惡作劇的頻繁點擊了
對於事件序列的處理還有很多 在後續的文中我在介紹(但願還有後續)
4.3 變換的原理
在上面的例子中我們都是對事件的序列進行的處理 看似葛優特點 但其實都是對事件序列的管理 也就是重新處理事件 然後在發送
在RxJava中他們都是 通過一個共同的方法實現的 lift(操作) 核心代碼(不用說 又是從別的地方拿來的):
- // 注意:這不是 lift() 的源碼,而是將源碼中與性能、兼容性、擴展性有關的代碼剔除後的核心代碼。
- // 如果需要看源碼,可以去 RxJava 的 GitHub 倉庫下載。
- public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {
- return Observable.create(new OnSubscribe<R>() {
- @Override
- public void call(Subscriber subscriber) {
- Subscriber newSubscriber = operator.call(subscriber);
- newSubscriber.onStart();
- onSubscribe.call(newSubscriber);//這個onSubscribe是新的的Observable的 通知給原來的Observable的Observable
- }
- });
- }
改方法的返回值是Observable 原來我們有一個Observable 但是通過這個方法之後我們就有了兩個Observable 我們成爲 原來的 和新的(能理解吧 不能理解就走吧)
簡單來說:
在新的Observable中 通過opertor.call方法 將創建新的Subscriber(在opertor中包含着用戶定義的變換 那麼這樣就相當於將新的變化插入到新的Subscriber中了),並且和舊的Subscriber建立聯繫 那麼此時 newSubscriber就可以向原來的observable進行訂閱
注意啦注意啦 父老鄉親們:
講述 lift() 的原理只是爲了讓你更好地瞭解 RxJava ,從而可以更好地使用它。然而不管你是否理解了 lift() 的原理,RxJava 都不建議開發者自定義 Operator 來直接使用 lift(),而是建議儘量使用已有的 lift() 包裝方法(如map() flatMap() 等)進行組合來實現需求,因爲直接使用 lift() 非常容易發生一些難以發現的錯誤。
4.4 compose (對 observable整體的轉換)
都是轉換 那麼 lift 和compose的區別是什麼呢
lift:對事件序列的改變
compose:針對 observable本身的轉換
有的鄉親大哥就說了 貌似沒有什麼卵用
那麼我們看一個需求 : 如果我們需要對一個observable進行多個轉換 ? 老張頭說:直接使用多個lift() 不久成了 反正每個的返回值都是Observable 多次轉換沒毛病
如果我們需要對多個observable進行多個相同的轉換? 老李頭說:直接封裝方法 往裏面傳observable不久成了 有瑕疵 這樣好像限制了observable的靈 活性 那怎麼辦 找小舅子好像不靠譜 那麼這個就需要compose上陣了
來個例子:
- public class LiftAllTransformer implements Observable.Transformer<Integer, String> {
- @Override
- public Observable<String> call(Observable<Integer> observable) {
- return observable
- .lift1()
- .lift2()
- .lift3()
- .lift4();
- }
- }
- ...
- Transformer liftAll = new LiftAllTransformer();
- observable1.compose(liftAll).subscribe(subscriber1);
- observable2.compose(liftAll).subscribe(subscriber2);
- observable3.compose(liftAll).subscribe(subscriber3);
- observable4.compose(liftAll).subscribe(subscriber4);
5.線程控制scheduler(調度器)二
在4裏面我們白活了事件序列的變換 什麼map flatmap 以及他們的基本原理 我們還白活了一下 如何制定線程運行 比如圖片加載等 忘了的鄉親們 回頭瞅瞅去 別乾瞪眼
(可以利用 subscribeOn() 結合 observeOn() 來實現線程控制,讓事件的產生和消費發生在不同的線程) 結合之前的map()和 flatmap()我們瞭解了原理 那我們就像能不能產生多次線程的變換呢
observableOn()指定的是subscriber(訂閱者)的線程 那麼當然這個訂閱者 不一定是subscribe()方法調用的註冊者 (通過之前我們講述 lift原理可知 ) 所以observeOn()執行的subscriber並不是Observable所對應的subscriber 而是他的下一級 或者說是我們上面說的新的subscriber 那麼我們就可以這樣說 : 如果需要切換線程 那麼我們只需要調用observeOn()就可以啦
subscribeOn() 和 observeOn() 都做了線程切換的工作。不同的是, subscribeOn()的線程切換髮生在 OnSubscribe 中,即在它通知上一級 OnSubscribe 時,這時事件還沒有開始發送,因此 subscribeOn() 的線程控制可以從事件發出的開端就造成影響;而 observeOn() 的線程切換則發生在它內建的 Subscriber 中,即發生在它即將給下一級Subscriber 發送事件時,因此 observeOn() 控制的是它後面的線程。
5.1擴展
最近項目太忙了 未完待續...