響應式編程與RxJava

1. 響應式編程

1.1 響應式編程概念

  • 響應式編程是一種通過異步和數據流來構建事物關系的編程模型。
  • 事物的關系 也可以說成是 業務邏輯 ,是響應式編程的核心理念。
  • 數據流異步 是實現這個核心理念的關鍵。異步和數據流都是為了正確的構建事物的關系而存在的。

1.2 響應式編程demo

int a=1;
int b=a+1;
System.out.print(“b=”+b)    //  b=2
a=10;
System.out.print(“b=”+b)    //  b=2

上面是一段很常見的代碼,簡單的賦值打印語句,但是這種代碼有一個缺陷,那就是如果我們想表達的并不是一個賦值動作,而是b和a之間的關系,即無論a如何變化,b永遠比a大1。那么可以想見,我們就需要花額外的精力去構建和維護一個b和a的關系。
而響應式編程的想法正是企圖用某種操作符幫助你構建這種關系。
它的思想完全可以用下面的代碼片段來表達:

int a=1;
int b <= a+1;   // <= 符號只是表示a和b之間關系的操作符
System.out.print(“b=”+b)    //  b=2
a=10;
System.out.print(“b=”+b)    //  b=11

響應式編程的思想,它希望有某種方式能夠構建關系,而不是執行某種賦值命令。

應用初始化.png

比如在收單應用初始化邏輯中,先完成SDK初始化,數據庫初始化,簽到,才會跳轉到交易菜單界面。

在響應式編程中,這一流程可以這樣解讀


應用初始化2.png

在初始化過程中,SDK初始化,數據庫初始化,簽到這些業務完成之后才會去安排頁面跳轉的操作,那么這些上游的業務在自己工作完成之后,就需要通知下游,通知下游的方式有很多種,響應式編程的方式就是通過數據(事件)流。

每一個業務完成后,都會有一條數據(一個事件)流向下游,下游的業務收到這條數據(這個事件),才會開始自己的工作。

我們能發現SDK初始化,數據庫初始化,簽到這三個業務本身相互獨立,應當在不同的線程環境中執行,以保證他們不會相互阻塞。而假如沒有異步編程,我們可能只能在一個線程中順序調用這三個相對耗時較多的業務,最終再去做頁面跳轉,這樣做不僅沒有忠實反映業務本來的關系,而且會讓你的程序“反應”更慢。

總的來說,異步和數據流都是為了正確的構建事務的關系而存在的。只不過,異步是為了區分出無關的事務,而數據流(事件流)是為了聯系起有關的事務。

2. RxJava

Rx是響應式拓展,即支持響應式編程的一種拓展,用來處理事件和異步任務。

2.1 RxJava的優點

簡潔。而且當業務越繁瑣越復雜時這一點就越顯出優勢——它能夠保持簡潔。

2.2 RxJava的基本概念

我們都知道監聽者模式,訂閱模式這些概念。而Observable和Subscribers的英文意思就是如此。我們大概也知道差不多和監聽者模式差不多。

  • Observable事件源,被觀察者。
  • Observer / Subcriblers 觀察者,事件訂閱者
  • subscribe() 方法,綁定Observable與Subcribler或者Observabler
  • 事件 (包括 onNext,onComplete,onError 等事件)

以第一章的初始化應用為例:

Observable obserInitSDK=Observable.create((context)->{initSDK(context)}).subscribeOn(Schedulers.newThread())

Observable obserInitDB=Observable.create((context)->{initDatabase(context)}).subscribeOn(Schedulers.newThread())

Observable obserLogin=Observable.create((context)->{Login(context)})
                            .subscribeOn(Schedulers.newThread())
// 合并多個Observables的發射物                           
Observable observable = Observable.merge(obserInitSDK,obserInitDB,obserLogin)
// 訂閱被觀察者
observable.subscribe(()->{startActivity()})

當initSDK,initDB,Login都是耗時較長的操作時,遵照業務關系編寫響應式代碼可以極大的提高程序的執行效率,降低阻塞。
從上面代碼中,可以看出,響應式編程有如下優點

  • 在業務層面實現代碼邏輯分離,方便后期維護和拓展
  • 極大提高程序響應速度,充分發掘CPU的能力
  • 幫助開發者提高代碼的抽象能力和充分理解業務邏輯
  • Rx豐富的操作符會幫助我們極大的簡化代碼邏輯

2.3 操作符決策樹

RxJava的幾種主要操作符:

  • 創建操作:直接創建一個Observable
  • 組合操作:組合多個Observable
  • 變換操作:對Observable發射的數據執行變換操作
  • 過濾操作:從Observable發射的數據中取特定的值
  • 條件/布爾/過濾操作:轉發Observable的部分值
  • 算術/聚合操作:對Observable發射的數據序列求值

創建操作

用于創建Observable的操作符

  • Create — 通過調用觀察者的方法從頭創建一個Observable
  • Defer — 在觀察者訂閱之前不創建這個Observable,為每一個觀察者創建一個新的Observable
  • Empty/Never/Throw — 創建行為受限的特殊Observable
  • From — 將其它的對象或數據結構轉換為Observable
  • Interval — 創建一個定時發射整數序列的Observable
  • Just — 將對象或者對象集合轉換為一個會發射這些對象的Observable
  • Range — 創建發射指定范圍的整數序列的Observable
  • Repeat — 創建重復發射特定的數據或數據序列的Observable
  • Start — 創建發射一個函數的返回值的Observable
  • Timer — 創建在一個指定的延遲之后發射單個數據的Observable

變換操作

用于對Observable發射的數據進行變換

  • Buffer — 緩存,可以簡單的理解為緩存,它定期從Observable收集數據到一個集合,然后把這些數據集合打包發射,而不是一次發射一個
  • FlatMap — 扁平映射,將Observable發射的數據變換為Observables集合,然后將這些Observable發射的數據平坦化的放進一個單獨的Observable,可以認為是一個將嵌套的數據結構展開的過程。
  • GroupBy — 分組,將原來的Observable分拆為Observable集合,將原始Observable發射的數據按Key分組,每一個Observable發射一組不同的數據
  • Map — 映射,通過對序列的每一項都應用一個函數變換Observable發射的數據,實質是對序列中的每一項執行一個函數,函數的參數就是這個數據項
  • Scan — 掃描,對Observable發射的每一項數據應用一個函數,然后按順序依次發射這些值
  • Window — 窗口,定期將來自Observable的數據分拆成一些Observable窗口,然后發射這些窗口,而不是每次發射一項。類似于Buffer,但Buffer發射的是數據,Window發射的是Observable,每一個Observable發射原始Observable的數據的一個子集

過濾操作

用于從Observable發射的數據中進行選擇

  • Debounce — 只有在空閑了一段時間后才發射數據,通俗的說,就是如果一段時間沒有操作,就執行一次操作
  • Distinct — 去重,過濾掉重復數據項
  • ElementAt — 取值,取特定位置的數據項
  • Filter — 過濾,過濾掉沒有通過謂詞測試的數據項,只發射通過測試的
  • First — 首項,只發射滿足條件的第一條數據
  • IgnoreElements — 忽略所有的數據,只保留終止通知(onError或onCompleted)
  • Last — 末項,只發射最后一條數據
  • Sample — 取樣,定期發射最新的數據,等于是數據抽樣,有的實現里叫ThrottleFirst
  • Skip — 跳過前面的若干項數據
  • SkipLast — 跳過后面的若干項數據
  • Take — 只保留前面的若干項數據
  • TakeLast — 只保留后面的若干項數據

組合操作

用于將多個Observable組合成一個單一的Observable

  • And/Then/When — 通過模式(And條件)和計劃(Then次序)組合兩個或多個Observable發射的數據集
  • CombineLatest — 當兩個Observables中的任何一個發射了一個數據時,通過一個指定的函數組合每個Observable發射的最新數據(一共兩個數據),然后發射這個函數的結果
  • Join — 無論何時,如果一個Observable發射了一個數據項,只要在另一個Observable發射的數據項定義的時間窗口內,就將兩個Observable發射的數據合并發射
  • Merge — 將兩個Observable發射的數據組合并成一個
  • StartWith — 在發射原來的Observable的數據序列之前,先發射一個指定的數據序列或數據項
  • Switch — 將一個發射Observable序列的Observable轉換為這樣一個Observable:它逐個發射那些Observable最近發射的數據
  • Zip — 打包,使用一個指定的函數將多個Observable發射的數據組合在一起,然后將這個函數的結果作為單項數據發射

錯誤處理

用于從錯誤通知中恢復

  • Catch — 捕獲,繼續序列操作,將錯誤替換為正常的數據,從onError通知中恢復
  • Retry — 重試,如果Observable發射了一個錯誤通知,重新訂閱它,期待它正常終止

輔助操作

用于處理Observable的操作符

  • Delay — 延遲一段時間發射結果數據
  • Do — 注冊一個動作占用一些Observable的生命周期事件,相當于Mock某個操作
  • Materialize/Dematerialize — 將發射的數據和通知都當做數據發射,或者反過來
  • ObserveOn — 指定觀察者觀察Observable的調度程序(工作線程)
  • Serialize — 強制Observable按次序發射數據并且功能是有效的
  • Subscribe — 收到Observable發射的數據和通知后執行的操作
  • SubscribeOn — 指定Observable應該在哪個調度程序上執行
  • TimeInterval — 將一個Observable轉換為發射兩個數據之間所耗費時間的Observable
  • Timeout — 添加超時機制,如果過了指定的一段時間沒有發射數據,就發射一個錯誤通知
  • Timestamp — 給Observable發射的每個數據項添加一個時間戳
  • Using — 創建一個只在Observable的生命周期內存在的一次性資源

條件和布爾操作

用于單個或多個數據項,也可用于Observable

  • All — 判斷Observable發射的所有的數據項是否都滿足某個條件
  • Amb — 給定多個Observable,只讓第一個發射數據的Observable發射全部數據
  • Contains — 判斷Observable是否會發射一個指定的數據項
  • DefaultIfEmpty — 發射來自原始Observable的數據,如果原始Observable沒有發射數據,就發射一個默認數據
  • SequenceEqual — 判斷兩個Observable是否按相同的數據序列
  • SkipUntil — 丟棄原始Observable發射的數據,直到第二個Observable發射了一個數據,然后發射原始Observable的剩余數據
  • SkipWhile — 丟棄原始Observable發射的數據,直到一個特定的條件為假,然后發射原始Observable剩余的數據
  • TakeUntil — 發射來自原始Observable的數據,直到第二個Observable發射了一個數據或一個通知
  • TakeWhile — 發射原始Observable的數據,直到一個特定的條件為真,然后跳過剩余的數據

算術和聚合操作

用于整個數據序列

  • Average — 計算Observable發射的數據序列的平均值,然后發射這個結果
  • Concat — 不交錯的連接多個Observable的數據
  • Count — 計算Observable發射的數據個數,然后發射這個結果
  • Max — 計算并發射數據序列的最大值
  • Min — 計算并發射數據序列的最小值
  • Reduce — 按順序對數據序列的每一個應用某個函數,然后返回這個值
  • Sum — 計算并發射數據序列的和

另外還有連接操作轉換操作,可以通過文檔查看使用方法。

2.4 RxJava 基礎框架解析

  • 先從比較常用的create方法看
public static Completable create(CompletableOnSubscribe source) {
        ObjectHelper.requireNonNull(source, "source is null");
        return RxJavaPlugins.onAssembly(new CompletableCreate(source));
    }

在 create 方法中,其實很簡單,只是對 source 進行判空處理,并將 source 用 ObservableCreate 包裝起來,并返回回去。下面讓我們一起來看一下 ObservableCreate方法

public final class CompletableCreate extends Completable {

    final CompletableOnSubscribe source;

    public CompletableCreate(CompletableOnSubscribe source) {
        this.source = source;
    }

    // daizy -- 持有了上游 source 的引用,并重寫 subscribeActual 方法
    @Override
    protected void subscribeActual(CompletableObserver observer) {
        Emitter parent = new Emitter(observer);
        observer.onSubscribe(parent);

        try {
            source.subscribe(parent);
        } catch (Throwable ex) {
            Exceptions.throwIfFatal(ex);
            parent.onError(ex);
        }
    }

ObservableCreate 也很簡單,它是 Observable 的子類,持有了上游 source 的引用,并重寫 subscribeActual 方法,這個方法要結合訂閱Subscribe源碼看。

@SchedulerSupport(SchedulerSupport.NONE)
    @Override
    public final void subscribe(CompletableObserver observer) {
        // 檢查 observer 是否為 null,為 null 拋出異常
        ObjectHelper.requireNonNull(observer, "observer is null");
        try {
            // RxJavaPlugins 插件的,暫時不管
            observer = RxJavaPlugins.onSubscribe(this, observer);
            // 檢查 observer 是否為 null,為 null 拋出異常
            ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null CompletableObserver. Please check the handler provided to RxJavaPlugins.setOnCompletableSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins");

            subscribeActual(observer);
        } catch (NullPointerException ex) { // NOPMD
            throw ex;
        } catch (Throwable ex) {
            Exceptions.throwIfFatal(ex);
            RxJavaPlugins.onError(ex);
            throw toNpe(ex);
        }
    }

subscribe 方法也比較簡單,大概可以分為以下兩步:

  • 第一步,對observer 進行判空,為空則拋出異常
  • 第二步,調用 subscribeActual 方法,在Observable類 中,subscribeActual 是一個抽象方法,要關注的是其實現類的subscribeActual方法。從上面的分析,我們知道,當我們調用 Observable create(ObservableOnSubscribe source) 方法的時候,最終會返回 ObservableCreate 實例。因此,我們只需要關注 ObservableCreate 的 subscribeActual 方法。
protected void subscribeActual(Observer<? super T> observer) {
        // CreateEmitter 是 ObservableCreate 的一個靜態內部類
        CreateEmitter<T> parent = new CreateEmitter<T>(observer);
        observer.onSubscribe(parent);

        try {
            // source 是上游 ObservableOnSubscribe 的引用
            source.subscribe(parent);
        } catch (Throwable ex) {
            Exceptions.throwIfFatal(ex);
            parent.onError(ex);
        }
    }

繼續看ObservableCreate的subscribeActual方法,在執行observer.onSubscribe 方法的時候,會將parent對象作為方法參數暴露出去,parent即是CreateEmitter,可以通過它的dispose方法取消訂閱關系。
接著在調用source.subscribe(parent)的時候,會先調用ObservableOnSubscribe 的 subscribe 方法。
因此,我們可以得出,調用的順序是:
Observable.subscrible -> Observable.subscribleActual -> Observable.subscribleActual -> observer.onSubscribe -> ObservableOnSubscribe.subscribe(emitter)
emitter是CreateEmitter的實例,包裝了observe,調用emitter的方法,就會調用observe的 onNext 、onComolete/onError方法。

以上是RxJava基本原理,Observable 和 Observer 通過 subscribe() 方法實現訂閱關系,從而 Observable 可以在需要的時候發出事件來通知 Observer,并且回調 Observer 的相應的方法。

2.5 RxJava 線程切換

Observable通過subscribeOn方法來指定線程

public final Observable<T> subscribeOn(Scheduler scheduler) {
        ObjectHelper.requireNonNull(scheduler, "scheduler is null");
        return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<T>(this, scheduler));
    }

通過代碼可以看出,先對scheduler進行判空,然后用ObservableSubscribeOn 將scheduler 包裝起來,接下來研究看看ObservableSubscribeOn這個類的源碼。

final Scheduler scheduler;

    public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) {
        super(source);
        this.scheduler = scheduler;
    }

    @Override
    public void subscribeActual(final Observer<? super T> observer) {
        final SubscribeOnObserver<T> parent = new SubscribeOnObserver<T>(observer);

        observer.onSubscribe(parent);

        parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
    }

首先先來看他的構造函數 ,有兩個參數 source ,scheduler。

  • source 代表上游的引用,是 Observable 的一個實例
  • scheduler 調度器可以通過 Schedulers.newThread() 或者 Schedulers.io() 創建相應的實例。
    RxJava 可用的調度器大概有下面幾種,根據需求選擇:
圖片.png

我們主要看下這個方法

parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));

SubscribeTask 這個類,他是 ObservableSubscribeOn 的一個非靜態內部類,可以看到 其實也比較簡單,他實現了 Runnable 接口,并且持有 parent 引用。

 final class SubscribeTask implements Runnable {
        private final SubscribeOnObserver<T> parent;

        SubscribeTask(SubscribeOnObserver<T> parent) {
            this.parent = parent;
        }

        @Override
        public void run() {
            source.subscribe(parent);
        }
    }

在 run 方法中,通過 source.subscribe(parent) 建立聯系。因而,當我們的 SubscribeTask 的 run 方法運行在哪個線程,相應的 observer 的 subscribe 方法就運行在哪個線程。

接下來再看看scheduleDirect的實現

public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {
        final Worker w = createWorker();

        final Runnable decoratedRun = RxJavaPlugins.onSchedule(run);

        DisposeTask task = new DisposeTask(decoratedRun, w);

        w.schedule(task, delay, unit);

        return task;
    }

這個方法主要是將task包裝成DisposeTask,然后通過Worker進行調度。再看看Worker 是在做啥。
Scheduler我們以NewThreadScheduler為例子

public final class NewThreadScheduler extends Scheduler {

    final ThreadFactory threadFactory;

    private static final String THREAD_NAME_PREFIX = "RxNewThreadScheduler";
    /**
     * daizy -- 線程池
     */
    private static final RxThreadFactory THREAD_FACTORY;

    /** The name of the system property for setting the thread priority for this Scheduler. */
    private static final String KEY_NEWTHREAD_PRIORITY = "rx2.newthread-priority";

    static {
        int priority = Math.max(Thread.MIN_PRIORITY, Math.min(Thread.MAX_PRIORITY,
                Integer.getInteger(KEY_NEWTHREAD_PRIORITY, Thread.NORM_PRIORITY)));

        THREAD_FACTORY = new RxThreadFactory(THREAD_NAME_PREFIX, priority);
    }

    public NewThreadScheduler() {
        this(THREAD_FACTORY);
    }

    public NewThreadScheduler(ThreadFactory threadFactory) {
        this.threadFactory = threadFactory;
    }


    @NonNull
    @Override
    public Worker createWorker() {
        // 通過線程池來調度
        return new NewThreadWorker(threadFactory);
    }
}

通過代碼可以看出來,Worker里頭封裝了線程池,所以RxJava的線程切換,也是基于線程池來處理。

回過來看DisposeTask

static final class DisposeTask implements Disposable, Runnable, SchedulerRunnableIntrospection {

        @NonNull
        final Runnable decoratedRun;

        @NonNull
        final Worker w;

        @Nullable
        Thread runner;

        DisposeTask(@NonNull Runnable decoratedRun, @NonNull Worker w) {
            this.decoratedRun = decoratedRun;
            this.w = w;
        }

        @Override
        public void run() {
            runner = Thread.currentThread();
            try {
                decoratedRun.run();
            } finally {
                dispose();
                runner = null;
            }
        }

        @Override
        public void dispose() {
            if (runner == Thread.currentThread() && w instanceof NewThreadWorker) {
                ((NewThreadWorker)w).shutdown();
            } else {
                w.dispose();
            }
        }

        @Override
        public boolean isDisposed() {
            return w.isDisposed();
        }

        @Override
        public Runnable getWrappedRunnable() {
            return this.decoratedRun;
        }
    }

DisposeTask 實現了 Disposable,Runnable ,SchedulerRunnableIntrospection 接口,Disposable 接口主要是用來取消訂閱關系的 Disposable。

從上面的分析,可以得出Observable.subscribeOn方法,控制Observable的執行線程是通過將 Observable.subscribe(Observer) 的操作放在了指定線程中,當我們調用 subcribe 的時候,它的過程是從下往上的,即下面的 Observable 調用上面的 Observable。

用流程圖描述如下:


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

推薦閱讀更多精彩內容

  • 響應式編程簡介 響應式編程是一種基于異步數據流概念的編程模式。數據流就像一條河:它可以被觀測,被過濾,被操作,或者...
    說碼解字閱讀 3,080評論 0 5
  • 轉一篇文章 原地址:http://gank.io/post/560e15be2dca930e00da1083 前言...
    jack_hong閱讀 933評論 0 2
  • 一、RxJava操作符概述 RxJava中的操作符就是為了提供函數式的特性,函數式最大的好處就是使得數據處理簡潔易...
    無求_95dd閱讀 3,152評論 0 21
  • 注:只包含標準包中的操作符,用于個人學習及備忘參考博客:http://blog.csdn.net/maplejaw...
    小白要超神閱讀 2,210評論 2 8
  • 一、Retrofit詳解 ·Retrofit的官網地址為 : http://square.github.io/re...
    余生_d630閱讀 1,886評論 0 5