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

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

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

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

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

rx01.png

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

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

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

對于寫慣java的人,這么寫是沒什么問題的。但如果深入使用過rxjava之后,這么寫就非常別扭了??吹絝or 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. 先發(fā)射一個data目錄路徑
  2. 需求是多次上傳文件,得用flatMap將data映射成一個Observable<File[]>
    2.1 當然你可以選擇直接listFile(filter),但這樣回調(diào)又套回調(diào),不是很好看。
    2.2 用filter操作符將發(fā)射來的File[]過濾

比如像2.1這樣寫

rx02.png

或者像2.2這樣寫


rx03.png

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

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


rx04.png

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

產(chǎn)品說:需求變了~

接下來的workflow就很有趣了?,F(xiàn)在有了多個Observable<File>,一個個上傳就是了。

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


rx05.png

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

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

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

之前學習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,直到一個執(zhí)行完畢后再執(zhí)行下一個。

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

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

這個也可以用在隊列上傳文件場景上咯。but,concat()是創(chuàng)建型操作符,再次變換就不能使用了。不過可以用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里又創(chuàng)建一個retrofit相關的Observable?當時想的是,因為要在upload成功后得刪除文件啊。如果把subscribe放到外層去,那接收到的全是服務器response,不知道當前的response屬于哪一個file upload。所以我就又寫了次變換。(這里肯定可以優(yōu)化的,寫的太挫)

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

到這里還沒完,假如無網(wǎng)絡又或者服務器異常。在第一個Observable就會失敗,此時還需要繼續(xù)請求嗎?很有可能后面的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,后續(xù)的不執(zhí)行了。

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

覺醒分割線

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

rx10.png

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

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

rx08.png

還剩最后一個問題:線程調(diào)度。

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

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

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

rx09.png

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

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

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

參考閱讀

  1. 理解操作符,還是看官網(wǎng)最佳ReactiveX
  2. 感謝小鄧子幫忙梳理流程。
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容