Google Sample MVP
Android架構(gòu)藍圖
目前Android主流的開發(fā)架構(gòu):原生開發(fā)(MVC),MVP,MVVM等
今天簡單的說一下我對于Android架構(gòu)的了解和對Google MVP的認識分析知残。
說Android就不得不提到Java,Android的應(yīng)用層和Java有著不解之緣练湿,Android應(yīng)用層參考Java的實現(xiàn)并且進行了很多的優(yōu)化番枚,比如大家都熟悉的JVM與Android虛擬機。其實Android開發(fā)也是對于Java GUI圖形界面開發(fā)的優(yōu)化村砂,完成了MVC的分層烂斋,用布局文件(xml文件)完成了對于視圖布局的抽象和優(yōu)化。
以下是自己結(jié)合自己實際開發(fā)中的經(jīng)驗對MVP的一些感悟础废。
先看一下對于MVP架構(gòu)的目錄結(jié)構(gòu)圖
Note: in a MVP context, the term "view" is overloaded:
· The class android.view.View will be referred to as "Android View"
· The view that receives commands from a presenter in MVP, will be simply called "view".
注意:在MVP的上下文里汛骂,“view”一詞有多重含義:
· android.view.View被稱為“Android View”
· 在MVP中,從presenter接收命令的view將被簡單地稱為“view”评腺。
It uses fragments for two reasons:
· 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.
(MVP中的View實現(xiàn))使用Fragment有兩個原因:
· Activity與Fragment之間的分離很好的符合了MVP的實現(xiàn):Activity作為整體控制器來創(chuàng)建和連接views與presenters帘瞭。
· 平板布局或者屏幕上有多個views的布局可以很好的利用Fragments框架。
/**
* This specifies the contract between the view and the presenter.
*/
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 saveTask(String title, String description);
void populateTask();
}
}
**
* Listens to user actions from the UI ({@link AddEditTaskFragment}), retrieves the data and updates
* the UI as required.
*/
public class AddEditTaskPresenter implements AddEditTaskContract.Presenter,
TasksDataSource.GetTaskCallback {
……
/**
* Creates a presenter for the add/edit view.
*
* @param taskId ID of the task to edit or null for a new task
* @param tasksRepository a repository of data for tasks
* @param addTaskView the add/edit view
*/
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 (!isNewTask()) {
populateTask();
}
}
@Override
public void saveTask(String title, String description) {
if (isNewTask()) {
createTask(title, description);
} else {
updateTask(title, description);
}
}
……
}
public class AddEditTaskFragment extends Fragment implements AddEditTaskContract.View {
public static final String ARGUMENT_EDIT_TASK_ID = "EDIT_TASK_ID";
private AddEditTaskContract.Presenter mPresenter;
private TextView mTitle;
private TextView mDescription;
public static AddEditTaskFragment newInstance() {
return new AddEditTaskFragment();
}
public AddEditTaskFragment() {
// Required empty public constructor
}
@Override
public void onResume() {
super.onResume();
//BasePresenter接口的方法蒿讥,主要完成數(shù)據(jù)的初始化
mPresenter.start();
}
//在構(gòu)造函數(shù)中調(diào)用完成presenter和View的關(guān)聯(lián)
@Override
public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FloatingActionButton fab =
(FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task_done);
fab.setImageResource(R.drawable.ic_done);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.saveTask(mTitle.getText().toString(), mDescription.getText().toString());
}
});
}
……
}
public class AddEditTaskActivity extends AppCompatActivity {
……
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addtask_act);
……
// Create the presenter
new AddEditTaskPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
addEditTaskFragment);
}
……
}
在構(gòu)造方法中調(diào)用View的setPresenter方法與View建立聯(lián)系
Activity 在項目中是一個全局控制著蝶念,負責(zé)創(chuàng)建View以及Presenter,并將兩者聯(lián)系起來
Contract這個類是首次出現(xiàn)于google的mvp示例中芋绸,以前的MVP模式并未見到媒殉,這個類定義了View接口和Presenter接口為對方的實例提供的方法。容易的可以看出View和Presenter之間的操作摔敛。
這個契約類的好處是方便接口統(tǒng)一管理廷蓉、修改,同時马昙,內(nèi)容清晰桃犬,一目了然刹悴,維護起來也方便。
先來看看官方的代碼目錄(這里只是功能模塊的目錄攒暇,不包括測試模塊土匀,畢竟這里分析的是官方的實現(xiàn)代碼)
tasks 包可以顯示任務(wù)列表
taskdetail包顯示任務(wù)詳情
addedittask包添加和編輯任務(wù)
statistics包用來顯示任務(wù)的完成情況
data包數(shù)據(jù)模塊對應(yīng)mvp的M
Util包就是通用的方法。
我們的項目中也運用到了mvp的思想扯饶,其實mvp并沒有固定的寫法恒削,正確的去理解架構(gòu)的思想,都可以有自己獨特的mvp寫法尾序。
簡單說一下我們項目中MVP架構(gòu)的實現(xiàn)
我們項目中的實現(xiàn)與Google實現(xiàn)不同只有Presenter接口和接口的實現(xiàn)類钓丰,Presenter與View直接的調(diào)用關(guān)聯(lián)關(guān)系同過泛型參數(shù)實現(xiàn),這使得我們的具有類文件更少的優(yōu)勢每币,同時不可避免帶來了相互調(diào)用比較模糊携丁;(接口相互之間持有引用)
在我們的實現(xiàn)之中,F(xiàn)ragment或者Activity相當于作為了默認的View層(接口以及實現(xiàn)類)
我們的項目實現(xiàn)里面沒有明顯的Repository層兰怠,上層(activity/fragment/presenter)不需要知道數(shù)據(jù)的細節(jié)(或者說 - 數(shù)據(jù)源)梦鉴,來自于網(wǎng)絡(luò)、數(shù)據(jù)庫揭保,亦或是內(nèi)存等等肥橙。如此,一來上層可以不用關(guān)心細節(jié)秸侣,二來底層可以根據(jù)需求修改存筏,不會影響上層,兩者的分離用可以幫助協(xié)同開發(fā)
還有一個不同的是我們的Presenter加入了生命周期味榛,我認為這一定程度上增加了Presenter層的復(fù)雜程度(以及Onclick事件的處理AZ)
有時候我們還面臨一個問題椭坚,接口數(shù)據(jù)模型不一致,View不能方便的復(fù)用
在MVVM的模式中ViewModel的存在很好的解決了這個問題搏色;
MVVM的實現(xiàn)類似于觀察者模式善茎,采用了數(shù)據(jù)模型與xml布局文件綁定的形式;
很多MVVM的框架都是實現(xiàn)了布局文件與代碼的雙向綁定频轿,xml既可以調(diào)用java的代碼垂涯,改變數(shù)據(jù)模型的同時UI也能自己改變。
Google 也給出了MVP與DataBinding
t is based on the todo-mvp sample and uses the Data Binding library to display data and bind UI elements to actions.
It doesn't follow a strict Model-View-ViewModel or a Model-View-Presenter pattern, as it uses both View Models and Presenters.
The Data Binding Library saves on boilerplate code allowing UI elements to be bound to a property in a data model.
Layout files are used to bind data to UI elements
Events are also bound with an action handler
Data can be observed and set up to be updated automatically when needed
Diagram
MVVM對比與MVP的最大優(yōu)勢就是更加解耦UI邏輯與業(yè)務(wù)邏輯. View與ViewModel的耦合, 要弱于View與Presenter的耦合. View是ViewModel的消費者, 當修改UI時, 導(dǎo)致較少地修改ViewModel. 根據(jù)業(yè)務(wù)關(guān)注點(Concern), 設(shè)置更加多的高內(nèi)聚View與ViewModel, 在多個頁面中共享與替換.
MVVM使業(yè)務(wù)邏輯更加徹底地分離, 使用DataBinding分離UI顯示與UI邏輯, 實現(xiàn)View與ViewModel的一對多, ViewModel與Model的多對多, 模塊復(fù)用更加完備, 進一步提高可測試性.
MVP 的優(yōu)缺點
任何事務(wù)都存在兩面性航邢,MVP當然也不列外集币,我們來看看MVP的優(yōu)缺點。
優(yōu)點:
模塊職責(zé)劃分明顯翠忠,層次清晰
Presenter可以復(fù)用,一個Presenter可以用于多個View乞榨,而不需要更改Presenter的邏輯(當然是在View的改動不影響業(yè)務(wù)邏輯的前提下)
利于測試驅(qū)動開發(fā)秽之。以前的Android開發(fā)是難以進行單元測試的(雖然很多Android開發(fā)者都沒有寫過測試用例当娱,但是隨著項目變得越來越復(fù)雜,沒有測試是很難保證軟件質(zhì)量的考榨;而且近幾年來Android上的測試框架已經(jīng)有了長足的發(fā)展——開始寫測試用例吧)跨细,在使用MVP的項目中Presenter對View是通過接口進行,在對Presenter進行不依賴UI環(huán)境的單元測試的時候河质〖讲眩可以通過Mock一個View對象,這個對象只需要實現(xiàn)了View的接口即可掀鹅。然后依賴注入到Presenter中散休,單元測試的時候就可以完整的測試Presenter應(yīng)用邏輯的正確性。
View可以進行組件化乐尊。在MVP當中戚丸,View不依賴Model。這樣就可以讓View從特定的業(yè)務(wù)場景中脫離出來扔嵌,可以說View可以做到對業(yè)務(wù)完全無知限府。它只需要提供一系列接口提供給上層操作。這樣就可以做到高度可復(fù)用的View組件痢缎。
增加了Contract接口胁勺,便于模塊功能的管理和擴展。
缺點:
Presenter中除了應(yīng)用邏輯以外独旷,還有大量的View->Model署穗,Model->View的手動同步邏輯,造成Presenter比較笨重势告,維護起來會比較困難蛇捌。
由于對視圖的渲染放在了Presenter中,所以視圖和Presenter的交互會過于頻繁咱台。 UI復(fù)雜的界面Presenter過多地渲染了視圖络拌,往往會使得它與特定的視圖的聯(lián)系過于緊密。一旦視圖需要變更回溺,那么Presenter也需要變更了春贸。
額外的代碼復(fù)雜度及使用、學(xué)習(xí)成本遗遵。
MVP架構(gòu)更好地分離項目職責(zé), 解除業(yè)務(wù)邏輯與UI邏輯之間的耦合. 對于小型項目而言, 與設(shè)計模式類似, 使用接口會導(dǎo)致過度設(shè)計, 增加代碼量. 當處理復(fù)雜頁面時, Presenter層會包含大量UI邏輯與業(yè)務(wù)邏輯, 顯得非常冗余, 違反單一職責(zé)原理.
關(guān)于presenter一直持有Activity對象導(dǎo)致的內(nèi)存泄漏問題
只要用過mvp這個問題可能很多人都知道萍恕。寫mvp的時候,presenter會持有view车要,如果presenter有后臺異步的長時間的動作允粤,比如網(wǎng)絡(luò)請求,這時如果返回退出了Activity,后臺異步的動作不會立即停止类垫,這里就會有內(nèi)存泄漏的隱患司光,所以會在presenter中加入一個銷毀view的方法。