RxJava 從入門到全解析

nt1979發表於2021-09-09

前言

使用了RxJava有一段時間了,深深感受到了其“牛逼”之處。下面,就從RxJava的基礎開始,一步一步與大家分享一下這個強大的非同步庫的用法!

上給的解釋是:
“RxJava is a Java VM implementation of Reactive Extensions: 

a library for composing asynchronous and event-based programs by using observable sequences.”

大概就是說RxJava是Java VM上一個靈活的、使用可觀測序列來組成的一個非同步的、基於事件的庫。咋一看好像不知道是啥東西… … 沒事,往下看~

 這段解釋,重點就在於非同步!但是它又不像 AsyncTask 這樣用法簡單,所以剛接觸RxJava的童鞋,可能會覺得特別難,無從下手,沒事,相信透過這篇文章,大夥兒可以有一個比較深刻的理解!

RxJava精華可以濃縮為非同步兩個字,其核心的東西不外乎兩個:

1.  Observable(被觀察者) 

2.  Observer/Subscriber(觀察者)

Observables可以發出一系列的 事件,這裡的事件可以是任何東西,例如網路請求、複雜計算處理、資料庫操作、檔案操作等等,事件執行結束後交給 Observer/Subscriber 的回撥處理。

下獲取最新版本。

後面會詳細介紹,這裡先有個瞭解。
// 建立物件,just裡面的每一個引數,相當於呼叫一次Subscriber#OnNext()Observable observable = Observable.just("Hello World!");

這樣,是不是簡單了許多?

進行消費。首先,先建立一個觀察者。
// 建立一個ObserverObserver observer = new Observer() {    @Override
    public void onCompleted() {
        Log.i(TAG, "complete");
    }    @Override
    public void onError(Throwable e) {

    }    @Override
    public void onNext(String s) {
        Log.i(TAG, s);
    }
};

或者

// 建立一個SubscriberSubscriber subscriber = new Subscriber() {    @Override
    public void onCompleted() {
        Log.i(TAG, "complete");
    }    @Override
    public void onError(Throwable e) {

    }    @Override
    public void onNext(String s) {
        Log.i(TAG, s);
    }
};
  1. Observer 是觀察者, Subscriber 也是觀察者,Subscriber 是一個實現了Observer介面的抽象類,對 Observer 進行了部分擴充套件,在使用上基本沒有區別;

  2. Subscriber 多了傳送之前呼叫的 onStart() 和解除訂閱關係的 unsubscribe() 方法。

  3. 並且,在 RxJava 的 subscribe 過程中,Observer 也總是會先被轉換成一個 Subscriber 再使用。所以在這之後的示例程式碼,都使用 Subscriber 來作為觀察者。

與來說,好像為了列印一個“Hello World!”要費好大的勁… 其實,RxJava 自身提供了精簡回撥方式,我們可以為 Subscriber 中的三種狀態根據自身需要分別建立一個回撥動作 Action
// onComplete()Action0 onCompleteAction = new Action0() {    @Override
    public void call() {
        Log.i(TAG, "complete");
    }
};// onNext(T t)Action1 onNextAction = new Action1() {    @Override
    public void call(String s) {
        Log.i(TAG, s);
    }
};// onError(Throwable t)Action1 onErrorAction = new Action1() {    @Override
    public void call(Throwable throwable) {

    }
};

那麼,RxJava 的事件訂閱支援以下三種不完整定義的回撥。

observable.subscribe(onNextAction);

observable.subscribe(onNextAction, onErrorAction);

observable.subscribe(onNextAction, onErrorAction, onCompleteAction);

我們可以根據當前需要,傳入對應的 Action, RxJava 會相應的自動建立 Subscriber。

  1. Action0 表示一個無回撥引數的Action;

  2. Action1 表示一個含有一個回撥引數的Action;

  3. 當然,還有Action2 ~ Action9,分別對應2~9個引數的Action;

  4. 每個Action,都有一個 call() 方法,透過泛型T,來指定對應引數的型別;

 進行改寫。
final ImageView ivLogo = (ImageView) findViewById(R.id.ivLogo);

Observable.create(new Observable.OnSubscribe() {    @Override
    public void call(Subscriber super String> subscriber) {

        subscriber.onNext("https://ss2.baidu.com/-vo3dSag_xI4khGko9WTAnF6hhy/image/h%3D200/sign=4db5130a073b5bb5a1d727fe06d2d523/cf1b9d16fdfaaf51965f931e885494eef11f7ad6.jpg");
    }
}).map(new Func1() {    @Override
    public Drawable call(String url) {        try {
            Drawable drawable = Drawable.createFromStream(new URL(url).openStream(), "src");            return drawable;
        } catch (IOException e) {

        }        return null;
    }
})        // 指定 subscribe() 所在的執行緒,也就是call()方法呼叫的執行緒
        .subscribeOn(Schedulers.io())        // 指定 Subscriber 回撥方法所在的執行緒,也就是onCompleted, onError, onNext回撥的執行緒
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Subscriber() {            @Override
            public void onCompleted() {

            }            @Override
            public void onError(Throwable e) {
                Log.e(TAG, e.toString());
            }            @Override
            public void onNext(Drawable drawable) {                if (drawable != null) {
                    ivLogo.setImageDrawable(drawable);
                }
            }
        });

經過改寫程式碼後,有什麼變化呢? Observable 建立了一個 String 事件,也就是產生一個url,透過 map 運算子進行變換,返回Drawable物件,這個變換指的就是透過url進行網路圖片請求,返回一個Drawable。所以簡單的來說就是把String事件,轉換為Drawable事件。邏輯表示就是:

Observable --> map變換 --> Observable

那麼,Func1 是什麼呢?與 Action1 類似,不同的是 FuncX 有返回值,而 ActionX 沒有。為什麼需要返回值呢?目的就在於物件的變換,由String物件轉換為Drawable物件。同樣,也有Func0 ~ Func9,對應不同的引數個數。

當然了,RxJava 的變換,可不止於map這麼簡單,繼續往下!

會介紹這個。為了演示效果,先舉個簡單栗子:
Observable
    .create(new Observable.OnSubscribe() {        @Override
        public void call(Subscriber super Integer> subscriber) {            int i = 0;            int[] times = new int[]{100, 1000};            while (true) {
                i++;                if (i >= 100)                    break;
                subscriber.onNext(i);                try {                    // 注意!!!!
                    // 當i為奇數時,休眠1000ms,然後才傳送i+1,這時i不會被過濾掉
                    // 當i為偶數時,只休眠100ms,便傳送i+1,這時i會被過濾掉
                    Thread.sleep(times[i % 2]);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            subscriber.onCompleted();
        }
    })    // 間隔400ms以內的事件將被丟棄
    .debounce(400, TimeUnit.MILLISECONDS)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Subscriber() {        @Override
        public void onCompleted() {
            Log.i(TAG, "complete");
        }        @Override
        public void onError(Throwable e) {
            Log.e(TAG, e.toString());
        }        @Override
        public void onNext(Integer integer) {
            Log.i(TAG, "integer = " + integer);
        }
    });

輸出結果:

11-23 10:44:45.167 MainActivity: integer = 111-23 10:44:46.270 MainActivity: integer = 311-23 10:44:47.373 MainActivity: integer = 511-23 10:44:48.470 MainActivity: integer = 711-23 10:44:49.570 MainActivity: integer = 911-23 10:44:50.671 MainActivity: integer = 1111-23 10:44:51.772 MainActivity: integer = 1311-23 10:44:52.872 MainActivity: integer = 1511-23 10:44:53.973 MainActivity: integer = 17...

我們設定過濾條件為400ms,可以發現,奇數正常輸出,因為在它的下一個事件事件隔了1000ms,所以它不會被過濾掉;偶數被過濾掉,是因為它距離下一個事件(奇數)只隔了100ms。並且,輸出的兩個事件相隔大約為 100ms + 1000ms = 1100ms

  • merge 
    用於合併兩個Observable為一個Observable。較為簡單。

    Observable.merge(Observable1, Observable2)
        .subscribe(subscriber);
  • concat 
    順序執行多個Observable,個數為1 ~ 9。例子稍後與first運算子一起~~

  • compose 
    與 flatMap 類似,都是進行變換,返回Observable物件,啟用併傳送事件。

    1. compose 是唯一一個能夠從資料流中得到原始Observable的運算子,所以,那些需要對整個資料流產生作用的操作(比如,subscribeOn()和observeOn())需要使用 compose 來實現。相較而言,如果在flatMap()中使用subscribeOn()或者observeOn(),那麼它僅僅對在 flatMap 中建立的Observable起作用,而不會對剩下的流產生影響。這樣就可以簡化subscribeOn()以及observeOn()的呼叫次數了。

    2. compose 是對 Observable 整體的變換,換句話說, flatMap 轉換Observable裡的每一個事件,而 compose 轉換的是整個Observable資料流。

    3. flatMap 每傳送一個事件都建立一個 Observable,所以效率較低。而 compose 運算子只在主幹資料流上執行操作。

    4. 建議使用 compose 代替 flatMap

  • first 
    只傳送符合條件的第一個事件。可以與前面的contact運算子,做網路快取。舉個例子:依次檢查Disk與Network,如果Disk存在快取,則不做網路請求,否則進行網路請求。

    // 從快取獲取Observable fromDisk = Observable.create(new Observable.OnSubscribe() {    @Override
        public void call(Subscriber super BookList> subscriber) {
            BookList list = getFromDisk();        if (list != null) {
                subscriber.onNext(list);
            } else {
                subscriber.onCompleted();
            }
        }
    });// 從網路獲取Observable fromNetWork = bookApi.getBookDetailDisscussionList();
    
    Observable.concat(fromDisk, fromNetWork)        // 如果快取不為null,則不再進行網路請求。反之
            .first()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber() {            @Override
                public void onCompleted() {
    
                }            @Override
                public void onError(Throwable e) {
    
                }            @Override
                public void onNext(BookList discussionList) {
    
                }
            });

    網路快取用法,具體可參見我的專案:

  • timer 
    可以做定時操作,換句話講,就是延遲執行。事件間隔由timer控制。舉個例子:兩秒後輸出“Hello World!”

    Observable.timer(2, TimeUnit.SECONDS)
        .subscribe(new Subscriber() {        @Override
            public void onCompleted() {
    
            }        @Override
            public void onError(Throwable e) {
    
            }        @Override
            public void onNext(Long aLong) {
                Log.i(TAG, "Hello World!");
            }
        });
  • interval 
    定時的週期性操作,與timer的區別就在於它可以重複操作。事件間隔由interval控制。舉個例子:每隔兩秒輸出“Hello World!”

    Observable.interval(2, TimeUnit.SECONDS)
        .subscribe(new Subscriber() {        @Override
            public void onCompleted() {
    
            }        @Override
            public void onError(Throwable e) {
    
            }        @Override
            public void onNext(Long aLong) {
                Log.i(TAG, "Hello World!");
            }
        });
  • throttleFirst 
    與debounce類似,也是時間間隔太短,就丟棄事件。可以用於防抖操作,比如防止雙擊。

    RxView.clicks(button)
      .throttleFirst(1, TimeUnit.SECONDS)
      .subscribe(new Observer() {      @Override
          public void onCompleted() {
    
          }      @Override
          public void onError(Throwable e) {
    
          }      @Override
          public void onNext(Object o) {
               Log.i(TAG, "do clicked!");
          }
      });

    上面這個RxView詳見:, 主要與RxJava結合用於一些View的事件繫結,JakeWharton大神的專案,厲害。

  • Single 
    Single與Observable類似,相當於是他的精簡版。訂閱者回撥的不是OnNext/OnError/onCompleted,而是回撥OnSuccess/OnError。

    Single.create(new Single.OnSubscribe() {    @Override
        public void call(SingleSubscriber super Object> subscriber) {
            subscriber.onSuccess("Hello");
        }
    }).subscribe(new SingleSubscriber() {    @Override
        public void onSuccess(Object value) {
            Log.i(TAG, value.toString());
        }    @Override
        public void onError(Throwable error) {
    
        }
    });
  • Subject 
    Subject這個類,既是Observable又是Observer,啥意思呢?就是它自身既是事件的生產者,又是事件的消費者,相當於自身是一條管道,從一端進,又從另一端出。舉個例子:PublishSubject

    Subject subject = PublishSubject.create();// 1.由於Subject是Observable,所以進行訂閱subject.subscribe(new Subscriber() {    @Override
        public void onCompleted() {
    
        }    @Override
        public void onError(Throwable e) {
    
        }    @Override
        public void onNext(Object o) {
            Log.i(TAG, o.toString());
        }
    });// 2.由於Subject同時也是Observer,所以可以呼叫onNext傳送資料subject.onNext("world");

    這個好像有點厲害的樣子,哈哈。可以配合debounce,避免SearchEditText頻繁請求。

    Subject subject = PublishSubject.create();
    
    subject.debounce(400, TimeUnit.MILLISECONDS)
            .subscribe(new Subscriber() {        @Override
            public void onCompleted() {
    
            }        @Override
            public void onError(Throwable e) {
    
            }        @Override
            public void onNext(Object o) {            // request
            }
        });
    
    edittext.addTextChangedListener(new TextWatcher() {    @Override 
        public void beforeTextChanged(CharSequence s, int start, int count, int after) { }    @Override 
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            subject.onNext(s.toString());
        }    @Override 
        public void afterTextChanged(Editable s) { } 
    });
  • 最後的獲取Readme內容顯示在WebView上的例子。

    Retrofit2 + RxJava + Dagger2: 具體可參見我的專案,裡面有比較詳細的用法。 

    不難發現,Retrofit 把請求封裝進 Observable ,在請求結束後呼叫 onNext() 以及 OnCompleted() 或在請求失敗後呼叫 onError()

    :RxJava形式的請求,並不能減少程式碼量,但是邏輯非常清晰。假如請求到資料之後需要對資料進行處理,並且是耗時操作,難道要再開一個執行緒,或者用AsyncTask再做一次非同步?很顯然,RxJava的變換很好的解決了這個問題,依然會使邏輯結構清晰。

    這個庫。

    , 主要與RxJava結合用於一些View的事件繫結。

    做週期性操作的例子,並沒有使之停下來的,沒有去控制訂閱的生命週期,這樣,就有可能引發記憶體洩漏。所以,在Activity#onDestroy()的時候或者不需要繼續執行的時候應該取消訂閱。
    Subscription subscription = Observable.interval(2, TimeUnit.SECONDS)
        .subscribe(new Subscriber() {        @Override
            public void onCompleted() {
    
            }        @Override
            public void onError(Throwable e) {
    
            }        @Override
            public void onNext(Long aLong) {
                Log.i(TAG, "Hello World!");
            }
        });// 呼叫unsubscribe();方法進行取消訂閱subscription.unsubscribe();

    但是,如果有很多個資料來源,那豈不是要取消很多次?當然不是的,可以利用 CompositeSubscription, 相當於一個 Subscription 集合。

    CompositeSubscription list = new CompositeSubscription();
    list.add(subscription1);
    list.add(subscription2);
    list.add(subscription3);// 統一呼叫一次unsubscribe,就可以把所有的訂閱都取消list.unsubscribe();

    http://www.apkbus.com/blog-873055-77431.html

    來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4822/viewspace-2811953/,如需轉載,請註明出處,否則將追究法律責任。

    相關文章