談?wù)剬?duì)于響應(yīng)式編程RxJava的理解 - 核心思想篇
談?wù)剬?duì)于響應(yīng)式編程RxJava的理解 - 原理篇
對(duì)于RxJava,大家應(yīng)該需要很好的理解其核心思想,或者說你應(yīng)該知道為什么要使用RxJava?使用RxJava的好處是什么?RxJava的使用場(chǎng)景是什么?更簡(jiǎn)單的說,你不知道響應(yīng)式編程是什么?本文將針對(duì)這幾點(diǎn),談?wù)勛约簩?duì)于RxJava的理解。
對(duì)于Rx響應(yīng)式編程的理解:
有一個(gè)起點(diǎn)(Observable)、一個(gè)終點(diǎn)(Observer),事件從起點(diǎn)開始傳遞,無中斷的流向終點(diǎn),在傳遞的過程中,可以對(duì)事件進(jìn)行攔截(攔截可以改變事件的返回值),但終點(diǎn)只關(guān)心它的上一個(gè)攔截。
這也是我們平時(shí)說的所謂的Rx“響應(yīng)式編程”的體現(xiàn)。而鏈?zhǔn)秸{(diào)用(流式調(diào)用)只是RxJava里面的一個(gè)點(diǎn)而已。使用RxJava我們可以更優(yōu)雅的書寫代碼、書寫代碼的整個(gè)思路也會(huì)變的非常的"流暢".
響應(yīng)式編程框架除了RxJava,還有很多,例如RxJs、Rx.net、RxSwift,具體可以看。ReactiveX官網(wǎng)
舉個(gè)例子
比如,現(xiàn)在我們點(diǎn)擊一個(gè)按鈕,從網(wǎng)絡(luò)上下載一張圖片,顯示到ImageView里,
- 不使用RxJava的情況
public void downloadImageAction(View view) {
progressDialog = new ProgressDialog(this);
progressDialog.setTitle("下載圖片中...");
progressDialog.show();
new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL(PATH);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setConnectTimeout(5000);
int responseCode = httpURLConnection.getResponseCode(); // 才開始 request
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = httpURLConnection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
Message message = handler.obtainMessage();
message.obj = bitmap;
handler.sendMessage(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private final Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
Bitmap bitmap = (Bitmap) msg.obj;
image.setImageBitmap(bitmap);
if (progressDialog != null) progressDialog.dismiss();
return false;
}
});
上面代碼很簡(jiǎn)單,就是開一個(gè)線程去下載圖片,下載開始的時(shí)候顯示一個(gè)ProgressDialog,下載完成后通過handler發(fā)送一個(gè)消息到主線程,在主線程里隱藏ProgressDialog,并且顯示圖片。代碼是不是很零散,這里一塊,那里一塊的,如果我們還有增加需求,比如在圖片上加水印,在各個(gè)地方加打印日志,代碼找起來是不是會(huì)比較難找。
- 使用RxJava的情況下:
public void rxJavaDownloadImageAction(View view) {
// 起點(diǎn)
Observable.just(PATH) // 內(nèi)部會(huì)分發(fā) PATH Stirng // TODO 第二步
// TODO 第三步
.map(new Function<String, Bitmap>() {
@Override
public Bitmap apply(String s) throws Exception {
URL url = new URL(PATH);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setConnectTimeout(5000);
int responseCode = httpURLConnection.getResponseCode(); // 才開始 request
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = httpURLConnection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
}
return null;
}
})
//增加圖片水印
.map(new Function<Bitmap, Bitmap>() {
@Override
public Bitmap apply(Bitmap bitmap) throws Exception {
Paint paint = new Paint();
paint.setTextSize(88);
paint.setColor(Color.RED);
return drawTextToBitmap(bitmap, "同學(xué)們大家好",paint, 88 , 88);
}
})
// 日志記錄
.map(new Function<Bitmap, Bitmap>() {
@Override
public Bitmap apply(Bitmap bitmap) throws Exception {
Log.d(TAG, "apply: 是這個(gè)時(shí)候下載了圖片啊:" + System.currentTimeMillis());
return bitmap;
}
})
// .compose(rxud())
.subscribeOn(Schedulers.io()) // 上面 異步
.observeOn(AndroidSchedulers.mainThread()) // 給下面切換 主線程
// 訂閱 起點(diǎn) 和 終點(diǎn) 訂閱起來
.subscribe(
// 終點(diǎn)
new Observer<Bitmap>() {
// 訂閱開始
@Override
public void onSubscribe(Disposable d) {
// 預(yù)備 開始 要分發(fā)
// TODO 第一步
progressDialog = new ProgressDialog(DownloadActivity.this);
progressDialog.setTitle("download run");
progressDialog.show();
}
// TODO 第四步
// 拿到事件
@Override
public void onNext(Bitmap bitmap) {
image.setImageBitmap(bitmap);
}
// 錯(cuò)誤事件
@Override
public void onError(Throwable e) {
}
// TODO 第五步
// 完成事件
@Override
public void onComplete() {
if (progressDialog != null)
progressDialog.dismiss();
}
});
}
上面的代碼充分展示了RxJava的鏈?zhǔn)讲恢袛嗾{(diào)用,一開始有一個(gè)Observable的起點(diǎn),然后通過map操作符將path也就是網(wǎng)絡(luò)請(qǐng)求地址傳入,在apply請(qǐng)求網(wǎng)絡(luò)返回一個(gè)Bitmap,因?yàn)榫W(wǎng)絡(luò)是耗時(shí)操作,所以一開始通過subscribeOn(Schedulers.io())切換到異步io線程執(zhí)行網(wǎng)絡(luò)請(qǐng)求,之后再通過observeOn(AndroidSchedulers.mainThread())切換回主線程,在ImageView里顯示Bitmap。如果這時(shí)我們需要增加需求,例如:中間還包括寫日志、增加圖片水印操作,我們只有在這個(gè)鏈?zhǔn)秸{(diào)用的地方增加一些操作符就可以了,而且代碼看起來十分的優(yōu)雅(如果使用lambda表達(dá)式的話,代碼量也會(huì)減少很多)。。
現(xiàn)在結(jié)合流程圖和文章開頭所說的Rx思想,現(xiàn)在相信大家對(duì)于RxJava的核心思想已經(jīng)有自己的直觀理解了。
我們知道在一個(gè)App里使用最多的就是網(wǎng)絡(luò)請(qǐng)求,RxJava和Retrofit的結(jié)合使用,也是現(xiàn)在最常見的網(wǎng)絡(luò)請(qǐng)求方式。下面結(jié)合RxJava的操作符說說RxJava的使用場(chǎng)景。
網(wǎng)絡(luò)嵌套調(diào)用
- 場(chǎng)景一:雙層列表
第一個(gè)列表的網(wǎng)絡(luò)請(qǐng)求是查詢各個(gè)主項(xiàng)目的信息,第二個(gè)列表的網(wǎng)絡(luò)請(qǐng)求是根據(jù)主項(xiàng)目的id查詢其子item的信息。那么我們是不是可以這樣寫
.subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
api.getProject() // 查詢主數(shù)據(jù)
.compose(DownloadActivity.rxud())
.subscribe(new Consumer<ProjectBean>() {
@Override
public void accept(ProjectBean projectBean) throws Exception {
for (ProjectBean.DataBean dataBean : projectBean.getData()) { // 10
// 查詢item數(shù)據(jù)
api.getProjectItem(1, dataBean.getId())
.compose(DownloadActivity.rxud())
.subscribe(new Consumer<ProjectItem>() {
@Override
public void accept(ProjectItem projectItem) throws Exception {
Log.d(TAG, "accept: " + projectItem); // 可以UI操作
}
});
}
}
});
}
});
.compose(DownloadActivity.rxud()),其實(shí)就是封裝了之前線程切換的兩句代碼,便于調(diào)用,可以不要管,其他的代碼大家應(yīng)該一目了然了。但這樣寫,我們現(xiàn)在就兩個(gè)網(wǎng)絡(luò)請(qǐng)求看不出什么問題,但例如我們現(xiàn)在的app是銀行的app的話,我們知道銀行app 的業(yè)務(wù)十分復(fù)雜,一個(gè)業(yè)務(wù)請(qǐng)求可能涉及到5、6個(gè)網(wǎng)絡(luò)請(qǐng)求,如果像上面這樣寫的話,代碼一直往里縮進(jìn),很不優(yōu)雅,這時(shí)flatMap就派上用場(chǎng)了。
flatMap的使用
// 我只給下面 切換 異步
.observeOn(Schedulers.io())
.flatMap(new Function<Object, ObservableSource<ProjectBean>>() {
@Override
public ObservableSource<ProjectBean> apply(Object o) throws Exception {
return api.getProject(); // 主數(shù)據(jù)
}
})
.flatMap(new Function<ProjectBean, ObservableSource<ProjectBean.DataBean>>() {
@Override
public ObservableSource<ProjectBean.DataBean> apply(ProjectBean projectBean) throws Exception {
return Observable.fromIterable(projectBean.getData()); // 我自己搞一個(gè)發(fā)射器 發(fā)多次 10
}
})
.flatMap(new Function<ProjectBean.DataBean, ObservableSource<ProjectItem>>() {
@Override
public ObservableSource<ProjectItem> apply(ProjectBean.DataBean dataBean) throws Exception {
return api.getProjectItem(1, dataBean.getId());
}
})
.observeOn(AndroidSchedulers.mainThread()) // 給下面切換 主線程
.subscribe(new Consumer<ProjectItem>() {
@Override
public void accept(ProjectItem projectItem) throws Exception {
// 如果我要更新UI 會(huì)報(bào)錯(cuò)2 不會(huì)報(bào)錯(cuò)1
Log.d(TAG, "accept: " + projectItem);
}
});
對(duì)于flatMap的理解,網(wǎng)上很多解釋特別拗口,其實(shí)簡(jiǎn)單的說,就是你傳進(jìn)去一個(gè)事件,這個(gè)事件會(huì)返回多個(gè)結(jié)果,這時(shí)候,你就可以用操作符flatMap,它會(huì)把這個(gè)事件返回的多個(gè)結(jié)果一個(gè)個(gè)按順序返回回來,這句話Observable.fromIterable(projectBean.getData());其實(shí)就是幫助我們實(shí)現(xiàn)了事件返回結(jié)果的迭代。
- 場(chǎng)景二:用戶注冊(cè)完直接進(jìn)行登陸操作
doOnNext的使用
MyRetrofit.createRetrofit().create(IReqeustNetwor.class)
.registerAction(new RegisterRequest()) // todo 1.請(qǐng)求服務(wù)器注冊(cè)操作 // todo 2
.subscribeOn(Schedulers.io()) // 給上面 異步
.observeOn(AndroidSchedulers.mainThread()) // 給下面分配主線程
.doOnNext(new Consumer<RegisterResponse>() { // todo 3
@Override
public void accept(RegisterResponse registerResponse) throws Exception {
// todo 2.注冊(cè)完成之后,更新注冊(cè)UI
}
})
// todo 3.馬上去登錄服務(wù)器操作
.observeOn(Schedulers.io()) // 給下面分配了異步線程
.flatMap(new Function<RegisterResponse, ObservableSource<LoginResponse>>() { // todo 4
@Override
public ObservableSource<LoginResponse> apply(RegisterResponse registerResponse) throws Exception {
Observable<LoginResponse> loginResponseObservable = MyRetrofit.createRetrofit().create(IReqeustNetwor.class)
.loginAction(new LoginReqeust());
return loginResponseObservable;
}
})
.observeOn(AndroidSchedulers.mainThread()) // 給下面 執(zhí)行主線程
.subscribe(new Observer<LoginResponse>() {
// 一定是主線程,為什么,因?yàn)?subscribe 馬上調(diào)用onSubscribe
@Override
public void onSubscribe(Disposable d) {
// TODO 1
progressDialog = new ProgressDialog(RequestActivity.this);
progressDialog.show();
// UI 操作
disposable = d;
}
@Override
public void onNext(LoginResponse loginResponse) { // todo 5
// TODO 4.登錄完成之后,更新登錄的UI
}
@Override
public void onError(Throwable e) {
}
// todo 6
@Override
public void onComplete() {
// 殺青了
if (progressDialog != null) {
progressDialog.dismiss();
}
}
});
因?yàn)閟ubscribe方法返回值是void,調(diào)用subscribe的話,整個(gè)事件流就結(jié)束了,
而observeOn方法的返回值是Observable,
我們就可以繼續(xù)在被觀察者Observable上執(zhí)行事件流,從而繼續(xù)執(zhí)行flatMap等一系列的操作符。