Android官方MVP架構解讀
綜述
對于MVP (Model View Presenter)架構是從著名的MVC(Model View Controller)架構演變而來的。而對于Android應用的開發中本身可視為一種MVC架構。通常在開發中將XML文件視為MVC中的View角色,而將Activity則視為MVC中的Controller角色。不過更多情況下在實際應用開發中Activity不能夠完全充當Controller,而是Controller和View的合體。于是Activity既要負責視圖的顯示,又要負責對業務邏輯的處理。這樣在Activity中代碼達到上千行,甚至幾千行都不足為其,同時這樣的Activity也顯得臃腫不堪。所以對于MVC架構并不很合適運用于Android的開發中。下面就來介紹一下MVP架構以及看一下google官方給出的MVP架構示例。
MVP架構簡介
對于一個應用而言我們需要對它抽象出各個層面,而在MVP架構中它將UI界面和數據進行隔離,所以我們的應用也就分為三個層次。
- View: 對于View層也是視圖層,在View層中只負責對數據的展示,提供友好的界面與用戶進行交互。在Android開發中通常將Activity或者Fragment作為View層。
- Model: 對于Model層也是數據層。它區別于MVC架構中的Model,在這里不僅僅只是數據模型。在MVP架構中Model它負責對數據的存取操作,例如對數據庫的讀寫,網絡的數據的請求等。
Presenter:對于Presenter層他是連接View層與Model層的橋梁并對業務邏輯進行處理。在MVP架構中Model與View無法直接進行交互。所以在Presenter層它會從Model層獲得所需要的數據,進行一些適當的處理后交由View層進行顯示。這樣通過Presenter將View與Model進行隔離,使得View和Model之間不存在耦合,同時也將業務邏輯從View中抽離。
下面通過MVP結構圖來看一下MVP中各個層次之間的關系。
在MVP架構中將這三層分別抽象到各自的接口當中。通過接口將層次之間進行隔離,而Presenter對View和Model的相互依賴也是依賴于各自的接口。這點符合了接口隔離原則,也正是面向接口編程。在Presenter層中包含了一個View接口,并且依賴于Model接口,從而將Model層與View層聯系在一起。而對于View層會持有一個Presenter成員變量并且只保留對Presenter接口的調用,具體業務邏輯全部交由Presenter接口實現類中處理。
官方MVP架構分析
項目介紹
對于MVP架構有了一些的了解,而在前端時間Google給出了一些App開發架構的實現。
項目地址為:https://github.com/googlesamples/android-architecture.
在這里進入README看一下這次Google給出那些Android開發架構的實現。
對于上面五個開發架構的實現表示到目前為止是已經完成的項目,而下面兩個則表示正在進行的中的項目。現在首先來介紹一下這幾個架構。
- todo-mvp: 基礎的MVP架構。
- todo-mvp-loaders:基于MVP架構的實現,在獲取數據的部分采用了loaders架構。
- todo-mvp-databinding: 基于MVP架構的實現,采用了數據綁定組件。
- todo-mvp-clean: 基于MVP架構的clean架構的實現。
- todo-mvp-dagger2: 基于MVP架構,采用了依賴注入dagger2。
- dev-todo-mvp-contentproviders: 基于mvp-loaders架構,使用了ContenPproviders。
dev-todo-mvp-rxjava: 基于MVP架構,對于程序的并發處理和數據層(MVP中的Model)的抽象。
從上述的介紹中可以看出,對于官方給出所有的架構的實現最終都是基于MVP架構。所以在這里就對上面的基礎的MVP架構todo-mvp進行分析。
項目結構的分析
對于這個項目,它實現的是一個備忘錄的功能。對于工作中未完成的任務添加到待辦任務列表中。我們能夠在列表中可以對已完成的任務做出標記,能夠進入任務詳細頁面修改任務內容,也能夠對已完成的任務和未完成的任務數量做出統計。
首先在這里來看一下todo-mvp整體的項目結構
從上圖中可以看出,項目整體包含了一個app src目錄,四個測試目錄。在src目錄下面對代碼的組織方式是按照功能進行劃分。在這個項目中包含了四個功能,它們分別是:任務的添加編輯(addedittask),任務完成情況的統計(statistics),任務的詳情(taskdetail),任務列表的顯示(tasks)。對于data包它是項目中的數據源,執行數據庫的讀寫,網絡的請求操作都存放在該包內,也是MVP架構中的Model層。而util包下面則是存放一些項目中使用到的工具類。在最外層存放了兩接口BasePresenter和BaseView。它們是Presenter層接口和View層接口的基類,項目中所有的Presenter接口和View層接口都繼承自這兩個接口。
現在進入功能模塊內看下在模塊內部對類是如何劃分的。在每個功能模塊下面將類分作xxActivity,xxFragment,xxPresenter,xxContract。也正是這些類構成了項目中的Presenter層與View層。下面就來分析在這個項目中是如何實現MVP架構。
MVP架構的實現
在這里只從宏觀上關注MVP架構的實現,對于代碼的內部細節在就不在具體分析。那么就以任務的添加和編輯這個功能來看一下Android官方是如何實現MVP架構。
Model層的實現
首先我們從MVP架構的最內層開始分析,也就是對應的Model層。在這個項目中對應的data包下的內容。在data下對數據庫等一些數據源的封裝。對于Presenter層提供了TasksDataSource接口。在這里看一下這個TasksDataSource接口。
public interface TasksDataSource {
interface LoadTasksCallback {
void onTasksLoaded(List<Task> tasks);
void onDataNotAvailable();
}
interface GetTaskCallback {
void onTaskLoaded(Task task);
void onDataNotAvailable();
}
void getTasks(@NonNull LoadTasksCallback callback);
void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
void saveTask(@NonNull Task task);
......
}
TasksDataSource接口的實現是TasksLocalDataSource,在TasksDataSource中的方法也就是一些對數據庫的增刪改查的操作。而在TasksDataSource的兩個內部接口LoadTasksCallback和GetTaskCallback是Model層的回調接口。它們的真正實現是在Presenter層。對于成功獲取到數據后變或通過這個回調接口將數據傳遞Presenter層。同樣,若是獲取失敗同樣也會通過回調接口來通知Presenter層。下面來看一下TasksDataSource的實現類。
public class TasksLocalDataSource implements TasksDataSource {
......
@Override
public void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback) {
//根據taskId查訓出相對應的task
......
if (task != null) {
callback.onTaskLoaded(task);
} else {
callback.onDataNotAvailable();
}
}
@Override
public void saveTask(@NonNull Task task) {
checkNotNull(task);
SQLiteDatabase db = mDbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(TaskEntry.COLUMN_NAME_ENTRY_ID, task.getId());
values.put(TaskEntry.COLUMN_NAME_TITLE, task.getTitle());
values.put(TaskEntry.COLUMN_NAME_DESCRIPTION, task.getDescription());
values.put(TaskEntry.COLUMN_NAME_COMPLETED, task.isCompleted());
db.insert(TaskEntry.TABLE_NAME, null, values);
db.close();
}
......
}
在這里我們針對任務的添加和編輯功能,所以省略很多代碼。可以看出在TasksLocalDataSource中實現的getTask方法,在這個方法中傳入TasksDataSource內的GetTaskCallback回調接口。在getTask方法的實現可以看出在查詢到Task以后調用回調方法,若是在Presenter層中實現了這兩個回調方法,便將數據傳遞到Presenter層。而對于查詢到的Task為空的時候也是通過回調方法執行對應的操作。
同樣對于通過網絡請求獲取到數據也是一樣,對于成功請求到的數據可以通過回調方法將數據傳遞到Presenter層,對于網絡請求失敗也能夠通過回調方法來執行相對應的操作。
Presenter與View層提供的接口
由于在Presenter和View層所提供的接口在一個類中,在這里就先來查看他們對外所提供了哪些接口。首先觀察一下兩個基類接口BasePresenter和BaseView。
BasePresenter
package com.example.android.architecture.blueprints.todoapp;
public interface BasePresenter {
void start();
}
在BasePresenter中只存在一個start方法。這個方法一般所執行的任務是在Presenter中從Model層獲取數據,并調用View接口顯示。這個方法一般是在Fragment中的onResume方法中調用。
BaseView
package com.example.android.architecture.blueprints.todoapp;
public interface BaseView<T> {
void setPresenter(T presenter);
}
在BaseView中只有一個setPresenter方法,對于View層會存在一個Presenter對象。而setPresenter正是對View中的Presenter進行初始化。
AddEditTaskContract
在Android官方給出的MVP架構當中對于Presenter接口和View接口提供的形式與我們平時在網上所見的有所不同。在這里將Presenter中的接口和View的接口都放在了AddEditTaskContract類里面。這樣一來我們能夠更清晰的看到在Presenter層和View層中有哪些功能,方便我們以后的維護。下面就來看一下這個AddEditTaskContract類。
public interface AddEditTaskContract {
interface View extends BaseView<Presenter> {
void showEmptyTaskError();
void showTasksList();
void setTitle(String title);
void setDescription(String description);
boolean isActive();
}
interface Presenter extends BasePresenter {
void createTask(String title, String description);
void updateTask( String title, String description);
void populateTask();
}
}
在這里很清晰的可以看出在View層中處理了一些數據顯示的操作,而在Presenter層中則是對Task保存,更新等操作。
Presenter層的實現
下面就來看一下在Presenter是如何實現的。
public class AddEditTaskPresenter implements AddEditTaskContract.Presenter,
TasksDataSource.GetTaskCallback {
......
public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
@NonNull AddEditTaskContract.View addTaskView) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository);
mAddTaskView = checkNotNull(addTaskView);
mAddTaskView.setPresenter(this);
}
@Override
public void start() {
if (mTaskId != null) {
populateTask();
}
}
......
@Override
public void populateTask() {
if (mTaskId == null) {
throw new RuntimeException("populateTask() was called but task is new.");
}
mTasksRepository.getTask(mTaskId, this);
}
@Override
public void onTaskLoaded(Task task) {
// The view may not be able to handle UI updates anymore
if (mAddTaskView.isActive()) {
mAddTaskView.setTitle(task.getTitle());
mAddTaskView.setDescription(task.getDescription());
}
}
......
}
在這里可以看到在AddEditTaskPresenter中它不僅實現了自己的Presenter接口,也實現了GetTaskCallback的回調接口。并且在Presenter中包含了Model層TasksDataSource的對象mTasksRepository和View層AddEditTaskContract.View的對象mAddTaskView。于是整個業務邏輯的處理就擔負在Presenter的身上。
從Presenter的業務處理中可以看出,首先調用Model層的接口getTask方法,通過TaskId來查詢Task。在查詢到Task以后,由于在Presenter層中實現了Model層的回調接口GetTaskCallback。這時候在Presenter層中就通過onTaskLoaded方法獲取到Task對象,最后通過調用View層接口實現了數據的展示。
View層的實現
對于View的實現是在Fragment中,而在Activity中則是完成對Fragment的添加,Presenter的創建操作。下面首先來看一下AddEditTaskActivity類。
public class AddEditTaskActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addtask_act);
......
if (addEditTaskFragment == null) {
addEditTaskFragment = AddEditTaskFragment.newInstance();
......
ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
addEditTaskFragment, R.id.contentFrame);
}
// Create the presenter
new AddEditTaskPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
addEditTaskFragment);
}
......
}
對于Activity的提供的功能也是非常的簡單,首先創建Fragment對象并將其添加到Activity當中。之后創建Presenter對象,并將Fragment也就是View傳遞到Presenter中。
下面再來看一下View的實現,也就是Fragment。
public class AddEditTaskFragment extends Fragment implements AddEditTaskContract.View {
......
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
@Override
public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
......
@Override
public void setTitle(String title) {
mTitle.setText(title);
}
......
}
在這對于源碼就不在過多貼出。在Fragment中,通過setPresenter獲取到Presenter對象。并通過調用Presenter中的方法來實現業務的處理。而在Fragment中則只是對UI的一些操作。這樣一來對于Fragment類型的代碼減少了很多,并且邏輯更加清晰。
我們注意到View層的實現是通過Fragment來完成的。對于View的實現為什么要采用Fragment而不是Activity。來看一下官方是如何解釋的。
The separation between Activity and Fragment fits nicely with this implementation of MVP: the Activity is the overall controller that creates and connects views and presenters.
Tablet layout or screens with multiple views take advantage of the Fragments framework.
在這里官方對于采用Fragment的原因給出了兩種解釋。
- 通過Activity和Fragment分離非常適合對于MVP架構的實現。在這里將Activity作為全局的控制者將Presenter于View聯系在一起。
- 采用Fragment更有利于平板電腦的布局或者是多視圖屏幕。
總結
通過MVP架構的使用可以看出對于各個層次之間的職責更加單一清晰,同時也很大程度上降低了代碼的耦合度。對于官方MVP架構示例,google也明確表明對于他們所給出的這些架構示例只是作為參考,而不是一個標準。所以對于基礎的MVP架構有更大的擴展空間。例如綜合google給出的示例。我們可以通過在MVP架構的基礎上使用dagger2,rxJava等來構建一個Clean架構。也是一個很好的選擇。
智能推薦
Android MVP 架構
Android Boilerplate location:https://github.com/ribot/android-boilerplate Sample Android app that we use at ribot as a reference for new Android projects. It demonstrates the architecture, tools and g...
Android 官方架構示例android-architecture之todo-mvp深入解析
google在GitHub上開源了android-architecture項目,包含了MVP、MVVM等架構的示例項目,今天我們從todo?mvp開始入手,研究里面代碼的具體實現 項目地址 todo-mvp項目地址 應用功能介紹 了解一個項目的主要功能最快的方法就是直接安裝,然后運行,就可以知道主要有哪些頁面,有哪些功能 todo示例項目是一個代辦事項的簡單App,有四個頁面 待辦列表頁 待辦詳情...
Google官方關于Android架構中MVP模式的示例續-DataBinding
基于前面的TODO示例,使用Data Binding庫來顯示數據并綁定UI元素的響應動作。 這個示例并未嚴格遵循 Model-View-ViewModel 或 Model-View-Presenter 模式,其中既有View Model,也有Presenter。 用到的Data Binding庫保存樣板代碼,該代碼允許將UI元素綁定到Data Model的一個屬性。 布局文件中直接綁定數據到UI元...
基于Android真實項目教你一步一步搭建架構2 -- Google官方Mvp架構
前言 筆者上一篇文章說過,上家公司的項目架構是在Google官方todo-mvp-Rxjava的Demo架構基礎上進行搭建的,而這個todo-mvp-Rxjava架構是基于todo-mvp加入rxjava搭建的,所以我們還是先來看下Android-architecture項目的todo-mvp分支 官方todo-mvp地址 todo-mvp項目結構 todo-mvp主要使用了mvp架構來實現(如果...
Android中的MVP架構
一、架構演變概述 我記得我找第一份工作時,面試官問我“android是否屬于MVC架構模式,簡述一下”。確實,Android的整體設計結構就是MVC的設計模式,在J2EE的開發中,使用的也是MVC模式,MVC模式是一個經典,經歷了幾十年的考驗。Android項目中的MVC架構: View:是應用程序中處理數據顯示的部分,對應于layout文件下的布局文件 Model:業務邏...
猜你喜歡
Android架構--MVC、MVP、MVVM
前言 MVC、MVP、MVVM是當下Android開發中主要的項目架構,當你開發一個app時,合理的軟件架構有利于后期的維護。Android開發中我們可以選擇的架構只有以上三種,三種架構各有優缺點,我們可以從中選擇最合適我們的架構。 MVC MVC(Model-View-Controller)是最常見的軟件架構之一,也是我們剛開始做Android開發選用的架構。 數據關系為: - View接收用戶...
Android MVP架構簡單示例
M:Model V:View P:Presenter MVP:主要思想就是把UI抽象成View接口,把業務邏輯抽象成Presenter接口. 下面寫一個比較常見的登錄例子,MainActivity實現登錄,OtherActivity模擬文件下載,寫了兩個Activity,BasePresenter使用泛型,是因為MainBaseActivity和...
android之MVP架構一
android界一直流傳著這樣一種架構–MVP!What, Most Valueable Player?!NO,NO,NO! 此MVP非彼MVP,打籃球的還搞不懂android的。那么MVP架構到底是什么呢? —MVP其實同MVC一樣,是一種編程模式和思想,也許更準確地講是一種架構。 MVC簡介 開發Android的都知道MVC。 M對應Model,代表業務數據 V對應Vi...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...