響應式編程在Android中的應用

響應式編程簡介

  • 響應式編程是一種基于異步數據流概念的編程模式。數據流就像一條河:它可以被觀測,被過濾,被操作,或者為新的消費者與另外一條流合并為一條新的流。
  • 響應式編程的一個關鍵概念是事件。事件可以被等待,可以觸發過程,也可以觸發其它事件。事件是唯一的以合適的方式將我們的現實世界映射到我們的軟件中:如果屋里太熱了我們就打開一扇窗戶。同樣的,當我們更改電子表(變化的傳播)中的一些數值時,我們需要更新整個表格或者我們的機器人碰到墻時會轉彎(響應事件)。
  • 今天,響應式編程最通用的一個場景是UI:我們的移動App必須做出對網絡調用、用戶觸摸輸入和系統彈框的響應。在這個世界上,軟件之所以是事件驅動并響應的是因為現實生活也是如此。

響應式編程的具體實現 - RxJava

基本概念

RxJava的四種角色

  • Observable
  • Observer
  • Subscriber
  • Subject

Observable和Subject是兩個“生產”實體,Observer和Subscriber是兩個“消費”實體。

熱Observable和冷Observable

從發射物的角度來看,有兩種不同的Observable:熱的和冷的。一個"熱"的Observable典型的只要一創建完就開始發射數據,因此所有后續訂閱它的觀察者可能從序列中間的某個位置開始接受數據(有一些數據錯過了)。一個"冷"的Observable會一直等待,直到有觀察者訂閱它才開始發射數據,因此這個觀察者可以確保會收到整個數據序列。

Observable創建符

  • Observable.create()
Observable.create(new Observable.OnSubscribe<Object>(){
    @Override
    public void call(Subscriber<? super Object> subscriber{
    }
});
  • Observable.from()
    from() 創建符可以從一個列表/數組來創建Observable,并一個接一個的從列表/數組中發射出來每一個對象,或者也可以從Java Future 類來創建Observable,并發射Future對象的 .get() 方法返回的結果值。傳入 Future 作為參數時,我們可以指定一個超時的值。Observable將等待來自 Future 的結果;如果在超時之前仍然沒有結果返回,Observable將會觸發 onError() 方法通知觀察者有錯誤發生了。

    List<Integer> items = new ArrayList<Integer>();
    items.add(1);
    items.add(10);
    items.add(100);
    items.add(200);
    
    Observable<Integer> observableString = Observable.from(items);
    Subscription subscriptionPrint = observableString.subscribe(new        Observer<Integer>() {
      @Override
      public void onCompleted() {
      System.out.println("Observable completed");
      }
      @Override
      public void onError(Throwable e) {
      System.out.println("Oh,no! Something wrong happened!");
      }
      @Override
      public void onNext(Integer item) {
      System.out.println("Item is " + item);
      }
    });
    
  • Observable.just()
    just() 方法可以傳入一到九個參數,它們會按照傳入的參數的順序來發射它們。 just() 方法也可以接受列表或數組,就像 from() 方法,但是它不會迭代列表發射每個值,它將會發射整個列表。通常,當我們想發射一組已經定義好的值時會用到它。但是如果我們的函數不是時變性的,我們可以用just來創建一個更有組織性和可測性的代碼庫。

Observable<String> observableString = Observable.just(helloWorld
());
Subscription subscriptionPrint = observableString.subscribe(new
Observer<String>() {
    @Override
    public void onCompleted() {
    System.out.println("Observable completed");
    }
    @Override
    public void onError(Throwable e) {
    System.out.println("Oh,no! Something wrong happened!");
    }
    @Override
    public void onNext(String message) {
    System.out.println(message);
    }
});

helloWorld() 方法比較簡單,像這樣:

private String helloWorld(){
    return "Hello World";
}

Subject

Subject 既可以是 Observable,也可以是 Observer。
RxJava 提供四種不同的 Subject :

  • PublishSubject
  • BehaviorSubject
    BehaviorSubject會首先向他的訂閱者發送截至訂閱前最新的一個數據對象(或初始值),然后正常發送訂閱后的數據流。

BehaviorSubject<Integer> behaviorSubject = BehaviorSubject.create(1);
```
在這個短例子中,我們創建了一個能發射整形(Integer)的BehaviorSubject。由于每當Observes訂閱它時就會發射最新的數據,所以它需要一個初始值。

  • ReplaySubject
    ReplaySubject 會緩存它所訂閱的所有數據,向任意一個訂閱它的觀察者重發:

ReplaySubject<Integer> replaySubject = ReplaySubject.create();
```

  • AsyncSubject

    當Observable完成時AsyncSubject只會發布最后一個數據給已經訂閱的每一個觀察者。

    AsyncSubject<Integer> asyncSubject = AsyncSubject.create();
    

直接創建 Observable

在我們的第一個列子里,我們將檢索安裝的應用列表并填充RecycleView的item來展示它們。我們也設想一個下拉刷新的功能和一個進度條來告知用戶當前任務正在執行。

首先,我們創建Observable。我們需要一個函數來檢索安裝的應用程序列表并把它提供給我們的觀察者。我們一個接一個的發射這些應用程序數據,將它們分組到一個單獨的列表中,以此來展示響應式方法的靈活性。

private Observable<AppInfo> getApps(){
    return Observable.create(subscriber -> {
        List<AppInfoRich> apps = new ArrayList<AppInfoRich>();
        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        List<ResolveInfo> infos = getActivity().queryIntentActivities(mainIntent, 0);
        for(ResolveInfo info : infos){
            apps.add(new AppInfoRich(getActivity(),info));
        }
        for (AppInfoRich appInfo:apps) {
            Bitmap icon = Utils.drawableToBitmap(appInfo.getIcon());
            String name = appInfo.getName();
            String iconPath = mFilesDir + "/" + name;
            Utils.storeBitmap(App.instance, icon,name);
            if (subscriber.isUnsubscribed()){
                return;
            }
            subscriber.onNext(new AppInfo(name, iconPath, appInfo.getLastUpdateTime()));
        }
        if (!subscriber.isUnsubscribed()){
            subscriber.onCompleted();
        }
    });
}

AppInfo為App信息的實體類,包括上次更新時間、圖標、名字三個屬性,此處省略。

需要重點注意的是在發射新的數據或者完成序列之前要檢測觀察者的訂閱情況。這樣的話代碼會更高效,因為如果沒有觀察者等待時我們就不生成沒有必要的數據項。

接下來,我們來定義下拉刷新的方法:

private void refreshTheList() {
    getApps().toSortedList()
    .subscribe(new Observer<List<AppInfo>>() {
    @Override
    public void onCompleted() {
        Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show();
    }
    @Override
    public void onError(Throwable e) {
        Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
        mSwipeRefreshLayout.setRefreshing(false);
    }
    @Override
    public void onNext(List<AppInfo> appInfos) {
        mRecyclerView.setVisibility(View.VISIBLE);
        mAdapter.addApplications(appInfos);
        mSwipeRefreshLayout.setRefreshing(false);
    }
    });
}

從列表創建 Observable

在這個例子中,我們將引入 from() 函數。使用這個特殊的“創建”函數,我們可以從一個列表中創建一個Observable。Observable將發射出列表中的每一個元素,我們可以通過訂閱它們來對這些發出的元素做出響應。

private void loadList(List<AppInfo> apps) {
    mRecyclerView.setVisibility(View.VISIBLE);
    Observable.from(apps).subscribe(new Observer<AppInfo>() {
        @Override
        public void onCompleted() {
            mSwipeRefreshLayout.setRefreshing(false);
            Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show();
        }
        @Override
        public void onError(Throwable e) {
            Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
            mSwipeRefreshLayout.setRefreshing(false);
        }
        @Override
        public void onNext(AppInfo appInfo) {
            mAddedApps.add(appInfo);
            mAdapter.addApplication(mAddedApps.size() - 1, appInfo);
        }
    });
}

和第一個例子一個主要的不同是我們在 onCompleted() 函數中停掉進度條是因為我們一個一個的發射元素;
第一個例子中的Observable發射的是整個list,因此在 onNext() 函數中停掉進度條的做法是安全的。

具有特殊功能的創建符

  • just()

    你可以將一個函數作為參數傳給 just() 方法,你將會得到一個已存在代碼的原始Observable版本。在一個新的響應式架構的基礎上遷移已存在的代碼,這個方法可能是一個有用的開始點。

  • repeat()

    假如你想對一個Observable重復發射三次數據 :

    Observable.just(appOne,appTwo,appThree)
        .repeat(3)
        .subscribe();
    

    我們在 just() 創建Observable后追加了 repeat(3) ,它將會創建9個元素的序列,每一個都單獨發射。

  • defer()

    有這樣一個場景,你想在這聲明一個Observable但是你又想推遲這個Observable的創建直到觀察者訂閱時。看下面的 getInt() 函數:

    private Observable<Integer> getInt(){
        return Observable.create(subscriber -> {
            if(subscriber.isUnsubscribed()){
                return;
            }
            App.L.debug("GETINT");
            subscriber.onNext(42);
            subscriber.onCompleted();
        });
    }
    

    這比較簡單,并且它沒有做太多事情,但是它正好為我們服務。現在,我們可以創建一個新的Observable并且應用 defer() :

    Observable<Integer> deferred = Observable.defer(this::getInt);
    

    這次, deferred 存在,但是 getInt() create() 方法還沒有調用 : logcat日志也沒有“GETINT”打印出來 :

    deferred.subscribe(number -> {
        App.L.debug(String.valueOf(number));
    });
    

    但是一旦我們訂閱了, create() 方法就會被調用并且我們也可以在logcat日志中打印出兩個值:GETINT 和 42。

  • range()

    從一個指定的數字X開始發射N個數字。range() 函數用兩個數字作為參數:第一個是起始點,第二個是我們想發射數字的個數。

  • interval()

    interval() 函數在你需要創建一個輪詢程序時非常好用。interval() 函數的兩個參數:一個指定兩次發射的時間間隔,另一個是用到的時間單位。

  • timer()

    如果你需要一個一段時間之后才發射的Observable,你可以使用 timer()。

過濾Observables

過濾序列

RxJava讓我們使用 filter() 方法來過濾我們觀測序列中不想要的值。

我們從發出的每個元素中過濾掉開頭字母不是C的 :

.filter(new Func1<AppInfo,Boolean>(){
    @Override
    public Boolean call(AppInfo appInfo){
        return appInfo.getName().startsWith("C");
    }
})

我們傳一個新的 Func1 對象給 filter() 函數,即只有一個參數的函數。 Func1 有一個 AppInfo 對象來作為它的參數類型并且返回 Boolean 對象。只要條件符合 filter() 函數就會返回 true 。此時,值會發射出去并且所有的觀察者都會接收到。

filter() 函數最常用的用法之一時過濾 null 對象:

.filter(new Func1<AppInfo,Boolean>(){
    @Override
    public Boolean call(AppInfo appInfo){
        return appInfo != null;
    }
})

它幫我們免去了在 onNext() 函數調用中再去檢測 null 值,讓我們把注意力集中在應用業務邏輯上。

獲取我們需要的數據

當我們不需要整個序列時,而是只想取開頭或結尾的幾個元素,我們可以用 take() 或 takeLast() 。

  • take()

    take() 函數用整數N來作為一個參數,從原始的序列中發射前N個元素,然后完成:

    Observable.from(apps)
        .take(3)
        .subscribe(...);
    
  • takeLast()

    如果我們想要最后N個元素,我們只需使用 takeLast() 函數:

    Observable.from(apps)
        .takeLast(3)
        .subscribe(...);
    

有且僅有一次

  • distinct()

    就像 takeLast() 一樣, distinct() 作用于一個完整的序列,然后得到重復的過濾項,它需要記錄每一個發射的值。如果你在處理一大堆序列或者大的數據記得關注內存使用情況。

    Observable<AppInfo> fullOfDuplicates = Observable.from(apps)
        .take(3)
        .repeat(3);
    fullOfDuplicates.distinct()
        .subscribe(...);
    
  • ditinctUntilChanged()

    如果在一個可觀測序列發射一個不同于之前的一個新值時讓我們得到通知這時候該怎么做?ditinctUntilChanged() 過濾函數能做到這一點。它能輕易的忽略掉所有的重復并且只發射出新的值。

First and last

first() 方法和 last() 方法很容易弄明白。它們從Observable中只發射第一個元素或者最后一個元素。這兩個都可以傳 Func1 作為參數。
與 first() 和 last() 相似的變量有: firstOrDefault() 和 lastOrDefault() 。這兩個函數當可觀測序列完成時不再發射任何值時用得上。在這種場景下,如果Observable不再發射任何值時我們可以指定發射一個默認的值。

Skip and SkipLast

skip() 和 skipLast() 函數與 take() 和 takeLast() 相對應。它們用整數N作參數,從本質上來說,它們不讓Observable發射前N個或者后N個值。

ElementAt

如果我們只想要可觀測序列發射的第五個元素該怎么辦? elementAt() 函數僅從一個序列中發射第n個元素然后就完成了。
如果我們想查找第五個元素但是可觀測序列只有三個元素可供發射時該怎么辦?我們可以使用 elementAtOrDefault() 。

Sampling

在Observable后面加一個 sample() ,我們將創建一個新的可觀測序列,它將在一個指定的時間間隔里由Observable發射最近一次的數值:

Observable<Integer> sensor = [...]
sensor.sample(30,TimeUnit.SECONDS)
    .subscribe(...);

如果我們想讓它定時發射第一個元素而不是最近的一個元素,我們可以使用 throttleFirst() 。

Timeout

我們可以使用 timeout() 函數來監聽源可觀測序列,就是在我們設定的時間間隔內如果沒有得到一個值則發射一個錯誤。我們可以認為 timeout() 為一個Observable的限時的副本。如果在指定的時間間隔內Observable不發射值的話,它監聽的原始的Observable時就會觸發 onError() 函數。

Subscription subscription = getCurrentTemperature()
    .timeout(2,TimeUnit.SECONDS)
    .subscribe(...);

Debounce

debounce() 函數過濾掉由Observable發射的速率過快的數據;如果在一個指定的時間間隔過去了仍舊沒有發射一個,那么它將發射最后的那個。

下圖展示了多久從Observable發射一次新的數據, debounce() 函數開啟一個內部定時器,如果在這個時間間隔內沒有新的據發射,則新的Observable發射出最后一個數據:

debounce() 函數示意圖

變換Observables

*map家族

RxJava提供了幾個mapping函數: map() , flatMap() , concatMap() , flatMapIterable() 以及 switchMap() .所有這些函數都作用于一個可觀測序列,然后變換它發射的值,最后用一種新的形式返回它們。

  • Map

    RxJava的 map 函數接收一個指定的 Func 對象然后將它應用到每一個由Observable發射的值上。

    Observable.from(apps)
        .map(new Func1<AppInfo,AppInfo>(){
            @Override
            public Appinfo call(AppInfo appInfo){
                String currentName = appInfo.getName();
                String lowerCaseName = currentName.toLowerCase();
                appInfo.setName(lowerCaseName);
                return appInfo;
            }
        })
        .subscribe(...);
    

    正如你看到的,像往常一樣創建我們發射的Observable之后,我們追加一個 map 調用,我們創建一個簡單的函數來更新 AppInfo對象并提供一個名字小寫的新版本給觀察者。

  • FlatMap

    在復雜的場景中,我們有一個這樣的Observable:它發射一個數據序列,這些數據本身也可以發射Observable。RxJava的 flatMap() 函數提供一種鋪平序列的方式,然后合并這些Observables發射的數據,最后將合并后的結果作為最終的Observable。

    flatMap() 函數示意圖

    當我們在處理可能有大量的Observables時,重要是記住任何一個Observables發生錯誤的情況, flatMap() 將會觸發它自己的 onError() 函數并放棄整個鏈。重要的一點提示是關于合并部分:它允許交叉。正如上圖所示,這意味著 flatMap() 不能夠保證在最終生成的Observable中源Observables確切的發射順序。

  • ConcatMap

    RxJava的 concatMap() 函數解決了 flatMap() 的交叉問題,提供了一種能夠把發射的值連續在一起的鋪平函數,而不是合并它們,如下圖所示:

    這里寫圖片描述
  • FlatMapIterable

    作為*map家族的一員, flatMapInterable() 和 flatMap() 很像。僅有的本質不同是它將源數據兩兩結成對并生成Iterable,而不是原始數據項和生成的Observables。

  • SwitchMap

    switchMap() 和 flatMap() 很像,除了一點:每當源Observable發射一個新的數據項(Observable)時,它將取消訂閱并停止監視之前那個數據項產生的Observable,并開始監視當前發射的這一個。

  • Scan

    RxJava的 scan() 函數可以看做是一個累積函數。 scan() 函數對原始Observable發射的每一項數據都應用一個函數,計算出函數的結果值,并將該值填充回可觀測序列,等待和下一次發射的數據一起使用。

    作為一個通用的例子,給出一個累加器:

    Observable.just(1,2,3,4,5)
        .scan((sum,item) -> sum + item)
        .subscribe(new Subscriber<Integer>() {
            @Override
            public void onCompleted() {
                Log.d("RXJAVA", "Sequence completed.");
            }
            @Override
            public void onError(Throwable e) {
                Log.e("RXJAVA", "Something went south!");
            }
            @Override
            public void onNext(Integer item) {
                Log.d("RXJAVA", "item is: " + item);
            }
        });
    

    我們得到的結果是:

    RXJAVA: item is: 1
    RXJAVA: item is: 3
    RXJAVA: item is: 6
    RXJAVA: item is: 10
    RXJAVA: item is: 15
    RXJAVA: Sequence completed.

GroupBy

RxJava提供了一個有用的函數從列表中按照指定的規則: groupBy() 來分組元素。下圖中的例子展示了 groupBy() 如何將發射的值根據他們的形狀來進行分組。

這里寫圖片描述

這個函數將源Observable變換成一個發射Observables的新的Observable。它們中的每一個新的Observable都發射一組指定的數據。

為了創建一個分組了的已安裝應用列表,我們在 loadList() 函數中引入了一個新的元素:

Observable<GroupedObservable<String,AppInfo>> groupedItems = Observable.from(apps)
            .groupBy(new Func1<AppInfo,String>(){
                @Override
                public String call(AppInfo appInfo){
                    SimpleDateFormat formatter = new SimpleDateFormat("MM/yyyy");
                    return formatter.format(new Date(appInfo.getLastUpdateTime()));
                }
            });

現在我們創建了一個新的Observable, groupedItems ,它將會發射一個帶有 GroupedObservable 的序列。 GroupedObservable 是一個特殊的Observable,它源自一個分組的key。在這個例子中,key就是 String ,代表的意思是 Month/Year 格式化的最近更新日期。

Buffer

RxJava中的 buffer() 函數將源Observable變換一個新的Observable,這個新的Observable每次發射一組列表值而不是一個一個發射。

buffer() 函數有幾種變體。其中有一個是允許你指定一個 skip 值:此后每 skip 項數據,用count項數據填充緩沖區。另一個是buffer() 帶一個 timespan 的參數,會創建一個每隔timespan時間段就會發射一個列表的Observable。

Window

RxJava的 window() 函數和 buffer() 很像,但是它發射的是Observable而不是列表。

正如 buffer() 一樣, window() 也有一個 skip 變體。

Cast

cast() 函數是 map() 操作符的特殊版本。它將源Observable中的每一項數據都轉換為新的類型,把它變成了不同的 Class 。

組合Observables

Merge

在”異步的世界“中經常會創建這樣的場景,我們有多個來源但是又只想有一個結果:多輸入,單輸出。RxJava的 merge() 方法將幫助你把兩個甚至更多的Observables合并到他們發射的數據項里。下圖給出了把兩個序列合并在一個最終發射的Observable。

這里寫圖片描述

正如你看到的那樣,發射的數據被交叉合并到一個Observable里面。注意如果你同步的合并Observable,它們將連接在一起并且不會交叉。

Observable<AppInfo> mergedObserbable = Observable.merge(observableApps,observableReversedApps);
mergedObserbable.subscribe(...);

注意錯誤時的toast消息,你可以認為每個Observable拋出的錯誤都將會打斷合并。如果你需要避免這種情況,RxJava提供了 mergeDelayError() ,它能從一個Observable中繼續發射數據即便是其中有一個拋出了錯誤。當所有的Observables都完成時, mergeDelayError() 將會發射 onError()。

ZIP

在一種新的可能場景中處理多個數據來源時會帶來:多從個Observables接收數據,處理它們,然后將它們合并成一個新的可觀測序列來使用。RxJava有一個特殊的方法可以完成: zip() 合并兩個或者多個Observables發射出的數據項,根據指定的函數Func* 變換它們,并發射一個新值。下圖展示了 zip() 方法如何處理發射的“numbers”和“letters”然后將它們合并一個新的數據項:

這里寫圖片描述
Observable.zip(observableApp, tictoc, (AppInfo appInfo, Long time) -> updateTitle(appInfo, time))
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(...);

zip() 函數有三個參數:兩個Observables和一個 Func2 。

Join

前面兩個方法, zip() 和 merge() 方法作用在發射數據的范疇內,在決定如何操作值之前有些場景我們需要考慮時間的。RxJava的 join() 函數基于時間窗口將兩個Observables發射的數據結合在一起。

這里寫圖片描述

為了正確的理解上一張圖,我們解釋下 join() 需要的參數:

  • 第二個Observable和源Observable結合。
  • Func1 參數:在指定的由時間窗口定義時間間隔內,源Observable發射的數據和從第二個Observable發射的數據相互配合返回的Observable。
  • Func1 參數:在指定的由時間窗口定義時間間隔內,第二個Observable發射的數據和從源Observable發射的數據相互配合返回的Observable。
  • Func2 參數:定義已發射的數據如何與新發射的數據項相結合。

combineLatest

RxJava的 combineLatest() 函數有點像 zip() 函數的特殊形式。正如我們已經學習的, zip() 作用于最近未打包的兩個Observables。相反, combineLatest() 作用于最近發射的數據項:如果 Observable1 發射了A并且 Observable2 發射了B和C, combineLatest() 將會分組處理AB和AC,如下圖所示:

這里寫圖片描述

And,Then和When

在將來還有一些 zip() 滿足不了的場景。如復雜的架構,或者是僅僅為了個人愛好,你可以使用And/Then/When解決方案。它們在RxJava的joins包下,使用Pattern和Plan作為中介,將發射的數據集合并到一起。

這里寫圖片描述

Switch

給出一個發射多個Observables序列的源Observable, switch() 訂閱到源Observable然后開始發射由第一個發射的Observable發射的一樣的數據。當源Observable發射一個新的Observable時, switch() 立即取消訂閱前一個發射數
據的Observable(因此打斷了從它那里發射的數據流)然后訂閱一個新的Observable,并開始發射它的數據。

StartWith

RxJava的 startWith() 是 concat() 的對應部分。正如 concat() 向發射數據的Observable追加數據那樣,在Observable開始發射他們的數據之前,startWith() 通過傳遞一個參數來先發射一個數據序列。

Schedulers-解決Android主線程問題

Schedulers

調度器以一種最簡單的方式將多線程用在你的Apps的中。它們時RxJava重要的一部分并能很好地與Observables協同工作。它們無需處理實現、同步、線程、平臺限制、平臺變化而可以提供一種靈活的方式來創建并發程序。

RxJava提供了5種調度器:

  • .io()
  • .computation()
  • .immediate()
  • .newThread()
  • .trampoline()
Schedulers.io()

這個調度器時用于I/O操作。它基于根據需要,增長或縮減來自適應的線程池。我們將使用它來修復我們之前看到的 StrictMode 違規做法。由于它專用于I/O操作,所以并不是RxJava的默認方法;正確的使用它是由開發者決定的。

重點需要注意的是線程池是無限制的,大量的I/O調度操作將創建許多個線程并占用內存。一如既往的是,我們需要在性能和簡捷兩者之間找到一個有效的平衡點。

Schedulers.computation()

這個是計算工作默認的調度器,它與I/O操作無關。它也是許多RxJava方法的默認調度器: buffer() , debounce() , delay() , interval() , sample() , skip()。

Schedulers.immediate()

這個調度器允許你立即在當前線程執行你指定的工作。它是 timeout() , timeInterval() ,以及 timestamp() 方法默認的調度器。

Schedulers.newThread()

這個調度器正如它所看起來的那樣:它為指定任務啟動一個新的線程。

Schedulers.trampoline()

當我們想在當前線程執行一個任務時,并不是立即,我們可以用 .trampoline() 將它入隊。這個調度器將會處理它的隊列并且按序運行隊列中每一個任務。它是 repeat() 和 retry() 方法默認的調度器。

非阻塞I/O操作

使用 Schedulers.io() 創建非阻塞的版本:

public static void storeBitmap(Context context, Bitmap bitmap, String filename) {
    Schedulers.io().createWorker().schedule(() -> {
        blockingStoreBitmap(context, bitmap, filename);
    });
}

SubscribeOn and ObserveOn

我們學到了如何在一個調度器上運行一個任務。但是我們如何利用它來和Observables一起工作呢?RxJava提供了 subscribeOn() 方法來用于每個Observable對象。 subscribeOn() 方法用 Scheduler 來作為參數并在這個Scheduler上執行Observable調用。

首先,我們需要一個新的 getApps() 方法來檢索已安裝的應用列表:

private Observable<AppInfo> getApps() {
    return Observable.create(subscriber -> {
        List<AppInfo> apps = new ArrayList<>();
        SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
        Type appInfoType = new TypeToken<List<AppInfo>>(){}.getType();
        String serializedApps = sharedPref.getString("APPS", "");
        if (!"".equals(serializedApps)) {
            apps = new Gson().fromJson(serializedApps,appInfoType);
        }
        for (AppInfo app : apps) {
            subscriber.onNext(app);
        }
        subscriber.onCompleted();
    });
}

然后,我們所需要做的是指定 getApps() 需要在調度器上執行:

getApps().subscribeOn(Schedulers.io())
    .subscribe(new Observer<AppInfo>() { [...]

最后,我們只需在 loadList() 函數添加幾行代碼,那么每一項就都準備好了:

getApps()
    .onBackpressureBuffer()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<AppInfo>() { [...]

observeOn() 方法將會在指定的調度器上返回結果:如例子中的UI線程。 onBackpressureBuffer() 方法將告訴Observable發射的數據如果比觀察者消費的數據要更快的話,它必須把它們存儲在緩存中并提供一個合適的時間給它們。

處理耗時的任務

一個與I/O無關的耗時的任務:

getObservableApps(apps)
    .onBackpressureBuffer()
    .subscribeOn(Schedulers.computation())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<AppInfo>() { [...]

總結

RxJava提供了一種以面向時序的方式考慮數據的機會:所有事情都是持續變化的,數據在更新,事件在觸發,然后你就可以創建事件響應式的、靈活的、運行流暢的App。

謹記可觀測序列就像一條河:它們是流動的。你可以“過濾”(filter)一條河,你可以“轉換”(transform)一條河,你可以將兩條河合并(combine)成一個,然后依然暢流如初。最后,它就成了你想要的那條河。

“Be Water,my friend” - Bruce Lee

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容