談?wù)剬?duì)于響應(yīng)式編程RxJava的理解 - 核心思想篇

談?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)

image.png

舉個(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ì)減少很多)。。

image.png

現(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é)束了,


image.png

而observeOn方法的返回值是Observable,


image.png

我們就可以繼續(xù)在被觀察者Observable上執(zhí)行事件流,從而繼續(xù)執(zhí)行flatMap等一系列的操作符。

至此關(guān)于RxJava的核心使用思想已經(jīng)介紹完畢了,當(dāng)然如果大家還想學(xué)習(xí)RxJava的其他操作符的話可以點(diǎn)擊ReactiveX官網(wǎng)或者自行百度,RxJava的操作符非常的多,下圖,我總結(jié)了開發(fā)中會(huì)用到的RxJava的所有的操作符,大家可以采用“漸進(jìn)式”的學(xué)習(xí)方法,用到哪個(gè)再去看哪個(gè),再去學(xué),基本使用思想知道了,百度到用法自然就不會(huì)悶逼。
image.png

如果想進(jìn)一步了解RxJava中的核心源碼的話,可以看下面這篇文章。談?wù)剬?duì)于響應(yīng)式編程RxJava的理解 - 原理篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。