摘要:之前寫過一系列的文章,也承諾過會盡快有的介紹。所以這次還是給大家分享一個使用解決問題的案例,希望對大家在使用的時候有一點點啟發。上述這一套復雜的業務邏輯如果使用傳統編碼方式將是極其復雜的。
之前寫過一系列RxJava1的文章,也承諾過會盡快有RxJava2的介紹。無奈實際項目中還未真正的使用RxJava2,不敢妄動筆墨。所以這次還是給大家分享一個使用RxJava1解決問題的案例,希望對大家在使用RxJava的時候有一點點啟發。對RxJava還不了解的同學可以先去看看我之前的RxJava系列文章:
RxJava系列1(簡介)
RxJava系列2(基本概念及使用介紹)
RxJava系列3(轉換操作符)
RxJava系列4(過濾操作符)
RxJava系列5(組合操作符)
RxJava系列6(從微觀角度解讀RxJava源碼)
RxJava系列7(最佳實踐)
業務場景拿MinimalistWeather這個開源的天氣App來舉例:
進入App首頁后,首先我們需要從數據庫中獲取當前城市的天氣數據,如果數據庫中存在天氣數據則在UI頁面上展示天氣數據;如果數據庫中未存儲當前城市的天氣數據,或者已存儲的天氣數據的發布時間相比現在已經超過了一小時,并且網絡屬于連接狀態則調用API從服務端獲取天氣數據。如果獲取到到的天氣數據發布時間和當前數據庫中的天氣數據發布時間一致則丟棄掉從服務端獲取到的天氣數據,如果不一致則更新數據庫并且在頁面上展示最新的天氣信息。(同時天氣數據源是可配置的,可選擇是小米天氣數據源還是Know天氣數據源)
解決方案首先我們需要創建一個從數據庫獲取天氣數據的Observable observableForGetWeatherFromDB,同時我們也需要創建一個從API獲取天氣數據的Observable observableForGetWeatherFromNetWork;為了在無網絡狀態下免于創建observableForGetWeatherFromNetWork我們在這之前需要首先判斷下網絡狀態。最后使用contact操作符將兩個Observable合并,同時使用distinct和takeUntil操作符來過濾篩選數據以符合業務需求,然后結合subscribeOn和observeOn做線程切換。上述這一套復雜的業務邏輯如果使用傳統編碼方式將是極其復雜的。下面我們來看看使用RxJava如何清晰簡潔的來實現這個復雜的業務:
ObservableobservableForGetWeatherData; //首先創建一個從數據庫獲取天氣數據的Observable Observable observableForGetWeatherFromDB = Observable.create(new Observable.OnSubscribe () { @Override public void call(Subscriber super Weather> subscriber) { try { Weather weather = weatherDao.queryWeather(cityId); subscriber.onNext(weather); subscriber.onCompleted(); } catch (SQLException e) { throw Exceptions.propagate(e); } } }); if (!NetworkUtils.isNetworkConnected(context)) { observableForGetWeatherData = observableForGetWeatherFromDB; } else { //接著創建一個從網絡獲取天氣數據的Observable Observable observableForGetWeatherFromNetWork = null; switch (configuration.getDataSourceType()) { case ApiConstants.WEATHER_DATA_SOURCE_TYPE_KNOW: observableForGetWeatherFromNetWork = ApiClient.weatherService.getKnowWeather(cityId) .map(new Func1 () { @Override public Weather call(KnowWeather knowWeather) { return new KnowWeatherAdapter(knowWeather).getWeather(); } }); break; case ApiConstants.WEATHER_DATA_SOURCE_TYPE_MI: observableForGetWeatherFromNetWork = ApiClient.weatherService.getMiWeather(cityId) .map(new Func1 () { @Override public Weather call(MiWeather miWeather) { return new MiWeatherAdapter(miWeather).getWeather(); } }); break; } assert observableForGetWeatherFromNetWork != null; observableForGetWeatherFromNetWork = observableForGetWeatherFromNetWork .doOnNext(new Action1 () { @Override public void call(Weather weather) { Schedulers.io().createWorker().schedule(() -> { try { weatherDao.insertOrUpdateWeather(weather); } catch (SQLException e) { throw Exceptions.propagate(e); } }); } }); //使用concat操作符將兩個Observable合并 observableForGetWeatherData = Observable.concat(observableForGetWeatherFromDB, observableForGetWeatherFromNetWork) .filter(new Func1 () { @Override public Boolean call(Weather weather) { return weather != null && !TextUtils.isEmpty(weather.getCityId()); } }) .distinct(new Func1 () { @Override public Long call(Weather weather) { return weather.getRealTime().getTime();//如果天氣數據發布時間一致,我們再認為是相同的數據從丟棄掉 } }) .takeUntil(new Func1 () { @Override public Boolean call(Weather weather) { return System.currentTimeMillis() - weather.getRealTime().getTime() <= 60 * 60 * 1000;//如果天氣數據發布的時間和當前時間差在一小時以內則終止事件流 } }); } observableForGetWeatherData.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1 () { @Override public void call(Weather weather) { displayWeatherInformation(); } }, new Action1 () { @Override public void call(Throwable throwable) { Toast.makeText(context, throwable.getMessage(), Toast.LENGTH_LONG).show(); } });
上面的代碼看起來比較復雜,我們采用Lambda表達式簡化下代碼:
Observable小技巧observableForGetWeatherData; //首先創建一個從數據庫獲取天氣數據的Observable Observable observableForGetWeatherFromDB = Observable.create(new Observable.OnSubscribe () { @Override public void call(Subscriber super Weather> subscriber) { try { Weather weather = weatherDao.queryWeather(cityId); subscriber.onNext(weather); subscriber.onCompleted(); } catch (SQLException e) { throw Exceptions.propagate(e); } } }); if (!NetworkUtils.isNetworkConnected(context)) { observableForGetWeatherData = observableForGetWeatherFromDB; } else { //接著創建一個從網絡獲取天氣數據的Observable Observable observableForGetWeatherFromNetWork = null; switch (configuration.getDataSourceType()) { case ApiConstants.WEATHER_DATA_SOURCE_TYPE_KNOW: observableForGetWeatherFromNetWork = ApiClient.weatherService.getKnowWeather(cityId) .map(knowWeather -> new KnowWeatherAdapter(knowWeather).getWeather()); break; case ApiConstants.WEATHER_DATA_SOURCE_TYPE_MI: observableForGetWeatherFromNetWork = ApiClient.weatherService.getMiWeather(cityId) .map(miWeather -> new MiWeatherAdapter(miWeather).getWeather()); break; } assert observableForGetWeatherFromNetWork != null; observableForGetWeatherFromNetWork = observableForGetWeatherFromNetWork .doOnNext(weather -> Schedulers.io().createWorker().schedule(() -> { try { weatherDao.insertOrUpdateWeather(weather); } catch (SQLException e) { throw Exceptions.propagate(e); } })); //使用concat操作符將兩個Observable合并 observableForGetWeatherData = Observable.concat(observableForGetWeatherFromDB, observableForGetWeatherFromNetWork) .filter(weather -> weather != null && !TextUtils.isEmpty(weather.getCityId())) .distinct(weather -> weather.getRealTime().getTime())//如果天氣數據發布時間一致,我們再認為是相同的數據從丟棄掉 .takeUntil(weather -> System.currentTimeMillis() - weather.getRealTime().getTime() <= 60 * 60 * 1000);//如果天氣數據發布的時間和當前時間差在一小時以內則終止事件流 } observableForGetWeatherData.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(weather -> displayWeatherInformation(), throwable -> Toast.makeText(context, throwable.getMessage(), Toast.LENGTH_LONG).show());
在上述的實現中有幾點是我們需要注意的:
為什么我需要在判斷網絡那塊整個if else?這樣看起來很不優雅,我們通過RxJava符完全可以實現同樣的操作??!之所以這樣做是為了在無網絡狀況下去創建不必要的Observable observableForGetWeatherFromNetWork;
更新數據庫的操作不應該阻塞更新UI,因此我們在observableForGetWeatherFromNetWork的doOnNext中需要通過Schedulers.io().createWorker()去另起一條線程,以此保證更新數據庫不會阻塞更新UI的操作。
問題有同學可能會問為什么不在doOnNext之后再調用一次observeOn把更新數據庫的操作切換到一條新的子線程去操作呢?其實一開始我也是這樣做的,后來想想不對。整個Observable的事件傳遞處理就像是在一條流水線上完成的,雖然我們可以通過observeOn來指定子線程去處理更新數據庫的操作,但是只有等這條子線程完成了更新數據庫的任務后事件才會繼續往后傳遞,這樣就阻塞了更新UI的操作。對此有疑問的同學可以去看看我之前關于RxJava源碼分析的文章或者自己動手debug看看。
最后給大家留個兩個問題:
上述代碼是最佳實現方案嗎?還有什么更加合理的做法?
我們在observableForGetWeatherData中使用distinct和takeUntil過濾篩選天氣數據的時候網絡請求會不會已經發出去了?這樣做還有意義嗎?
歡迎大家留言討論。
本文中的代碼在MinimalistWeather中的WeatherDataRepository類中有同樣的實現,文章中為了更完整的將整個實現過程呈現出來,對代碼做了部分改動。
如果大家喜歡這一系列的文章,歡迎關注我的知乎專欄、Github以及簡書。
知乎專欄:https://zhuanlan.zhihu.com/baron
GitHub:https://github.com/BaronZ88
簡書:http://www.jianshu.com/users/cfdc52ea3399
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66436.html
摘要:響應式編程在介紹前,我們先聊聊響應式編程。響應式編程的一個關鍵概念是事件。今天,響應式編程最通用的一個場景是我們的移動必須做出對網絡調用用戶觸摸輸入和系統彈框的響應。并于年二月份正式向外展示了。 轉載請注明出處:https://zhuanlan.zhihu.com/p/20687178 RxJava系列1(簡介) RxJava系列2(基本概念及使用介紹) RxJava系列3(轉換操作...
閱讀 991·2021-11-23 09:51
閱讀 3479·2021-11-22 12:04
閱讀 2723·2021-11-11 16:55
閱讀 2941·2019-08-30 15:55
閱讀 3232·2019-08-29 14:22
閱讀 3358·2019-08-28 18:06
閱讀 1247·2019-08-26 18:36
閱讀 2132·2019-08-26 12:08