6章 RxJava基礎實戰

Hensen發表於2019-05-13

本篇文章已授權微信公眾號 YYGeeker 獨家釋出轉載請標明出處

CSDN學院課程地址

6. RxJava基礎實戰

6.1 模擬傳送驗證碼

應用場景:當使用者點選傳送驗證碼後,在倒數計時的時間內是不可以重新點選傳送驗證碼的,倒數計時結束後,傳送驗證碼的按鈕重新恢復點選,這裡舉例子為3s的倒數計時

public void verify(View view) {
    final long count = 3;//倒數計時時間
    final Button button = (Button) view;//當前按鈕

    Observable.interval(0, 1, TimeUnit.SECONDS)//定時器
            .take(count + 1)//取定時器前4個,當前值:0,1,2,3
            .map(new Function<Long, Long>() {
                @Override
                public Long apply(@NonNull Long aLong) throws Exception {
                    return count - aLong;//將值轉換下,當前值:3,2,1,0
                }
            })
            .observeOn(AndroidSchedulers.mainThread())//主執行緒更新UI
            .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.setText("傳送驗證碼");
                }
            });
}
複製程式碼

6.2 模擬使用者點選防抖動

應用場景:在某些應用場景中,使用者會多次點選同一個按鈕,導致有多次點選事件的產生,如果點選事件中是網路請求,那麼就會產生多次網路請求。正確的操作應該是,在一定時間內,使用者頻繁點選多次按鈕之後,只訪問一次網路請求。下面針對所說的需求進行編寫

寫法一:

/**
 * 模擬使用者點選防抖動
 */
public void query(View view) {
    RxUtils.click(view, 2)
            .subscribe(new Observer<Object>() {
                @Override
                public void onSubscribe(Disposable d) {

                }

                @Override
                public void onNext(Object o) {
                    System.out.println("onNext");
                }

                @Override
                public void onError(Throwable e) {

                }

                @Override
                public void onComplete() {
                    System.out.println("onComplete");
                }
            });
}

//封裝工具
static class RxUtils {
    static Observable<Object> click(final View view, long seconds) {
        return new ViewClickObservable(view)
                .throttleFirst(seconds, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnDispose(new Action() {
                                 @Override
                                 public void run() throws Exception {
                                     if (view != null) {
                                         view.setOnClickListener(null);
                                     }
                                 }
                             }
                );
    }
}

//建立一個觀察者
static class ViewClickObservable extends Observable<Object> {

    private View view;

    public ViewClickObservable(View view) {
        this.view = view;
    }

    //當這個觀察者被訂閱的時候,會執行下面的回撥
    @Override
    protected void subscribeActual(final Observer<? super Object> observer) {
        if (view != null) {
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    observer.onNext(v);
                }
            });
        }
    }
}
複製程式碼

寫法二:


static class ViewClickObservableOnSubscribe implements ObservableOnSubscribe<Object> {

    private ObservableEmitter<Object> emitter;

    public ObservableEmitter<Object> getEmitter() {
        return emitter;
    }

    @Override
    public void subscribe(ObservableEmitter<Object> e) throws Exception {
        this.emitter = e;
    }
}

//封裝工具
static class RxUtils {

    static Observable<Object> clicks(final View view, long seconds) {
        final ViewClickObservableOnSubscribe viewClickObservableOnSubscribe = new ViewClickObservableOnSubscribe();

        if (view != null) {
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    ObservableEmitter<Object> emitter = viewClickObservableOnSubscribe.getEmitter();
                    if (emitter != null && !emitter.isDisposed()) {
                        emitter.onNext(1);
                    }
                }
            });
        }

        return Observable
                .create(viewClickObservableOnSubscribe)
                .throttleFirst(seconds, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnDispose(new Action() {
                                 @Override
                                 public void run() throws Exception {
                                     if (view != null) {
                                         view.setOnClickListener(null);
                                     }
                                 }
                             }
                );
    }
}

/**
 * 模擬使用者點選防抖動
 */
public void query(View view) {
    RxUtils.clicks(view, 2)
            .subscribe(new Observer<Object>() {
                @Override
                public void onSubscribe(Disposable d) {

                }

                @Override
                public void onNext(Object o) {
                    System.out.println("onNext");
                }

                @Override
                public void onError(Throwable e) {

                }

                @Override
                public void onComplete() {
                    System.out.println("onComplete");
                }
            });
}
複製程式碼

6.3 模擬會員資訊的合併

應用場景:假如我們當前在好友聊天列表介面中,客戶端需要通過好友列表的uid去查詢好友列表中會員資訊,然後顯示會員圖示等資訊。假如聊天列表介面中的好友數量有成千上百個,客戶端每次從後臺批量查詢使用者會員資訊後,需要在本地做快取,如果這時使用者在聊天列表中進入某個群聊介面,這個時候還是需要去獲取群聊中的所有使用者的會員資訊,如果群聊介面中也包含有自己的好友,那麼我們就會去判斷,如果使用者的會員資訊在快取中存在,則從快取中獲取,如果在快取中不存在,則加入到一個請求集合中,批量查詢會員資訊後,合併本地快取的會員資訊和新的伺服器的會員資訊,將資訊返回給群聊介面

1、建立會員實體類

private HashMap<Long, Vip> mVipCache = new HashMap<>();//作為快取的型別

public static class Vip {
    //會員資訊實體類
}
複製程式碼

2、模擬從本地獲取資料

  1. 遍歷批量的uid引數,從快取和網路中獲取會員資訊的資料
  2. 如果本地資料存在,則需要將快取的會員資訊加入到vipInfo
  3. 如果本地資料不存在,則需要將uid加入到請求列表mRequestList
  4. 最後合併本地快取資料和網路請求資料
/**
 * 從本地快取中獲取資料
 */
public Observable<HashMap<Long, Vip>> getVipFromCache(List<Long> uids) {
    List<Long> mRequestList = new ArrayList<>();
    HashMap<Long, Vip> vipInfo = new HashMap<>();

    for (Long uid : uids) {
        if (mVipCache.containsKey(uid)) {
            Log.e("TAG", "從本地獲取資料:" + uid + "使用者");
            vipInfo.put(uid, mVipCache.get(uid));//如果快取中有資料,則從本地中取出
        } else {
            Log.e("TAG", "從網路獲取資料:" + uid + "使用者");
            mRequestList.add(uid);//如果快取中沒有資料,則加入到網路請求列表中
        }
    }

    if (mRequestList.isEmpty()) {
        //如果請求列表中為空,則直接返回快取的資料
        return Observable.just(vipInfo);
    }

    //合併快取的資料和網路獲取的資料
    return Observable.merge(Observable.just(vipInfo), getVipFromWeb(mRequestList));
}
複製程式碼

3、模擬從伺服器獲取資料

  1. 睡眠2s鍾,用於模擬網路請求的耗時時間
  2. 模擬後臺返回的資料,並加入到快取列表中
  3. 返回新的事件流
/**
 * 從網路上批量查詢Vip資訊
 */
public Observable<HashMap<Long, Vip>> getVipFromWeb(List<Long> uids) {
    //由於這裡沒有對應的介面,所以模擬請求網路資料
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    //模擬返回的資料
    HashMap<Long, Vip> vip = new HashMap<>();
    for (Long uid : uids) {
        //後臺返回的資料進行賦值
        Vip vipInfo = new Vip();
        //vipInfo.xxx = WebValue;
        //vipInfo.xxx = WebValue;
        vip.put(uid, vipInfo);
    }

    //快取到本地
    mVipCache.putAll(vip);

    return Observable.just(vip);
}
複製程式碼

4、模擬合併本地資料和伺服器資料

  1. 模擬應用場景,查詢當前好友列表的會員資訊
  2. 模擬應用場景,進入群聊頁面,查詢群聊列表的會員資訊
  3. 模擬應用場景,定時更新會員的資訊
/**
 * 模擬合併本地資訊和伺服器資訊
 */
public void vip(View view) {
    List<Long> uids = new ArrayList<>();

    Log.e("TAG", "第一次查詢,進入好友列表介面,查詢好友列表的會員資訊");
    uids.add(1L);
    uids.add(2L);
    getVipFromCache(uids);
    uids.clear();

    Log.e("TAG", "第二次查詢,進入群聊介面,查詢群聊中的會員資訊");
    uids.add(1L);
    uids.add(3L);
    uids.add(4L);
    getVipFromCache(uids);
    uids.clear();

    Log.e("TAG", "第三次查詢,定時更新最新會員資訊,更新所有快取裡的資料");
    uids.add(1L);
    uids.add(2L);
    uids.add(3L);
    uids.add(4L);
    mVipCache.clear();
    getVipFromCache(uids);
}
複製程式碼

5、輸出結果

達到我們預期的設想和最優的解決方案

第一次查詢,進入好友列表介面,查詢好友列表的會員資訊
從網路獲取資料:1使用者
從網路獲取資料:2使用者
第二次查詢,進入群聊介面,查詢群聊中的會員資訊
從本地獲取資料:1使用者
從網路獲取資料:3使用者
從網路獲取資料:4使用者
第三次查詢,定時更新最新會員資訊,更新所有快取裡的資料
從網路獲取資料:1使用者
從網路獲取資料:2使用者
從網路獲取資料:3使用者
從網路獲取資料:4使用者
複製程式碼

相關文章