Android 響應式編程 RxJava2 完全解析

使用了 RxJava2 有一段時間了,深深感受到了其“牛逼”之處。下面,就從 RxJava2 的基礎開始,一步步與大家分享一下這個強大的異步庫的用法!RxJava 是 一個在 Java VM 上使用可觀測的序列來組成異步的、基於事件的程序的庫,也就是用於實現異步操作的庫。

1.RxJava2 基礎

RxJava可以濃縮爲異步兩個字,其核心的東西不外乎兩個, Observables(被觀察者) 和 Observable(觀察者)。Observables可以發出一系列的 事件(例如網絡請求、複雜計算、數據庫操作、文件讀取等),事件執行結束後交給Observable 的回調處理。

1.RxJava2 的觀察者模式

觀察者模式是對象的行爲模式,也叫做發佈-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。

什麼是觀察者模式?舉個栗子,Android中View的點擊監聽器的實現,View是被觀察者,OnClickListener對象是觀察者,Activity要如何知道View被點擊了?那就是派一個OnClickListener對象,入駐View,與View達成一個訂閱關係,一旦View被點擊了,就通過OnClickListener對象的OnClick方法傳達給Activity。採用觀察者模式可以避免去輪詢檢查,節約有限的cpu資源。

RxJava 作爲一個工具庫,使用的便是通用形式的觀察者模式:

image

普通事件:onNext(),相當於 onClick()、onEvent();特殊事件:onCompleted() 和 onError()

如圖所示,RxJava 的基本概念分別爲:Observable(被觀察者,事件源),Observer(觀察者,訂閱者),subscribe (訂閱)、事件;不同的是,RxJava 把多個事件看做一個隊列,並對每個事件單獨處理。在一個隊列中 onCompleted() 和 onError(),只有一個會被調用。如果調用了 onCompleted() 就說明隊列執行完畢,沒有出現異常,否則調用 onError() 方法並終止隊列。

2.RxJava2 響應式編程結構

什麼是響應式編程?舉個栗子,a = b + c; 這句代碼將b+c的值賦給a,而之後如果b和c的值改變了不會影響到a,然而,對於響應式編程,之後b和c的值的改變也動態影響着a,意味着a會隨着b和c的變化而變化。

響應式編程的組成爲Observable/Operator/Subscriber,RxJava在響應式編程中的基本流程如下:

這個流程,可以簡單的理解爲:Observable -> Operator1 -> Operator2 -> Operator3 -> Subscriber

  1. Observable發出一系列事件,他是事件的產生者;
  2. Subscriber負責處理事件,他是事件的消費者;
  3. Operator是對Observable發出的事件進行修改和變換;
  4. 若事件從產生到消費不需要其他處理,則可以省略掉中間的Operator,從而流程變爲Obsevable -> Subscriber;
  5. Subscriber通常在主線程執行,所以原則上不要去處理太多的事務,而這些複雜的處理則交給Operator;
    3.創建一個完整的 RxJava2 調用

首先需要添加 RxJava2 在 Android 中的 Gradle 依賴:

compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile "io.reactivex.rxjava2:rxjava:2.0.8"

RxJava2 可以通過下面這幾種方法創建被觀察者:

// 發送對應的方法
Observable.create(new ObservableOnSubscribe<String>() {
    // 默認在主線程裏執行該方法
    @Override
    public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception {
        e.onNext("Hello");
        e.onNext("World");
        // 結束標識
        e.onComplete();
    }
});
// 發送多個數據
Observable.just("Hello", "World");
// 發送數組
Observable.fromArray("Hello", "World");
// 發送一個數據
Observable.fromCallable(new Callable<String>() {
    @Override
    public String call() throws Exception {
        return "Hello";
    }
});

RxJava2 支持鏈式編程,下來我們創建被觀察者,然後創建觀察者並訂閱:

// 創建被觀察者
Observable.just("Hello", "World")
// 將被觀察者切換到子線程
.subscribeOn(Schedulers.io())
// 將觀察者切換到主線程
.observeOn(AndroidSchedulers.mainThread())
// 創建觀察者並訂閱
.subscribe(new Observer<String>() {
    // Disposable 相當於RxJava1.x中的 Subscription,用於解除訂閱
    private Disposable disposable;
    @Override
    public void onSubscribe(Disposable d) {
        disposable = d;
    }
    @Override
    public void onNext(String s) {
        Log.i("JAVA", "被觀察者向觀察者發送的數據:" + s);
        if (s == "-1") {   // "-1" 時爲異常數據,解除訂閱
            disposable.dispose();
        }
    }
    @Override
    public void onError(Throwable e) {
    }
    @Override
    public void onComplete() {
    }
});

一旦 Observer 訂閱了 Observable,Observable 就會調用 Observer 的 onNext()、onCompleted()、onError() 等方法。至此一個完整的 RxJava 調用就完成了。看一下輸出的Log:

I/JAVA: 被觀察者向觀察者發送的數據:Hello

I/JAVA: 被觀察者向觀察者發送的數據:World

若喜歡簡潔、定製服務,那麼可以實現的方法跟上面的實現方法是對應起來的,大家看參數就知道哪個對應哪個了,你可以通過new Consumer(不需要實現的方法你可以不寫,看上去更簡潔),Consumer就是消費者的意思,可以理解爲消費了 onNext 等事件:

Observable.just("Hello", "World")
.subscribe(new Consumer<String>() {
    @Override
    public void accept(@NonNull String s) throws Exception {
        Log.i("JAVA", "被觀察者向觀察者發送的數據:" + s);
    }
}, new Consumer<Throwable>() {
    @Override
    public void accept(@NonNull Throwable throwable) throws Exception {
    }
}, new Action() {
    @Override
    public void run() throws Exception {
    }
}, new Consumer<Disposable>() {
    @Override
    public void accept(@NonNull Disposable disposable) throws Exception {
    }
});

4.RxJava2 的操作符

RxJava中提供了大量不同種類,不同場景的Operators(操作符),RxJava的強大性就來自於它所定義的操作符。主要分類:

RxJava 的操作符 說明 例如
創建操作 用於創建Observable的操作符 create、defer、from、just、start、repeat、range
變換操作 用於對Observable發射的數據進行變換 buffer、window、map、flatMap、groupBy、scan
過濾操作 用於從Observable發射的數據中進行選擇 debounce、distinct、filter、sample、skip、take
組合操作 用於將多個Observable組合成一個單一的Observable and、startwith、join、merge、switch、zip
異常處理 用於從錯誤通知中恢復 catch、retry
輔助操作 用於處理Observable的操作符 delay、do、observeOn、subscribeOn、subscribe
條件和布爾操作 all、amb、contains、skipUntil、takeUntil
算法和聚合操作 average、concat、count、max、min、sum、reduce
異步操作 start、toAsync、startFuture、FromAction、FromCallable、runAsync
連接操作 connect、publish、refcount、replay
轉換操作 toFuture、toList、toIterable、toMap、toMultiMap
阻塞操作 forEach、first、last、mostRecent、next、single
字符串操作 byLine、decode、encode、from、join、split、stringConcat

其中有一些高頻使用的操作符如下:

常用操作符 說明
interval 延時幾秒,每隔幾秒開始執行
take 超過多少秒停止執行
map 類型轉換
observeOn 在主線程運行
doOnSubscribe 在執行的過程中
subscribe 訂閱

5.RxJava2 線程調度器

調度器 Scheduler 用於控制操作符和被觀察者事件所執行的線程,不同的調度器對應不同的線程。RxJava提供了5種調度器:

RxJava 線程調度器 說明
Schedulers.immediate() 默認線程,允許立即在當前線程執行所指定的工作。
Schedulers.newThread() 新建線程,總是啓用新線程,並在新線程執行操作。
Schedulers.io() 適用於I/O操作,根據需要增長或縮減來自適應的線程池。多數情況下 io() 比 newThread() 更有效率。不要把計算工作放在 io() 中,可以避免創建不必要的線程。
Schedulers.computation() 適用於計算工作(CPU 密集型計算),即不會被 I/O 等操作限制性能的操作。這個 Scheduler 使用的固定的線程池,大小爲 CPU 核數。不要把 I/O 操作放在 computation() 中,否則 I/O 操作的等待時間會浪費 CPU。
Schedulers.trampoline() 當我們想在當前線程執行一個任務時,並不是立即,我們可以用.trampoline()將它入隊。這個調度器將會處理它的隊列並且按序運行隊列中每一個任務。
AndroidSchedulers.mainThread() RxAndroid 提供的,它指定的操作將在 Android 主線程運行。

可以使用 subscribeOn() 和 ObserveOn() 操作符進行線程調度,讓 Observable 在一個特定的調度器上執行。subscribeOn() 指定 subscribe() 所發生的線程,事件產生的線程。ObserveOn() 指定 Observer 所運行在的線程,事件消費的線程。

6.RxJava2 模擬發送驗證碼倒計時功能:

public void onCodeClick() {
    final long count = 60; // 設置60秒
    Observable.interval(0, 1, TimeUnit.SECONDS)
            .take(count + 1)
            .map(new Function<Long, Long>() {
                @Override
                public Long apply(@NonNull Long aLong) throws Exception {
                    return count - aLong; // 由於是倒計時,需要將倒計時的數字反過來
                }
            })
            .observeOn(AndroidSchedulers.mainThread())
            .doOnSubscribe(new Consumer<Disposable>() {
                @Override
                public void accept(@NonNull Disposable disposable) throws Exception {
                    button.setEnabled(false);
                    button.setTextColor(Color.GRAY);
                }
            })
            .subscribe(new Observer<Long>() {
                @Override
                public void onSubscribe(Disposable d) {
                }
                @Override
                public void onNext(Long aLong) {
                    button.setText(aLong + "秒後重發");
                }
                @Override
                public void onError(Throwable e) {
                }
                @Override
                public void onComplete() {
                    button.setEnabled(true);
                    button.setTextColor(Color.RED);
                    button.setText("發送驗證碼");
                }
            });
}

2.RxJava2 系列框架

RxJava 框架 說明 開源地址
RxAndroid 針對 Android 平臺的擴展框架,方便 RxJava 用於 Android 開發,目前 RxAndroid 主要的功能是對 Android 主線程的調度 AndroidSchedulers.mainThread()。 https://github.com/ReactiveX/RxAndroid
DataBinding DataBinding 是基於MVVM思想實現數據和UI綁定的的框架,支持雙向綁定。 DataBinding 是一個support庫,最低支持到Android 2.1
RxBinding 基於 RxJava 的用於綁定 Android UI 控件的框架,它可以異步獲取並處理控件的各類事件(例如點擊事件、文字變化、選中狀態) https://github.com/JakeWharton/RxBinding
Retrofit 網絡請求框架,Retrofit 結合 RxJava 簡化請求流程。 https://github.com/square/retrofit
RxPermissions 動態權限管理框架,動態權限內容可參考Android 6.0+ 運行時權限處理。 https://github.com/tbruyelle/RxPermissions
RxLifecycle 生命週期綁定,提供了基於 Activity 和 Fragment 生命週期事件的自動完成隊列,用於避免不完整回調導致的內存泄漏。 https://github.com/trello/RxLifecycle
RxBus 是一種基於RxJava實現事件總線的一種思想。可以替代EventBus/Otto,因爲他們都依賴於觀察者模式。 https://github.com/AndroidKnife/RxBus

3.RxJava2 與 Retrofit 的使用

RxJava 與 Retrofit 的使用,更像我們的 AsyncTask,通過網絡獲取數據然後通過 Handler 更新UI。首先需要導入依賴:

compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'

1.模擬用戶登陸獲取用戶數據

image

1.Bean對象:

public class UserParam {
    private String param1;
    private String param2;
    public UserParam(String param1, String param2) {
        this.param1 = param1;
        this.param2 = param2;
    }
    // 省略了 getter setter
}
public class NetBean {
    private FormBean form;
    // 省略了 getter setter
    public static class FormBean {
        private String username;
        private String password;
        // 省略了 getter setter
    }
}
public class UserBean {
    private String username;
    private String password;
    public UserBean(String username, String password) {
        this.username = username;
        this.password = password;
    }
    // 省略了 getter setter
}

2.ApiService,這裏返回Observable對象,也就是我們RxJava的被觀察者

public interface ApiService {
    @FormUrlEncoded
    @POST("/post")
    Observable<NetBean> getUserInfo(@Field("username")String username,
                                    @Field("password")String password);
}

3.RxJava + Retrofit 的實現

// 構建Retrofit
ApiService apiService = new Retrofit.Builder()
        .baseUrl("http://httpbin.org/")
        .addConverterFactory(GsonConverterFactory.create()) // RxJava2與Gson混用
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // RxJava2與Retrofit混用
        .build()
        .create(ApiService.class);

// 構建RxJava
UserParam param = new UserParam("zhangsan", "123");
// 發送param參數
Observable.just(param)
        // flatMap方法是用於數據格式轉換的方法,參數一表示原數據,
        // 參數二表示轉換的數據,那麼就是通過發送網絡參數,轉換成網絡返回的數據,調用Retrofit
        .flatMap(new Function<UserParam, ObservableSource<NetBean>>() {
            @Override
            public ObservableSource<NetBean> apply(@NonNull UserParam userParam)
                    throws Exception {
                // 1.發送網絡請求,獲取NetBean
                return apiService.getUserInfo(userParam.getParam1(), userParam.getParam2());
            }
        })
        .flatMap(new Function<NetBean, ObservableSource<UserBean>>() {
            @Override
            public ObservableSource<UserBean> apply(@NonNull NetBean netBean)
                    throws Exception {
                UserBean user = new UserBean(netBean.getForm().getUsername(),
                        netBean.getForm().getPassword());
                // 2.轉換NetBean數據爲我們需要的UserBean數據
                return Observable.just(user);
            }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<UserBean>() {
            @Override
            public void accept(@NonNull UserBean userBean) throws Exception {
                Log.i("JAVA", "" + "用戶名:" + userBean.getUsername()
                        + ", 密碼:" + userBean.getPassword());
            }
        });

2.模擬合併本地與服務器購物車列表

這個案例其實就是用戶添加購物車的時候,首先會在本地存儲一份,然後發現如果沒有網絡,那麼沒辦法提交到服務器上,只能等下一次有網絡的時候採用本地數據庫和服務器數據的合併來實現上傳到服務器。

image

首先需要準備 Retrofit 對象和獲取本地數據、網絡數據的方法:

private ApiService apiService;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 省略
    // 構建Retrofit
    apiService = new Retrofit.Builder()
            .baseUrl("http://httpbin.org/")
            .addConverterFactory(GsonConverterFactory.create()) // RxJava2與Gson混用
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // RxJava2與Retrofit混用
            .build()
            .create(ApiService.class);
}
/**
 * 獲取本地數據
 */
private Observable<List<String>> getDataForLocal() {
    List<String> list = new ArrayList<>();
    list.add("購物車的商品1");
    list.add("購物車的商品2");
    return Observable.just(list);
}
/**
 * 獲取網絡數據
 */
private Observable<List<String>> getDataForNet() {
    return Observable.just("shopName")
        // flatMap方法是用於數據格式轉換的方法,參數一表示原數據,
        // 參數二表示轉換的數據,那麼就是通過發送網絡參數,轉換成網絡返回的數據,調用Retrofit
        .flatMap(new Function<String, ObservableSource<NetBean>>() {
            @Override
            public ObservableSource<NetBean> apply(@NonNull String s) throws Exception {
                // 1.發送網絡請求,獲取數據
                return apiService.getCartList(s);
            }
        }).flatMap(new Function<NetBean, ObservableSource<List<String>>>() {
            @Override
            public ObservableSource<List<String>> apply(@NonNull NetBean netBean) throws Exception {
                // String shop = netBean.get_$Args257().getShopName();
                String shop = "購物車的商品3";
                List<String> list = new ArrayList<>();
                list.add(shop);
                // 2.轉換NetBean數據爲我們需要的List<String>數據
                return Observable.just(list);
            }
        }).subscribeOn(Schedulers.io());
}

然後就可以創建被觀察者並訂閱了,來完成合並本地與服務器購物車列表操作:

// merge操作符: 將兩個ObservableSource合併爲一個ObservableSource
Observable.merge(getDataForLocal(), getDataForNet())
        .subscribe(new Observer<List<String>>() {
            @Override
            public void onSubscribe(Disposable d) {
            }
            @Override
            public void onNext(List<String> strings) {
                for (String str: strings) { Log.i("JAVA", str); }
            }
            @Override
            public void onError(Throwable e) {
            }
            @Override
            public void onComplete() {
                Log.i("JAVA", "onComplete");
            }
        });

最後的打印結果是:

I/JAVA: 購物車的商品1

I/JAVA: 購物車的商品2

I/JAVA: 購物車的商品3

I/JAVA: onComplete

4.RxJava2 與 RxBinding 的使用

1.優化搜索請求

當我們在 EditText 打字時搜索的時候,可能用戶會打字很會快,那麼我們就沒有必要一直髮送網絡請求,請求搜索結果,我們可以通過當用戶打字停止後的延時500毫秒再發送搜索請求:

// RxTextView.textChanges(edittext): Rxbinding用法
RxTextView.textChanges(editText)
        // 表示延時多少秒後執行,當你敲完字之後停下來的半秒就會執行下面語句
        .debounce(500, TimeUnit.MILLISECONDS)
        // 數據轉換 flatMap: 當同時多個數據請求訪問的時候,前面的網絡數據會覆蓋後面的網絡數據
        // 數據轉換 switchMap: 當同時多個網絡請求訪問的時候,會以最後一個發送請求爲準,前面網絡數據會被最後一個覆蓋
        .switchMap(new Function<CharSequence, ObservableSource<List<String>>>() {
            @Override
            public ObservableSource<List<String>> apply(
                    @NonNull CharSequence charSequence) throws Exception {
                // 網絡請求操作,獲取我們需要的數據
                List<String> list = new ArrayList<String>();
                list.add("2017");
                list.add("2018");
                return Observable.just(list);
            }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<List<String>>() {
            @Override
            public void accept(@NonNull List<String> strings) throws Exception {
                // 更新UI
                Log.i("JAVA", strings.toString());
            }
        });

2.優化點擊請求

當用戶一直點擊一個按鈕的時候,我們不應該一直調用訪問網絡請求,而是 1秒內,只執行一次網絡請求。

RxView.clicks(button).throttleFirst(1, TimeUnit.SECONDS)
        .subscribe(new Observer<Object>() {
            @Override
            public void onSubscribe(Disposable d) {
            }
            @Override
            public void onNext(Object o) {
                Log.i("JAVA", "onClick");
            }
            @Override
            public void onError(Throwable e) {
            }
            @Override
            public void onComplete() {
            }
        });

5.RxJava2 踩過的一些坑

1.未解除訂閱而引起的內存泄漏

舉個例子,對於前面常用操作符 interval 做週期性操作的例子,並沒有使之停下來的,沒有去控制訂閱的生命週期,這樣,就有可能引發內存泄漏。所以,在 Activity 的 onDestroy() 方法執行的時候或者不需要繼續執行的時候應該解除訂閱。

更多 RxJava 示例代碼請訪問:https://github.com/smartbetter/RxJavaDemo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章