本文來自於騰訊bugly開發者社區,非經做者贊成,請勿轉載,原文地址:http://dev.qq.com/topic/5799d7844bef22a823b3ad44java
內容大綱:react
因爲原生 Android 開發應該已是一個基礎的 MVC 框架,因此在初始開發的時候並無遇到太多框架上的問題,但是一旦項目規模到了必定的程度,就須要對整個項目的代碼結構作一個整體上的規劃,最終的目的是使代碼可讀,維護性好,方便測試。’android
只有項目複雜度到了必定程度才須要使用一些更靈活的框架或者結構,簡單來講,寫個 Hello World 並不須要任何第三方的框架git
原生的 MVC 框架遇到大規模的應用,就會變得代碼難讀,很差維護,沒法測試的囧境。所以,Android 開發方面也有不少對應的框架來解決這些問題。github
構建框架的最終目的是加強項目代碼的可讀性 ,維護性 和方便測試 ,若是背離了這個初衷,爲了使用而使用,最終是得不償失的數據庫
從根本上來說,要解決上述的三個問題,核心思想無非兩種:一個是分層 ,一個是模塊化 。兩個方法最終要實現的就是解耦,分層講的是縱向層面上的解耦,模塊化則是橫向上的解耦。下面咱們來詳細討論一下 Android 開發如何實現不一樣層面上的解耦。編程
解耦的經常使用方法有兩種:分層 與模塊化json
橫向的模塊化對你們來可能並不陌生,在一個項目創建項目文件夾的時候就會遇到這個問題,一般的作法是將相同功能的模塊放到同一個目錄下,更復雜的,能夠經過插件化來實現功能的分離與加載。api
縱向的分層,不一樣的項目可能就有不一樣的分法,而且隨着項目的複雜度變大,層次可能愈來愈多。緩存
對於經典的 Android MVC 框架來講,若是隻是簡單的應用,業務邏輯寫到 Activity 下面並沒有太多問題,但一旦業務逐漸變得複雜起來,每一個頁面之間有不一樣的數據交互和業務交流時,activity 的代碼就會急劇膨脹,代碼就會變得可讀性,維護性不好。
因此這裏咱們就要介紹 Android 官方推薦的 MVP 框架,看看 MVP 是如何將 Android 項目層層分解。
若是你是個老司機,能夠直接參考下面幾篇文章(可在 google 搜到):
固然若是你以爲看官方的示例太麻煩,那麼本文會經過最簡潔的語言來說解如何經過 MVP 來實現一個合適的業務分層。
對一個經典的 Android MVC 框架項目來說,它的代碼結構大概是下面這樣(圖片來自參考文獻)
簡單來說,就是 Activity 或者 Fragment 直接與數據層交互,activity 經過 apiProvider 進行網絡訪問,或者經過 CacheProvider 讀取本地緩存,而後在返回或者回調裏對 Activity 的界面進行響應刷新。
這樣的結構在初期看來沒什麼問題,甚至能夠很快的開發出來一個展現功能,可是業務一旦變得複雜了怎麼辦?
咱們做一個設想,假如一次數據訪問可能須要同時訪問 api 和 cache,或者一次數據請求須要請求兩次 api。對於 activity 來講,它既與界面的展現,事件等有關係,又與業務數據層有着直接的關係,無疑 activity 層會極劇膨脹,變得極難閱讀和維護。
在這種結構下, activity 同時承擔了 view 層和 controller 層的工做,因此咱們須要給 activity 減負
因此,咱們來看看 MVP 是如何作這項工做的(圖片來自參考文獻)
這是一個比較典型的 MVP 結構圖,相比於第一張圖,多了兩個層,一個是 Presenter 和 DataManager 層。
所謂自古圖片留不住,老是代碼得人心。下面用代碼來講明這個結構的實現。
首先是 View 層的 Activity,假設有一個最簡單的從 Preference 中獲取字符串的界面
public class MainActivity extends Activity implements MainView { MainPresenter presenter; TextView mShowTxt; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mShowTxt = (TextView)findViewById(R.id.text1); loadDatas(); } public void loadDatas() { presenter = new MainPresenter(); presenter.addTaskListener(this); presenter.getString(); } @Override public void onShowString(String str) { mShowTxt.setText(str); } }
Activity 裏面包含了幾個文件,一個是 View 層的對外接口 MainView,一個是P層 Presenter
首先對外接口 MainView 文件
public interface MainView { void onShowString(String json); }
由於這個界面比較簡單,只須要在界面上顯示一個字符串,因此只有一個接口 onShowString,再看P層代碼
public class MainPresenter { MainView mainView; TaskManager taskData; public MainPresenter() { this.taskData = new TaskManager(new TaskDataSourceImpl()); } public MainPresenter test() { this.taskData = new TaskManager(new TaskDataSourceTestImpl()); return this; } public MainPresenter addTaskListener(MainView viewListener) { this.mainView = viewListener; return this; } public void getString() { String str = taskData.getTaskName(); mainView.onShowString(str); } }
能夠看到 Presenter 層是鏈接 Model 層和 View 層的中間層,所以持有 View 層的接口和 Model 層的接口。這裏就能夠看到 MVP 框架的威力了,經過接口的形式將 View 層和 Model 層徹底隔離開來。
接口的做用相似給層與層之間制定的一種通訊協議,兩個不一樣的層級相互交流,只要遵照這些協議便可,並不須要知道具體的實現是怎樣
看到這裏,有人可能就要問,這跟直接調用有什麼區別,爲何要大費周章的給 view 層和 Model 層各設置一個接口呢?具體緣由,咱們看看 Model 層的實現類就知道了。
下面這個文件是 DataManager.java,對應的是圖中的 DataManager 模塊
/** * 從數據層獲取的數據,在這裏進行拼裝和組合 */ public class TaskManager { TaskDataSource dataSource; public TaskManager(TaskDataSource dataSource) { this.dataSource = dataSource; } public String getShowContent() { //Todo what you want do on the original data return dataSource.getStringFromRemote() + dataSource.getStringFromCache(); } }
TaskDataSource.java 文件
/** * data 層接口定義 */ public interface TaskDataSource { String getStringFromRemote(); String getStringFromCache(); }
TaskDataSourceImpl.java 文件
public class TaskDataSourceImpl implements TaskDataSource { @Override public String getStringFromRemote() { return "Hello "; } @Override public String getStringFromCache() { return "World"; } }
TaskDataSourceTestImpl.java 文件
public class TaskDataSourceTestImpl implements TaskDataSource { @Override public String getStringFromRemote() { return "Hello "; } @Override public String getStringFromCache() { return " world Test "; } }
從上面幾個文件來看, TaskDataSource.java 做爲數據層對外的接口, TaskDataSourceImpl.java 是數據層,直接負責數據獲取,不管是從api得到,仍是從本地數據庫讀取數據,本質上都是IO操做。 TaskManager 是做爲業務層,對獲取到的數據進行拼裝,而後交給調用層。
這裏咱們來看看分層的做用
首先來說業務層 TaskManager,業務層的上層是 View 層,下層是 Data 層。在這個類裏,只有一個 Data 層的接口,因此業務層是不關心數據是如何取得,只須要經過接口得到數據以後,對原始的數據進行組合和拼裝。由於徹底與其上層和下層分離,因此咱們在測試的時候,能夠徹底獨立的是去測試業務層的邏輯。
TaskManager 中的 construct 方法的參數是數據層接口,這意味着咱們能夠給業務層注入不一樣的數據層實現。 正式線上發佈的時候注入 TaskDataSourceImpl 這個實現,在測試業務層邏輯的時候,注入 TaskDataSourceTestImpl.java 實現。
這也正是使用接口來處理每一個層級互相通訊的好處,能夠根據使用場景的不用,使用不一樣的實現
到如今爲止一個基於 MVP 簡單框架就搭建完成了,但其實還遺留了一個比較大的問題。
Android 規定,主線程是沒法直接進行網絡請求,會拋出 NetworkOnMainThreadException 異常
咱們回到 Presenter 層,看看這裏的調用。由於 presenter 層並不知道業務層以及數據層究竟是從網絡獲取數據,仍是從本地獲取數據(符合層級間相互透明的原則),由於每次調用均可能存在觸發這個問題。而且咱們知道,即便是從本地獲取數據,一次簡單的IO訪問也要消耗10MS左右。所以多而複雜的IO可能會直接引起頁面的卡頓。
理想的狀況下,全部的數據請求都應當在線程中完成,主線程只負責頁面渲染的工做
固然,Android 自己提供一些方案,好比下面這種:
public void getString() { final Handler mainHandler = new Handler(Looper.getMainLooper()); new Thread(){ @Override public void run() { super.run(); final String str = taskData.getShowContent(); mainHandler.post(new Runnable() { @Override public void run() { mainView.onShowString(str); } }); } }.start(); }
經過新建子線程進行IO讀寫獲取數據,而後經過主線程的 Looper 將結果經過傳回主線程進行渲染和展現。
但每一個調用都這樣寫,首先是新建線程會增長額外的成功,其次就是代碼看起來很難讀,縮進太多。
好在有了 RxJava ,能夠比較方便的解決這個問題。
RxJava 是一個天生用來作異步的工具,相比 AsyncTask, Handler 等,它的優勢就是簡潔,無比的簡潔。
在 Android 中使用 RxJava 須要加入下面兩個依賴
compile 'io.reactivex:rxjava:1.0.14' compile 'io.reactivex:rxandroid:1.0.1'
這裏咱們直接介紹如何使用 RxJava 解決這個問題,直接在 presenter 中修改調用方法 getString
public class MainPresenter { MainView mainView; TaskManager taskData; public MainPresenter() { this.taskData = new TaskManager(new TaskDataSourceImpl()); } public MainPresenter test() { this.taskData = new TaskManager(new TaskDataSourceTestImpl()); return this; } public MainPresenter addTaskListener(MainView viewListener) { this.mainView = viewListener; return this; } public void getString() { Func1 dataAction = new Func1<String,String>() { @Override public String call(String param) { return taskData.getTaskName(); } } Action1 viewAction = new Action1<String>() { @Override public void call( String str) { mainView.onShowString(str); } }; Observable.just("") .observeOn(Schedulers.io()) .map(dataAction) .observeOn(AndroidSchedulers.mainThread()) .subscribe(view); } }
簡單說明一下,與業務數據層的交互被定義到 Action1 裏,而後交由 rxJava,指定 Schedulers.io() 獲取到的線程來執行。Shedulers.io() 是專門用來進行IO訪問的線程,而且線程會重複利用,不須要額外的線程管理。而數據返回到 View 層的操做是在 Action1 中徹底,由 rxJava 交由 AndroidSchedulers.mainThread() 指定的UI主線程來執行。
從代碼量上來說,似比上一種方式要更多了,但實際上,當業務複雜度成倍增長的時候,RxJava 能夠採用這種鏈式編程方式隨意的增長調用和返回,而實現方式要比前面的方法靈活得多,簡潔得多。
具體的內容就不在這裏講了,你們能夠看參考下面的文章(可在 google 搜到):
RxJava 的使用場景遠不止這些,在上面第三篇文章提到了如下幾種使用場景:
至此爲止,經過 MVP+RxJava 的組合,咱們已經構建出一個比較靈活的 Android 項目框架,總共分紅了四部分:View 層,Presenter 層,Model 業務層,Data 數據持久化層。這個框架的優勢大概有如下幾點:
固然,這種方式可能還存在着各類各樣的問題,歡迎同窗們提出建議
更多精彩內容歡迎關注bugly的微信公衆帳號:
騰訊 Bugly是一款專爲移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的狀況以及解決方案。智能合併功能幫助開發同窗把天天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響用戶數最多的崩潰,精準定位功能幫助開發同窗定位到出問題的代碼行,實時上報能夠在發佈後快速的瞭解應用的質量狀況,適配最新的 iOS, Android 官方操做系統,鵝廠的工程師都在使用,快來加入咱們吧!