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

本文來自於騰訊bugly開發者社區,非經做者贊成,請勿轉載,原文地址:http://dev.qq.com/topic/57bfe...前端

Dev Club 是一個交流移動開發技術,結交朋友,擴展人脈的社羣,成員都是通過審覈的移動開發工程師。每週都會舉行嘉賓分享,話題討論等活動。java

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

分享內容簡介:react

RxJava是一個實現Java響應式編程的庫,讓異步事件以序列的形式組織。MVP則一般用來將View業務層與Model層分離開來,二者結合起來可輕鬆實現業務解耦、線程控制、單元測試等等強大功能android

內容大致框架:git

  1. Android開發框架的選擇github

  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分層框架

若是你是個習慣了讀文檔的老司機,能夠直接參考下面幾篇文章

  1. Android Application Architecture

  2. Android Architecture Blueprints - Github

  3. Google官方MVP示例之TODO-MVP - 簡書

  4. todo-mvp - github

  5. dev-todo-mvp-rxjava - github

固然若是以爲看官方的示例太麻煩,那麼下面咱們就來說解一下如何實現一個簡單的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文章名就能夠了)

1.給 Android 開發者的 RxJava 詳解
2.RxJava 與 Retrofit 結合的最佳實踐
3.RxJava使用場景小結
4.How To Use RxJava

結語

至此爲止,經過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 官方操做系統,鵝廠的工程師都在使用,快來加入咱們吧!

相關文章
相關標籤/搜索