Rxjava實踐: 把混亂的WORKFLOW擼成串吧

上個月做的事情比較多:改改iOS bug,學python,把項目重構成MVP,深入使用Rxjava。

這次來說說Rxjava,通過還原一個真實的開發過程,來感受下rxjava的便利之處。

巨坑從來都是由小坑慢慢塌陷的

先來看下一段最普通的代碼

rx01.png

在沒有特殊需求的情況下,代碼就這么簡單。你可以理解為,獲取一個目錄下的所有文件,將它們一個個傳到服務器上去。

看起來好像是沒什么問題,一個for循環搞定。一個task失敗了不影響另一個task。每個task run在一個單獨的子線程。

之前rxjava使用場景只局限于和Retrofit一起用。沒過多的使用操作符。因此在uploadFile(path)方法中就是最簡單的Retrofit+Rxjava上傳文件。rxjava就切換了下線程。

對于寫慣java的人,這么寫是沒什么問題的。但如果深入使用過rxjava之后,這么寫就非常別扭了。看到for loop了,你不想將它改成Observable.from()嘛?

把能看見的都改成stream吧

getFileList()方法是獲取sd卡中data包下所有以loc為后綴的files。

workflow分三步:

  1. locate to data dir
  2. list files under data dir
  3. filter files with .loc suffix

換成rxjava非常容易

  1. 先發射一個data目錄路徑
  2. 需求是多次上傳文件,得用flatMap將data映射成一個Observable<File[]>
    2.1 當然你可以選擇直接listFile(filter),但這樣回調又套回調,不是很好看。
    2.2 用filter操作符將發射來的File[]過濾

比如像2.1這樣寫

rx02.png

或者像2.2這樣寫


rx03.png

注意,在flatMap中又用from()操作符將File[]變換成一個OnSubscribeFromArray類型的Observable在內部通過for循環一個個發射。(感謝一位朋友指正,之前理解錯誤,以為是發射多個Observable。不看源碼真是稀里糊涂的)

假如你的API接口可以接收多個文件,其實也不用這樣寫。直接在flatMap中拼接RequestBody,調用API請求就可以了。比如像下圖這樣寫:


rx04.png

無奈需求是上傳loc文件同時還會再帶上一個sensor文件,所以就不能像上述這樣寫。

產品說:需求變了~

接下來的workflow就很有趣了。現在有了多個Observable<File>,一個個上傳就是了。

如果不考慮隊列,不考慮無網或上傳失敗情況。完全再來一個flatMap將Observable<File>變換為Observable<Response<HashMap>>就可以了。比如:


rx05.png

但現在的需求是,隊列上傳文件,也就是說,必須一個任務完成(成功|失敗)后才能進行下一個任務。這樣用flatMap就不可以了。(其實后來我考慮過這個問題,線程的調度本質還是由我聲明出來的線程池來決定的,如果用Schedulers.newThread(),那就會創建多個子線程。但如果用Schedulers.from(Executors.newSingleThreadExecutor())呢?)

需求總是多變的,好在有rxjava可以隨意變換。來吧,我們看看不用單個線程池,如何實現隊列。

不能隨意套路,坑的是自己

之前學習rxjava時,看過很多在android中高度使用rxjava的文章。有一個操作符很有意思-> concat()

The Concat operator concatenates the output of multiple Observables so that they act like a single Observable, with all of the items emitted by the first Observable being emitted before any of the items emitted by the second Observable (and so forth, if there are more than two).

即將多個Observables串起成一個Observable,直到一個執行完畢后再執行下一個。

我們可以將這個concat()應用在讀取緩存還是請求服務器, 如果緩存有數據,那就不用請求服務器了。

Observable<Data> cache;
Observable<Data> server;
Observable.concat(cache, server)
            .first()

這個也可以用在隊列上傳文件場景上咯。but,concat()是創建型操作符,再次變換就不能使用了。不過可以用concatMap(),

Returns a new Observable that emits items resulting from applying a function that you supply to each item emitted by the source Observable, where that function returns an Observable, and then emitting the items that result from concatenating those resulting Observables.

直接看代碼吧

rx06.png

這段寫的特別扭,為什么又要在一個Observable里又創建一個retrofit相關的Observable?當時想的是,因為要在upload成功后得刪除文件啊。如果把subscribe放到外層去,那接收到的全是服務器response,不知道當前的response屬于哪一個file upload。所以我就又寫了次變換。(這里肯定可以優化的,寫的太挫)

在concatMap中接收到from()發射來的一個Observable,變換成Retrofit請求,當Subscriber標記為onCompleted后再去執行下一個Observable。

到這里還沒完,假如無網絡又或者服務器異常。在第一個Observable就會失敗,此時還需要繼續請求嗎?很有可能后面的Observable也都不成功。那加個判斷吧。concat()可以和first()一起用。concatMap()也是可以的。

rx07.png

If you are only interested in the first item emitted by an Observable, or the first item that meets some criteria, you can filter the Observable with the First operator.

如果first() -> return true; 這樣只取到目前的這個Observable,后續的不執行了。

也就是說,只有在上傳成功時return false,繼續執行下一個Observable。否則就return true停止。

覺醒分割線

我想之前肯定是被concat(cache, db, server).first()整懵逼了,一心去套,才寫了上面這么二的代碼的。等等,容我換個姿勢。

rx10.png

看,對請求結果map變換一次就可以啦,如果成功刪除相關文件,不成功就是個異常了。Observable.error()。這樣就跳出了concatMap,也就是說,當異常發生時會停止后續的文件上傳。這樣first()也不需要啦。除非還有其他額外的停止flag要判斷。

到這里整個workflow就被rxjava梳理完畢了。是不是很有趣?我們來看下代碼全過程。

rx08.png

還剩最后一個問題:線程調度

之前一直都沒寫線程調度的地方。subscribeOn放在哪里比較好?

需求是:在主線程listFile拿到目錄下的所有文件,然后在子線程一個個隊列上傳文件,執行完畢后再切換到主線程彈dialog告知結果。

這樣來說,每個文件上傳時不需要切換線程,所以調用retrofit的地方是不需要subscribeOn。如果執意要在uploadTrip()后加上subscribeOn(io),也不是不可以。只是每個上傳task都在一個新的線程里執行的。但實際上,我們的文件上傳是個隊列,完全可以一直在同一個線程里執行。所以我在了flatMap后observeOn(io)。最終執行的log如下圖

rx09.png

之前亂嘗試,寫了兩個subscribeOn(),雖然邏輯是對的,但思想上來說是observeOn多次,subscribeOn一次,幸好有位朋友提醒,才改成了上圖。

好了,混亂的workflow總算擼成串了。平時看相關文章總覺得很簡單,無非就是幾個操作符拼接在一起,做了線程切換。不好理解的就是鏈式思維的轉換還有一些操作符:compose transformer等。等到真正應用到項目場景中,著實折騰了不少。比如不用flatMap,改為concatMap。比如線程調度。比如放棄使用retrofit+rxjava套路,重新認識reactive等。

總得來說,當理解了rxjava的鏈式思維并對一些復雜的邏輯重構之后,還是會愛上的。

參考閱讀

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

推薦閱讀更多精彩內容