Android MVP 實戰(zhàn)經(jīng)驗

一夜矗、表現(xiàn)層模式架構(gòu)的演變

三層架構(gòu)通常是指表現(xiàn)層(Presentation Layer)究西、業(yè)務(wù)邏輯層(Business Layer)和數(shù)據(jù)訪問層(Data Access Layer)俭缓。表現(xiàn)層是用戶和系統(tǒng)之間交流的橋梁,它一方面提供與用戶交互的界面箕憾,另一方面也提供了與數(shù)據(jù)交互的邏輯诬辈,便于協(xié)調(diào)用戶與系統(tǒng)的操作。我們所談?wù)摰?MVC趴乡、MVP对省、MVVM 等設(shè)計模式都屬于表現(xiàn)層的設(shè)計模式。

1. MVC(Model-View-Controller)模式

MVC

如果把 MVC 模式套用在 Android 中浙宜,那么:

  • View 對應(yīng) xml 布局官辽,實現(xiàn)數(shù)據(jù)的展示
  • Controller 對應(yīng) Activity / Fragment ,處理業(yè)務(wù)邏輯
  • Model 對應(yīng)數(shù)據(jù)源粟瞬,包括網(wǎng)絡(luò)接口數(shù)據(jù)同仆、數(shù)據(jù)庫、緩存等

使用 MVC 模式看似分工明確裙品,但是應(yīng)用到 Android 中俗批,會帶不少的問題:

  • Activity / Fragment 實現(xiàn)了多重職責(zé)俗或,即是 View,又是 Controller岁忘,導(dǎo)致代碼復(fù)雜臃腫辛慰,難以復(fù)用
  • 把 Activity / Fragment 作為 Controller,無法對 Controller 進行單元測試
    所以干像,在 Android 中帅腌,MVC 模式不太適用。

2. MVP(Model-View-Presenter) 模式

MVP 模式是 MVC 的進化版麻汰,它把 Controller 的職責(zé)從 Activity/Fragment 中拆分出來速客,作為 Presenter,這樣就實現(xiàn)了 Activity/Fragment 和業(yè)務(wù)邏輯的解耦五鲫,更好地解決了數(shù)據(jù)與界面的關(guān)系溺职。

二、細(xì)說 MVP

1. 職責(zé)劃分

MVP 各自的職責(zé)分別是:

  • View

    1. 對應(yīng) Activity / Fragment / Custom View
    2. 與用戶交互位喂,響應(yīng)用戶操作浪耘,分派事件行為給 Presenter 處理
    3. 響應(yīng) Presenter 回調(diào),對數(shù)據(jù)進行顯示
  • Presenter

    1. 是連接 VIew 和其它代碼的膠水
    2. 用于轉(zhuǎn)換 Model 的數(shù)據(jù)以便于 VIew 顯示
    3. Presenter 不做 UI 相關(guān)處理塑崖,也不包含上下文對象(Context)
  • Model

    1. 與數(shù)據(jù)進行交互七冲,對數(shù)據(jù)進行加工處理
    2. 通常是與 Android 無關(guān)的,不會用到 Android SDK
    3. 關(guān)注從哪里拿數(shù)據(jù)(Retrofit规婆、Sqlite etc.)

2. 架構(gòu)實現(xiàn)

MVP

谷歌官方已經(jīng)給出了一個 MVP 架構(gòu)的實踐示例[googlesamples/android-architecture](上面圖中的 REPOSITORIES 就是指 Model 層)癞埠。

接下來,我們以它為例來看一下具體的實現(xiàn)聋呢。我們簡化一下代碼,更直觀地看一下它們之間的關(guān)系颠区。

首先官方的例子定義了一個契約(Contract)類:

public interface TasksContract {
    interface View extends BaseView<Presenter> {
        void setLoadingIndicator(boolean active);
        void showTasks(List<Task> tasks);'' 
        void showLoadingTasksError();
        // ....
    }

    interface Presenter extends BasePresenter {
       void loadTasks();
       void addNewTask();
        // ....
    }
}

契約類就是把 MVP 所需要定義的幾個接口都寫在一個類里面削锰。在項目實現(xiàn)中,我會把 Model 接口也寫到契約類里毕莱,定義契約類的好處除了可以少寫幾個 Java 文件外器贩,也比較直觀。比如先在 Presenter 定義一個 loadTasks 方法朋截,那么相應(yīng)地蛹稍,Model 就接口需要定義一個 getTasks 方法來為 Presenter 提供數(shù)據(jù),View 接口則需要定義一個 showTasks 來顯示獲取到 Tasks 數(shù)據(jù)部服,以及 setLoadingIndicator唆姐、showLoadingTasksError 等方法來更新 UI 狀態(tài)。

  • Model 層的實現(xiàn)代碼:
public class TasksRepository implements TasksDataSource  {
    @Inject 
    TasksRepository(@Remote TasksDataSource tasksRemoteDataSource, 
                    @Local TasksDataSource tasksLocalDataSource) {
        mTasksRemoteDataSource = tasksRemoteDataSource;
        mTasksLocalDataSource = tasksLocalDataSource;
    }

    @Override
    public Observable<List<Task>> getTasks() {
        // 從緩存或者網(wǎng)絡(luò)接口等數(shù)據(jù)源獲取數(shù)據(jù)
    }
}

Model 層的代碼維護了兩個數(shù)據(jù)源(mTasksRemoteDataSource 和 mTasksLocalDataSource)廓八,用來為 Presenter 提供數(shù)據(jù)奉芦,Presenter 無需關(guān)心從哪里拿數(shù)據(jù)赵抢。代碼中的注解 @Inject 標(biāo)記了該構(gòu)造方法可以被注入,如果你使用了 Google 的 Dagger 框架声功,Dagger 可以提供 TasksRepository 所需的依賴烦却,并創(chuàng)建一個 TasksRepository 對象。

  • Presenter 層的實現(xiàn)代碼:
public class TasksPresenter implements TasksContract.Presenter {
    @Inject
    TasksPresenter(TasksRepository tasksRepository, TasksContract.View tasksView) {
        mTasksRepository = tasksRepository;
        mTasksView = tasksView;
    }

    @Override
    public void loadTasks() {
        mTasksRepository.getTasks()
                .observeOn(mSchedulerProvider.ui())
                .subscribe(new Observer<List<Task>>() {
                    @Override
                    public void onCompleted() {
                        mTasksView.setLoadingIndicator(false);
                    }
                    @Override
                    public void onError(Throwable e) {
                        mTasksView.showLoadingTasksError();
                    }
                    @Override
                    public void onNext(List<Task> tasks) {
                        mTasksView.showTasks(tasks);
                    }
                });
    }
}

這里我們使用了 Dagger 和 RxJava(官方例子是分開兩個獨立的分支)先巴,Dagger 注入所需的依賴其爵,并創(chuàng)建 TasksPresenter 對象。View 通過調(diào)用 Presenter 的 loadTasks 方法來獲取便于 VIew 展示的數(shù)據(jù)伸蚯。Presenter 就是用于響應(yīng) View 分派的事件摩渺,校驗數(shù)據(jù)并提交給 Model 處理,最后把 Model 處理的結(jié)果轉(zhuǎn)交給 View朝卒。

另外证逻,Presenter 也承擔(dān)了一部分 Activity / Fragment 的業(yè)務(wù)邏輯,這樣也減輕了 Activity / Fragment 作為 View 的負(fù)擔(dān)抗斤。

  • View 層的實現(xiàn)代碼
public class TasksFragment extends Fragment implements TasksContract.View {
   private TasksContract.Presenter mPresenter;
   
   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
       swipeRefreshLayout.setOnRefreshListener(new OnRefreshListener() {
           @Override
           public void onRefresh() {
               mPresenter.loadTasks();
           }
       });
   } 

   @Override
   public void showLoadingTasksError() {
       showMessage(getString(R.string.loading_tasks_error));
   }

   @Override
   public void showTasks(List<Task> tasks) {
       mListAdapter.replaceData(tasks);
       mTasksView.setVisibility(View.VISIBLE);
       mNoTasksView.setVisibility(View.GONE);
    }
}

View 就比較簡單了囚企,純粹做 UI 相關(guān)的處理,不關(guān)注業(yè)務(wù)處理瑞眼。它將用戶的行為傳遞給 Presenter龙宏,同時接收 Presenter 的調(diào)用來更新界面。在上面的代碼中伤疙,當(dāng) TasksFragment 初始化之后银酗,會調(diào)用 swipeRefreshLayout.setRefreshing 來觸發(fā) Presenter 的 loadTasks() 方法。之后徒像,當(dāng) Presenter 處理完后黍特,調(diào)用 View 的 showTasks 或者 showLoadingTasksError 方法,TasksFragment 只需要關(guān)注界面更新的具體實現(xiàn)锯蛀。

從上面的例子來看灭衷,Model-View-Presenter 三種角色之間分工明確,使得數(shù)據(jù)與界面之間的耦合更低旁涤,代碼復(fù)用性更高翔曲,也更方便于測試。

三劈愚、總結(jié)

使用 MVP 模式來開發(fā) Android 應(yīng)用給我們帶來了很多好處瞳遍,只是需要多定義 M-V-P 這三個接口(可以寫在一個 Contract 類中),正是因為這些接口菌羽,才使得類的組織結(jié)構(gòu)更加清晰掠械,每一層實現(xiàn)對應(yīng)的接口,只關(guān)注其本身單一的職責(zé)。這樣層與層的耦合底非常低份蝴,可維護性提高犁功。

一路走來,我們也在不斷地嘗試婚夫,持續(xù)地改進現(xiàn)有的架構(gòu)浸卦。我們結(jié)合了目前流行的框架來提高生產(chǎn)力;比如案糙,使用 Dagger 來為 Presenter 注入依賴(不需要再用new關(guān)鍵字創(chuàng)建各種對象)限嫌,還使用了 Retrofit、RxJava时捌、Realm 等框架更好地去組織數(shù)據(jù)層的接口怒医,以及使用 DataBinding 來簡化 UI 界面與數(shù)據(jù)實體的綁定。

架構(gòu)的作用是為解決痛點奢讨,適合自己的才是最好的稚叹。希望本文對你找到適合自己項目的架構(gòu)組織方式有所幫助。

// 能力一般拿诸,水平有限扒袖。文中有不妥或謬誤之處在所難免,請大家批評指正亩码。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末季率,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子描沟,更是在濱河造成了極大的恐慌飒泻,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吏廉,死亡現(xiàn)場離奇詭異泞遗,居然都是意外死亡,警方通過查閱死者的電腦和手機席覆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門刹孔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人娜睛,你說我怎么就攤上這事∝远茫” “怎么了畦戒?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長结序。 經(jīng)常有香客問我障斋,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任垃环,我火速辦了婚禮邀层,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遂庄。我一直安慰自己寥院,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布涛目。 她就那樣靜靜地躺著秸谢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪霹肝。 梳的紋絲不亂的頭發(fā)上估蹄,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天,我揣著相機與錄音沫换,去河邊找鬼臭蚁。 笑死,一個胖子當(dāng)著我的面吹牛讯赏,可吹牛的內(nèi)容都是我干的垮兑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼待逞,長吁一口氣:“原來是場噩夢啊……” “哼甥角!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起识樱,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤嗤无,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后怜庸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體当犯,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年割疾,在試婚紗的時候發(fā)現(xiàn)自己被綠了嚎卫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡宏榕,死狀恐怖拓诸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情麻昼,我是刑警寧澤奠支,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站抚芦,受9級特大地震影響倍谜,放射性物質(zhì)發(fā)生泄漏迈螟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一尔崔、第九天 我趴在偏房一處隱蔽的房頂上張望答毫。 院中可真熱鬧,春花似錦季春、人聲如沸洗搂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚕脏。三九已至,卻和暖如春侦锯,著一層夾襖步出監(jiān)牢的瞬間驼鞭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工尺碰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挣棕,地道東北人。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓亲桥,卻偏偏與公主長得像洛心,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子题篷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內(nèi)容