1. 程式人生 > >Android實戰——RxJava2+Retrofit+RxBinding解鎖各種新姿勢

Android實戰——RxJava2+Retrofit+RxBinding解鎖各種新姿勢

本文已授權微信公眾號:鴻洋(hongyangAndroid)原創首發。

前言

作為主流的第三方框架Rx系列,不學習也不行啊,對於初學者來說,可能RxJava看起來很難,用起來更難,但是你要知道,越複雜的東西往往能解決越複雜的問題,有可能你應用在專案中,也許你在面試的時候,就會和初級工程師拉開一大段距離。這門課程需要大家有Retrofit的基礎,如果想學習Retrofit的同學可以檢視我的部落格,廢話不多說,Hensen老師開車了。

RxJava2的介紹

用原話就是:RxJava2是一個在Java虛擬機器上,使用可觀察的序列構成基於事件的,非同步的程式庫。不理解沒關係,可以類比成我們的AsyncTask,這樣就好理解多了

RxJava2觀察者模式的介紹

觀察者模式就是RxJava使用的核心點,掌握這個模式,可以理解RxJava更簡單,觀察者模式簡單的說就是”訂閱-釋出”的模式,舉一例子說,當你訂閱某家牛奶店的早餐奶(訂閱過程),只要牛奶店生產牛奶,便會給你送過去(釋出過程)。這裡的牛奶店只有一家,但是訂閱的人可以很多,這是一種一對多的關係,只要牛奶店釋出牛奶,那麼訂閱的人就會收到牛奶。換做RxJava裡面的話,牛奶店就是被觀察者(Observable),訂閱的人就是觀察者(Observer)

RxJava2觀察者模式的使用

這裡我們舉一例子學校點名的例子,首先建立我們所說的觀察者和被觀察者

public interface Observable {
    //訂閱
    public void attach(Observer observer);
    //取消訂閱
    public void detach(Observer observer);
    //釋出
    public void notifyObservers(String message);
}
public interface Observer {
    //給個名字來分辨不同的觀察者
    void setName(String name);
    //觀察者的方法
    void say(String message);
}

各位可以思考一下,根據上面的介紹,學生和老師,誰是觀察者,誰是被觀察者,下面就看程式碼給你分析

public class Teather implements Observable {

    private List<Observer> observers = new ArrayList<>();

    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.say(message);
        }
    }
}
public class Student implements Observer {

    private String name;

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void say(String message) {
        System.out.println(message + ":" + this.name + "到");
    }
}

通過程式碼可以看到,注意分別實現的不同介面

1、老師是被觀察者,他需要實現介面的方法

  • 訂閱/取消訂閱:往集合中存放/移除觀察者
  • 釋出:迴圈遍歷觀察者,呼叫觀察者方法

2、學生是觀察者,那麼我們只需要給他個名字,實現觀察者的方法即可

最後,我們就把觀察者和被觀察者關聯起來,LessonStart (上課啦)

public class LessonStart {

    public static void main(String[] args){

        Observable teather = new Teather();

        Observer xiaoming = new Student();
        xiaoming.setName("小明");
        Observer xiaohong = new Student();
        xiaohong.setName("小紅");

        teather.attach(xiaoming);
        teather.attach(xiaohong);

        teather.notifyObservers("點名啦");
    }
}

程式碼很簡單,我們模擬了一個老師和小明同學和小紅同學,老師已經知道看到兩個人來了,那麼可以開始點名了,下面通過Log打印出資訊

點名啦:小明到
點名啦:小紅到

RxJava2的基本使用

首先我先貼出我們後面所用到的第三方依賴庫,以免後面忘記說了,大家對號入座

//retrofit
compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
//rx+retrofit
compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
//rxjava
compile "io.reactivex.rxjava2:rxjava:2.0.8"
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
//rxbinding
compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
compile 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
compile 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
compile 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
compile 'com.jakewharton.rxbinding2:rxbinding-recyclerview-v7:2.0.0'

其次還需要新增聯網許可權

<uses-permission android:name="android.permission.INTERNET" />

最後我們回到正題,看完上面的例子,我們可以知道RxJava就是這種訂閱和釋出的模式,換成我們的RxJava程式碼應該是怎麼樣的?當然也是通過被觀察者訂閱觀察者啦

//拿到被觀察者
Observable<String> observable = getObservable();
//拿到觀察者
Observer<String> observer = getObserver();
//訂閱
observable.subscribe(observer);

我們具體被觀察者和觀察者的實現,當然是創建出來啦

public Observable<String> getObservable() {
       return Observable.create(new ObservableOnSubscribe<String>() {
           @Override
           public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception {
               e.onNext("俊俊俊很帥");
               e.onNext("你值得擁有");
               e.onNext("取消關注");
               e.onNext("但還是要保持微笑");
               e.onComplete();
           }
       });
}

onNext方法就是我們的釋出過程,我們看其觀察者的建立就知道怎麼回事了

public Observer<String> getObserver() {
    return new Observer<String>() {

        Disposable disposable = null;

        @Override
        public void onSubscribe(Disposable d) {
            disposable = d;
        }

        @Override
        public void onNext(String s) {
            Log.e("onNext", s);
            if (s.equals("取消關注")) {
                //斷開訂閱
                disposable.dispose();
            }
        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onComplete() {
            Log.e("onComplete", "onComplete");
        }
    };
}

我們可以發現,觀察者的建立實現的方法,在被觀察者中是對應起來的,也就是說,我們釋出了什麼,就可以在觀察者中收到訂閱資訊,那麼我們就可以在程式碼中編寫我們的邏輯了,這樣基本上已經使用好了RxJava了,通過Log打印出資訊

1、人類就喜歡酷炫,炫耀,當然RxJava也少不了人類這種心理,就是鏈式程式設計,下面這段程式碼可以完美替代上面的所有程式碼

//建立被觀察者
Observable.create(new ObservableOnSubscribe<String>() {
    @Override
    //預設在主執行緒裡執行該方法
    public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception {
        e.onNext("俊俊俊很帥");
        e.onNext("你值得擁有");
        e.onNext("取消關注");
        e.onNext("但還是要保持微笑");
        e.onComplete();
    }
})
//將被觀察者切換到子執行緒
.subscribeOn(Schedulers.io())
//將觀察者切換到主執行緒
.observeOn(AndroidSchedulers.mainThread())
//建立觀察者並訂閱
.subscribe(new Observer<String>() {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(String s) {
        Log.e("onNext", s);
    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
});

這裡我多寫了兩個方法,也就是.subscribeOn(Schedulers.io())和.observeOn(AndroidSchedulers.mainThread()),這裡就是RxJava的好處之一,他可以手動切換執行緒,這兩個方法在這裡表示被觀察者建立實現的方法都放在io執行緒也就是子執行緒,因為在被觀察者中通常會呼叫網路資料請求,那麼網路請求必須在子執行緒執行,當網路請求收到後,則釋出出去,在觀察者中通過TextView等控制元件展示在介面上,那麼UI的更新必須在主執行緒進行,也就是我們上面的程式碼mainThread。如果你不深造RxJava,基本上這兩個方法已經成了固定的寫法,這也是很多初學者忘記新增上去的方法

2、久而久之,人類喜歡簡潔,喜歡定製服務,巧了,RxJava也給你滿足了,下面這段程式碼中,實現的方法跟上面的實現方法是對應起來的,大家看引數就知道哪個對應哪個了,你可以通過new Consumer,不需要實現的方法你可以不寫,看上去更簡潔,這裡我為了方便大家看,都new出來了,Consumer就是消費者的意思,可以理解為消費了onNext等等等事件

Observable<String> observable = getObservable();
observable.subscribe(new Consumer<String>() {
    @Override
    public void accept(@NonNull String s) throws Exception {
        Log.e("accept", 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 {

    }
});

哦,對了,我們還忘記列印Log資訊,不能否認了我很帥這個事實

04-03 01:32:48.445 13512-13512/com.handsome.boke2 E/onNext: 俊俊俊很帥
04-03 01:32:48.446 13512-13512/com.handsome.boke2 E/onNext: 你值得擁有
04-03 01:32:48.446 13512-13512/com.handsome.boke2 E/onNext: 取消關注

當然你覺得只要誇獎我一個帥就行了,那麼你也可以通過下面這幾種方法傳送給觀察者

public Observable<String> getObservable() {
    //1、可傳送對應的方法
    return Observable.create(new ObservableOnSubscribe<String>() {
        @Override
        public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception {
            e.onNext("俊俊俊很帥");
            e.onNext("你值得擁有");
            e.onNext("取消關注");
            e.onNext("但還是要保持微笑");
            e.onComplete();
        }
    });
    //2、傳送多個數據
    return Observable.just("俊俊俊很帥","你值得擁有","取消關注","但還是要保持微笑");
    //3、傳送陣列
    return Observable.fromArray("俊俊俊很帥","你值得擁有","取消關注","但還是要保持微笑");
    //4、傳送一個數據
    return Observable.fromCallable(new Callable<String>() {
        @Override
        public String call() throws Exception {
            return "俊俊俊很帥";
        }
    });
}

模擬傳送驗證碼

這裡的案例使用我們平時最簡單的需求,看效果圖就知道(圖片會卡,效果大家腦補)

這裡寫圖片描述

這裡是整個程式碼的實現思路,我會在程式碼下面註釋一下需要注意的點,程式碼我就直接貼出來,有句話說得好,成為大神,就必須先學會閱讀別人的程式碼,哈哈哈,我的程式碼閱讀起來應該沒問題的吧

public class ButtonEnableActivity extends AppCompatActivity implements View.OnClickListener {
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_button_enable);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        final long count = 3;
        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.BLACK);
                    }
                })
                .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("傳送驗證碼");
                    }
                });
    }
}

1、操作符

  • 像這種interval、take、map、observeOn、doOnSubscribe、subscribe都是屬於RxJava的操作符,簡單的說就是實現某個方法,裡面的功能都被包裝起來了
  • RxJava支援的操作符很多,很多操作符用起來都簡單,但是組合起來就很複雜,功能很強大,具體分類如圖所示

這裡寫圖片描述

2、操作符介紹

  • interval:延時幾秒,每隔幾秒開始執行
  • take:超過多少秒停止執行
  • map:型別轉換,由於是倒計時,案例需要將倒計時的數字反過來
  • observeOn:在主執行緒執行
  • doOnSubscribe:在執行的過程中
  • subscribe:訂閱

RxJava2與Retrofit的使用

RxJava與Retrofit的使用,更像我們的AsyncTask,通過網路獲取資料然後通過Handler更新UI

模擬使用者登陸獲取使用者資料

人類總是喜歡看圖說話,巧了,我給你提供了,我只能拿出我過硬的美工技術給你們畫圖了

這裡寫圖片描述

① Bean物件

public class UserParam {
    private String param1;
    private String param2;
    public UserParam(String param1, String param2) {
        this.param1 = param1;
        this.param2 = param2;
    }
    public String getParam1() {
        return param1;
    }
    public void setParam1(String param1) {
        this.param1 = param1;
    }
    public String getParam2() {
        return param2;
    }
    public void setParam2(String param2) {
        this.param2 = param2;
    }
    @Override
    public String toString() {
        return new Gson().toJson(this);
    }
}
  • 這裡我們採用的是httpbin的一個post介面,各位可以在它的網站試一下,這裡的NetBean是通過請求返回的資料,進行GsonFormat生成的
public class NetBean {
    private FormBean form;
    public FormBean getForm() {
        return form;
    }
    public void setForm(FormBean form) {
        this.form = form;
    }
    public static class FormBean {
        private String username;
        private String password;
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
    }
}
public class UserBean {
    private String username;
    private String passwrod;
    public UserBean(String passwrod, String username) {
        this.passwrod = passwrod;
        this.username = username;
    }
    public String getPasswrod() {
        return passwrod;
    }
    public void setPasswrod(String passwrod) {
        this.passwrod = passwrod;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    @Override
    public String toString() {
        return new Gson().toJson(this);
    }
}

② ApiService

  • 這裡返回Observable物件,也就是我們RxJava的被觀察者
public interface ApiService {
    @FormUrlEncoded
    @POST("/post")
    Observable<NetBean> getUserInfo(@Field("username")String username, @Field("password")String password);
}

③ RxJava+Retrofit的實現

public class RxLoginActivity extends AppCompatActivity {

    ApiService apiService;
    TextView tv_text;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rx_login);
        tv_text = (TextView) findViewById(R.id.tv_text);

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

        //構建RxJava
        UserParam param = new UserParam("hensen", "123456");
        //傳送param引數
        Observable.just(param)
                .flatMap(new Function<UserParam, ObservableSource<NetBean>>() {
                    @Override
                    public ObservableSource<NetBean> apply(@NonNull UserParam userParam) throws Exception {
                        //第一步:傳送網路請求,獲取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());
                        //第二步:轉換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 {
                        //第三步:接收第二步傳送過來的資料,進行UI更新
                        tv_text.setText("使用者名稱:" + userBean.getUsername() + "--密碼:" + userBean.getPasswrod());
                    }
                });
    }
}

1、Retrofit

  • RxJava與Retrofit一起使用必須在Retrofit上加上這句話addCallAdapterFactory(RxJava2CallAdapterFactory.create())

2、RxJava

  • 我們通過just方式傳送資料
  • flatMap方法是用於資料格式轉換的方法,其後面的引數UserParam與ObservableSource< NetBean>,引數一表示原資料,引數二表示轉換的資料,那麼就是通過傳送網路引數,轉換成網路返回的資料,呼叫Retrofit

合併本地與伺服器購物車列表

這個案例其實就是使用者新增購物車的時候,首先會在本地儲存一份,然後發現如果沒有網路,那麼沒辦法提交到伺服器上,只能等下一次有網路的時候採用本地資料庫和伺服器資料的合併來實現上傳到伺服器,這裡我們就貼RxJava與Retrofit的程式碼,不貼其他程式碼了,廢話不多說,上圖

這裡寫圖片描述

public class CartMegerActivity extends AppCompatActivity implements View.OnClickListener {
    private Button button;
    private ApiService apiService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cart_meger);
        apiService = new Retrofit.Builder()
                .baseUrl("http://httpbin.org/")
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build()
                .create(ApiService.class);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        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){
                    System.out.println(str);
                }
            }
            @Override
            public void onError(Throwable e) {}
            @Override
            public void onComplete() {
                System.out.println("onComplete");
            }
        });
    }
    private Observable<List<String>> getDataForLocal() {
        List<String> list = new ArrayList<>();
        list.add("購物車物品一");
        list.add("購物車物品二");
        return Observable.just(list);
    }
    private Observable<List<String>> getDataForNet() {
        return Observable.just("購物車物品三").flatMap(new Function<String, ObservableSource<NetBean>>() {
            @Override
            public ObservableSource<NetBean> apply(@NonNull String s) throws Exception {
                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();
                List<String> list = new ArrayList<>();
                list.add(shop);
                return Observable.just(list);
            }
        }).subscribeOn(Schedulers.io());
    }
}

這裡使用到merge的操作符,其表示意思就是將兩個ObservableSource合併為一個ObservableSource,最後的列印結果是

04-03 02:37:28.840 5615-5615/com.handsome.boke2 I/System.out: 購物車物品一
04-03 02:37:28.840 5615-5615/com.handsome.boke2 I/System.out: 購物車物品二
04-03 02:37:39.501 5615-6337/com.handsome.boke2 I/System.out: 購物車物品三
04-03 02:37:39.501 5615-6337/com.handsome.boke2 I/System.out: onComplete

RxJava2與RxBinding的使用

RxBinding的使用也是為了讓介面看起來更簡潔,剩去了傳統的findViewById和setOnClickListener的方法,不用任何宣告,只要新增依賴就可以直接使用了

優化搜尋請求

這裡的案例是說當我們在EditText打字實時搜尋的時候,可能使用者會打字很會快,那麼我們就沒有必要一直髮送網路請求,請求搜尋結果,我們可以通過當使用者打字停止後的延時500毫秒再發送搜尋請求

public class TextChangeActivity extends AppCompatActivity {
   private  EditText edittext;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_text_change);
        edittext = (EditText) findViewById(R.id.edittext);

        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("找不到2017年比俊俊俊更帥的人");
                        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 {
                        //介面更新,這裡用列印替代
                        System.out.println(strings.toString());
                    }
                });
    }
}

操作符

  • RxTextView.textChanges(edittext):Rxbinding用法
  • debounce:表示延時多少秒後執行
  • switchMap:也是資料轉換,與flatMap的區別在註釋中解釋很清楚了

演示效果圖

這裡寫圖片描述

優化點選請求

這個案例很簡單,當用戶一直點選一個按鈕的時候,我們不應該一直呼叫訪問網路請求,而是 1秒內,只執行一次網路請求

public class ButtonClickActivity extends AppCompatActivity {
    private Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_button_click);
        button = (Button) findViewById(R.id.button);
        RxView.clicks(button).throttleFirst(1, TimeUnit.SECONDS)
                .subscribe(new Observer<Object>() {
                    @Override
                    public void onSubscribe(Disposable d) {}
                    @Override
                    public void onNext(Object o) {
                        System.out.println("俊俊俊點選了按鈕");
                    }
                    @Override
                    public void onError(Throwable e) {}
                    @Override
                    public void onComplete() {}
                });
    }
}

原始碼下載

原始碼下載

結語

聽說認真看的同學都會學的很多哦,對於RxJava的操作符還是建議大家通過官網的wiki去深造,只有當RxJava用在專案中的時候,你才會體會它的好處,同時也讓你與初級工程師有了一定的距離,像很多RxBus、RxPermission、RxAndroid,很多人會疑問要不要去學習它,毫無疑問是必須學習的,技術是不斷更新,只有當你的技術跟上時代的時候,你才有可能和大神聊的津津有味,以上純屬瞎掰,當然有時間我也會去學習Rx系列,如果喜歡我的朋友可以關注我的部落格,或者加群大家一起學習吧