1. 程式人生 > >【騰訊Bugly乾貨分享】基於RxJava的一種MVP實現

【騰訊Bugly乾貨分享】基於RxJava的一種MVP實現

Dev Club 是一個交流移動開發技術,結交朋友,擴充套件人脈的社群,成員都是經過稽核的移動開發工程師。每週都會舉行嘉賓分享,話題討論等活動。

本期,我們邀請了騰訊IEG Android 開發工程師——戴俊,為大家分享《基於RxJava的一種MVP實現》。

分享內容簡介:

RxJava是一個實現Java響應式程式設計的庫,讓非同步事件以序列的形式組織。MVP則通常用來將View業務層與Model層分離開來,兩者結合起來可輕鬆實現業務解耦、執行緒控制、單元測試等等強大功能

內容大體框架:

  1. Android開發框架的選擇
  2. 如何一步步搭建MVP分層框架
  3. 使用RxJava來進行執行緒控制
  4. 結語

下面是本期分享內容整理

Hello,大家好,我是戴俊。目前在IEG騰訊動漫主要負責Android端的開發工作。

第一次進行這種微信群的分享,如果有任何疑問,歡迎大家在分享結束後提問。下面開始我們今天的分享。

1. Android開發框架的選擇

我們知道原生Android開發已經是一個基礎的MVC框架,所以在專案剛開始開發的時候並沒有遇到太多問題。

對一個經典的Android MVC框架來講,它的結構大概是下面這樣(圖片來自參考文獻)

這樣的結構下,Activity層既承擔了View層的一部分工作(因為XML作為View層的一部分功能實在太弱了),又承擔Controller層的工作,因此當業務變化時,Activity層會極劇膨脹。

拿我們專案早期的例子,一個Activity曾經最多達到了2000到3000行,重構的時候極其痛苦。

要解決這個問題,主要的辦法有兩種:
- 第一種是分層
- 第二種是模組化。

兩個方法最終要實現的都是解耦。分層講的是縱向層面上的解耦,模組化則是橫向上的解耦。

我們今天要討論的MVP就是一種通過分層來進行解耦的框架。

2. 如何一步步搭建MVP分層框架

如果你是個習慣了讀文件的老司機,可以直接參考下面幾篇文章

當然如果覺得看官方的示例太麻煩,那麼下面我們就來講解一下如何實現一個簡單的MVP構架。

這是一個比較典型的MVP結構圖(圖片來自參考文獻),相比於第一張圖,多了兩個層,一個是Presenter和DataManager層。

多出這兩個層到底有什麼作用,下面我們來用程式碼說明。

首先我們假設有一個從服務端獲取字串並顯示的手機上的簡單功能。下面是主介面的程式碼

Activity裡面包含了幾個檔案,一個是View層的對外介面MainView,一個是P層的Presenter。

首先看View層的對外介面檔案

因為這個功能比較簡單,只需要在裝置上顯示一個字串,所以只有一個介面方法onShowString(),再看P層程式碼

從上面三個檔案可以看到,View層通過註冊Listener將自己的介面MainView交給了Presenter, 而Presenter層持有Model層的也只是一個介面。通過Presenter層將業務層與展現層隔離了開來,這樣的好處是什麼?

我們知道介面的一個作用通常是用來抽象行為,對外部遮蔽實現細節。所以對於View層來說,業務細節被遮蔽了,對業務層來說,展示細節被遮蔽了。而對於處於中間的Presenter層來說,它就像一個介面拼裝器,把View層發出的請求傳遞給業務層,把業務層返回的資料又送還給View層展示,至於前後兩端怎麼實現的,它才不用關心。

介面的第二個作用是可以用來切換實現。我們先看下面的程式碼。

從上面三個檔案可以看到,業務層對外的只有一個介面,實現卻有兩個(DataSourceImpl和DataSourceTestImpl)。從名字大家就能看出來有什麼作用了,一個是正常環境的業務層實現,一個是測試環境的業務層實現。

這裡我們設想一個場景:

開發同學接到一個新的需求,設計稿也輸出完成了,然而後臺的介面卻遲遲沒到,怎麼辦? 現在通過MVP,我們把業務層實現切換到DataSourceTestImpl,是不是可以先自己假寫資料,調好一切前端和互動,然後泡一杯咖啡等後臺同學把介面寫完聯調?或者有時候為了重現一個bug,要在線上寫一條髒資料,測試完再刪除?

類似的應用場景其實有非常非常多,這裡我們就看到了使用介面解耦的一個好處了,不僅把業務層和展示層解耦開來,還把Android開發同其它的一切的外部資料依賴都解耦開來。

這裡我想提到之前討論過的單元測試問題,很多同學反饋專案開發過程中沒有做過,或者沒有時間精力去做單元測試,或者因為業務變化太大導致無法做單元測試。其實在我們專案中也遇到過樣的問題,但其實通過這樣分層之後,才發現單元測試其實是完全可以推進的,也完全不用再擔心測試的時候會把髒資料寫到線上的問題了。

到現在為止一個基於MVP簡單框架就搭建完成了,但其實還遺留了一個比較大的問題。

很多同學可能已經發現了,Presenter層在呼叫業務層的時候是直接呼叫的,而Android規定,主執行緒是無法直接進行網路請求,會丟擲NetworkOnMainThreadException異常。

所以在presenter層,我們需要進行一項執行緒切換的工作,這樣才能保證“所有的IO操作都應當在執行緒中完成,主執行緒只負責頁面渲染的工作”這一優化準則。

當然,Android本身提供一些方案,比如下面這種:

通過新建子執行緒進行IO讀寫獲取資料,然後通過主執行緒的Looper將結果通過傳回主執行緒進行展示,這種方案是勉強也行得通的。

但問題也有,一是執行緒需要額外管理,不可能每次發請求都要開啟一個執行緒;二是適應性差,假如資料請求有先後依賴,有並行的情況,這樣的寫法變得髒亂無比。

好在有了RxJava ,可以比較方便的解決這個問題。

3. 使用RxJava來進行執行緒控制

RxJava是一個天生用來做非同步的工具,相比AsyncTask,Handler等,它的優點就是簡潔,無比的簡潔。在Android中使用RxJava需要加入下面兩個依賴。

compile 'io.reactivex:rxjava:1.0.14' 
compile 'io.reactivex:rxandroid:1.0.1'

這裡我們直接介紹如何使用RxJava解決這個問題,在presenter中修改方法getData()。

簡單解釋一下,dataAction是我們的資料業務邏輯,viewAction是介面的顯示邏輯,通過RxJava的傳遞和變換,dataAction會在由RxJava管理的IO執行緒–Schedulers.io() 中執行,而viewAction則會在UI執行緒–AndroidSchedulers.mainThread()中執行。

RxJava當然不止這麼簡單,還有別的玩法,比方說進入一個介面的時候,需要先載入快取的資料,然後再從網路獲取更新的資料進行重新整理。有的時候,可能還需要處理IO過程中的異常情況,加入RxJava的異常處理引數。

RxJava的使用場景遠不止這些,執行緒變換、資料變換、介面順序依賴、介面併發請求這些要求對它來說都是小菜一碟。當然,有些同學可能覺得RxJava入手有些困難,程式碼也會變得不那麼直觀,但相信只要大家慢慢熟悉它之後,它就會變得無比討人喜歡。

下面列出了一些常見的RxJava的常用場景,其實還有更多的其它功能等待著大家去挖掘。

  1. 取資料先檢查快取的場景
  2. 需要等到多個介面併發取完資料,再更新
  3. 一個介面的請求依賴另一個API請求返回的資料
  4. 介面按鈕需要防止連續點選的情況
  5. 響應式的介面
  6. 複雜的資料變換

上面這些功能都可以通過RxJava來輕鬆完成。具體的使用就不再多講了,大家可以參考下面的文章:(Google文章名就可以了)

結語

至此為止,通過MVP+RxJava的組合,我們已經構建出一個比MVC更靈活的Android專案開發框架,好處大概有以下幾點:

  1. 每層各自獨立,通過介面通訊
  2. 實現與介面分離實現,不同場景(正式,測試)掛載不同的實現,方便測試寫假資料
  3. 所有的業務邏輯都在非UI執行緒中進行,最大限度減少IO操作對UI的影響
  4. 使用RxJava可以將複雜的呼叫進行鏈式組合,解決多重回調巢狀問題

以上就是我今天的分享,內容上可能還有不足甚至不夠好的地方,歡迎大家指出,一起討論學習。

這裡也順便打個廣告,歡迎大家下載騰訊動漫App,這裡有最新最熱的國漫日漫,支援正版,你我共享。

問答環節

Q1:對於這樣的一個MVP專案,它裡面的包結構怎樣規劃比較清晰合理呢?

包結構的通常分法有兩種:一種是按功能模組分,把某一個功能的presenter, activity,view層介面放到一起;一種是按型別分,P層M層和V層分成三個包。實際專案應用,我個人傾向於第一種,這種無論是開發過程,還是排查問題都會方便很多。當然,不同的專案還是有不同的分法的,不一而論。

Q2:耗時操作可能引起的記憶體洩露問題,請問是如何處理的。
Q3:用mvp時,請問你們在哪裡釋放一些引用,防止記憶體洩露的
Q4:p持有v的引用,請問怎麼解決Activity的記憶體洩露問題?
Q5:網特別慢的時候,應用退出,但網路請求還沒結束,p層回撥持有上下文造成記憶體洩露,一般怎麼解決啊。

這幾個問題其實比較類似,我們在實際專案中,presenter會隨著activity的生命週期進行銷燬,比如在onDestroy方法中對presenter進行置空和引用解綁, 當然我們可以給所有的Presenter寫一個共有父類BasePresenter,專門來處理這個問題。

Q6:需求包含列表頁的時候,列表項也是按照mvp的思想來分層,還是封裝成模組比較合適

目前我們的做法是直接封裝成模組,簡單的問題不宜過度設計

Q7:想問一下騰訊動漫這個app目前用的就是您講的這個架構嗎,在實際用的過程中有遇到什麼問題嗎

是的,我們已經使用了這個架構。實際使用過程中,經常會糾結的問題是業務邏輯層要不要再次獨立分層。

Q8:專案中做測試是好事,但我覺得建議去掉TestImpl測試檔案。如果專案打包時,打到包裡,會導致包變大,這種測試建議用node寫個簡單的服務,不知道嘉賓你咋看?

是的。正式專案中,可以通過註解,或者proguard或者gradle的配置將這些測試檔案不打到包裡。Node寫服務的話是不是又要搭環境,這裡的做法就是不使用任何外部環境依賴。

Q9:mvp一般都是activity和Fragment加入presenter層,那麼列表adapter裡的邏輯是否也要加上presenter層呢

Adapter其實跟View更接近的一個東西,它是用來處理重複顯示問題。一般來說,我們傳給adapter的資料完好能直接顯示的,建議在業務邏輯層將資料拼裝好再傳進去。
答:Adapter其實跟View更接近的一個東西,它是用來處理重複顯示問題。一般來說,我們傳給adapter的資料完好能直接顯示的,建議在業務邏輯層將資料拼裝好再傳進去。

Q10:我們專案中採用了MVP但是沒有用RxJava,m與p層採用回撥方式,這樣m通過回撥間接引用p,p層有v的引用。如果在網路情況不好頻繁開啟關閉頁面在網路請求結束前是否會有記憶體洩漏問題。rx是否能解決這個問題。還有當網路結束回撥時v對應activity destory了怎麼辦。每次都去判斷activity狀態嗎?

Rx不能解決記憶體洩漏的問題,前面2.3.7問題都提到了,通常的做法是在activity層銷燬的時候進行解綁。回撥時activity destory的話,我們現在的做法是對view層介面進行一次空值判定。如果有更好的辦法,也歡迎大家提出來討論

Q11:有時候例如自定義view依賴於伺服器返回的model,裡面也有很多根據model屬性去繪製的過程,這種情況怎麼處理?在P層丟擲一個model的get方法嗎?

自定義的View跟Activity一樣,我們統稱為View層。上面的例子中View層只有一個介面MainView,實際專案中,View層可能會實現好幾個介面。對一個經常會被利用的自定義View,會額外給它新建一個介面。

Q12:你的例子中p層實現中getDate()方法對資料進行了處理,是否m層只是單純的獲取原始資料,對於資料上的業務也放入到p層中處理,有沒有好的方式能夠複用有關資料業務的這塊邏輯

嗯,這個問題我們確實也遇到了。在專案實際操作過程中,如果有比較複雜業務流程,我會單獨再分離出一層業務層,業務層再去呼叫dataSource取資料。如果只是單純的取資料展示,現在這樣就夠了,儘量避免過度設計。

Q13:為了更好的解偶每一層,你們用MVP時 是否每層都有自己的資料結構,如果有的話,層與層之間的資料結構轉換開銷大不大?

目前來講,大部分的業務都是一個數據結構穿透使用的,偶爾會有資料結構重新封裝, 影響不大。我個人判斷的話,相比IO處理,資料結構的轉換開銷還是小的,而且,如果有很多複雜轉換的話,保證不要在UI執行緒中做,也不會太大問題。

Q14:activity與p層用介面的方式銜接的價值在哪?另外如何界定展現方法在哪呼叫?比如頁面需要顯示一個標題,內容是從之前頁面傳過來的,那是在activity接收後就直接顯示?還是先傳遞到p層再回調activity的顯示方法?感謝

價值在於,把presenter 與activity解耦之後,我可以在別的activity使用這個presenter層邏輯,也可以在這個activity 裡呼叫其它頁面的presenter方法。如果是前頁傳過來的,直接顯示就好,不做過度設計。

Q15:rxJava使用lamaba的語法格式的話貌似會將程式碼縮減很多,請問嘉賓有試過這種方式嗎?這個對專案的效能會有什麼影響嗎?因為我試用過幾次後一直出現oom的問題

lambda表示式會讓語法看起來更簡潔,非常推薦使用。但我們的專案目前只能使用jdk 7,悲傷。如果後面我們有機會切換的話,可以再一起分享一下。

Q16:rxjava怎麼實現佇列像handler message那樣,就是佇列執行,不是併發執行?

rxJava中的just方法和from方法都是以佇列形式發出事件。我猜你想問的問題可能是:一個介面的請求依賴另一個API請求返回的資料,這就是巢狀回撥問題。可以找下大頭鬼Bruce的一篇文章,《RxJava使用場景小結》,裡面有介紹的,這裡不詳細討論了。

更多精彩內容歡迎關注bugly的微信公眾賬號:

騰訊 Bugly是一款專為移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的情況以及解決方案。智慧合併功能幫助開發同學把每天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響使用者數最多的崩潰,精準定位功能幫助開發同學定位到出問題的程式碼行,實時上報可以在釋出後快速的瞭解應用的質量情況,適配最新的 iOS, Android 官方作業系統,鵝廠的工程師都在使用,快來加入我們吧!