【騰訊Bugly乾貨分享】一步一步實現Android的MVP框架

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

內容大綱:react

  1. Android 開發框架的選擇
  2. 如何一步步搭建分層框架
  3. 使用 RxJava 來解決主線程發出網絡請求的問題
  4. 結語

1、Android開發框架的選擇

因爲原生 Android 開發應該已是一個基礎的 MVC 框架,因此在初始開發的時候並無遇到太多框架上的問題,但是一旦項目規模到了必定的程度,就須要對整個項目的代碼結構作一個整體上的規劃,最終的目的是使代碼可讀,維護性好,方便測試。’android

只有項目複雜度到了必定程度才須要使用一些更靈活的框架或者結構,簡單來講,寫個 Hello World 並不須要任何第三方的框架git

原生的 MVC 框架遇到大規模的應用,就會變得代碼難讀,很差維護,沒法測試的囧境。所以,Android 開發方面也有不少對應的框架來解決這些問題。github

構建框架的最終目的是加強項目代碼的可讀性維護性方便測試 ,若是背離了這個初衷,爲了使用而使用,最終是得不償失的數據庫

從根本上來說,要解決上述的三個問題,核心思想無非兩種:一個是分層 ,一個是模塊化 。兩個方法最終要實現的就是解耦,分層講的是縱向層面上的解耦,模塊化則是橫向上的解耦。下面咱們來詳細討論一下 Android 開發如何實現不一樣層面上的解耦。編程

解耦的經常使用方法有兩種:分層模塊化json

橫向的模塊化對你們來可能並不陌生,在一個項目創建項目文件夾的時候就會遇到這個問題,一般的作法是將相同功能的模塊放到同一個目錄下,更復雜的,能夠經過插件化來實現功能的分離與加載。api

縱向的分層,不一樣的項目可能就有不一樣的分法,而且隨着項目的複雜度變大,層次可能愈來愈多。緩存

對於經典的 Android MVC 框架來講,若是隻是簡單的應用,業務邏輯寫到 Activity 下面並沒有太多問題,但一旦業務逐漸變得複雜起來,每一個頁面之間有不一樣的數據交互和業務交流時,activity 的代碼就會急劇膨脹,代碼就會變得可讀性,維護性不好。

因此這裏咱們就要介紹 Android 官方推薦的 MVP 框架,看看 MVP 是如何將 Android 項目層層分解。

2、如何一步步搭建分層框架

若是你是個老司機,能夠直接參考下面幾篇文章(可在 google 搜到):

  1. Android Application Architecture
  2. Android Architecture Blueprints - Github
  3. Google 官方 MVP 示例之 TODO-MVP - 簡書
  4. 官方示例1-todo-mvp - github
  5. dev-todo-mvp-rxjava - github

固然若是你以爲看官方的示例太麻煩,那麼本文會經過最簡潔的語言來說解如何經過 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 ,能夠比較方便的解決這個問題。

3、使用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 搜到):

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

RxJava 的使用場景遠不止這些,在上面第三篇文章提到了如下幾種使用場景:

  1. 取數據先檢查緩存的場景
  2. 須要等到多個接口併發取完數據,再更新
  3. 一個接口的請求依賴另外一個API請求返回的數據
  4. 界面按鈕須要防止連續點擊的狀況
  5. 響應式的界面
  6. 複雜的數據變換

4、結語

至此爲止,經過 MVP+RxJava 的組合,咱們已經構建出一個比較靈活的 Android 項目框架,總共分紅了四部分:View 層,Presenter 層,Model 業務層,Data 數據持久化層。這個框架的優勢大概有如下幾點:

  • 每層各自獨立,經過接口通訊
  • 實現與接口分離,不一樣場景(正式,測試)能夠掛載不一樣的實現,方便測試和開發寫假數據
  • 全部的業務邏輯都在非UI線程中進行,最大限度減小IO操做對UI的影響
  • 使用 RxJava 能夠將複雜的調用進行鏈式組合,解決多重回調嵌套問題

固然,這種方式可能還存在着各類各樣的問題,歡迎同窗們提出建議

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

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

相關文章
相關標籤/搜索