Android中MVP设计框架浅析

一、MVP设计模式简介

目前Android设计成熟的框架有MVC,MVP和MVVM,MVP是由MCV演变而来,MVVM是MVP的进一步升级。三种框架模式并没有完全的哪一种最好,只有在项目的需求上哪一种框架最适合。根据前人总结的项目经验,小项目用MVC,中型项目用MVP,大型项目用MVVM。

二、MVP版本的演进史

2.1 MVC在Android开发中的局限

MVP模式是由MVC发展演变而来的,MVC模式的UML图存在如下两个版本。其中版本一参见于MVP的百度百科和标准的MVC网络教程;版本二可以见于部分Android MVC和MVP的介绍网站。


                                                                     MVC 版本一


                                                              MVC 版本二

标准MVC在软件开发中的流程设定是,view层负责界面显示逻辑,Control层负责业务流程逻辑,Model层负责数据的存取逻辑。view层持有Control层的引用,Control层持有Model层的引用,Control层持有View层的引用,当View层有用户操作需要获取数据时,会通过Control层控制Model层进行数据的获取,最后在通过Model层回显到View层。MVC实现了在界面、业务逻辑和数据的分层设计,在改进这三层设计中的某一层设计时,不需要重新编写整个代码。

在Android的设计中,通常是用一个Activity组件设计一个页面的交互逻辑。在Activity的页面设计中,并不容易将View和Controller进行分层处理,这就导致View层会持有Model层的引用,就形成了MVC版本二的UML图。参见图下示例代码,


public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener {

private WeatherModel weatherModel;

private EditText cityNOInput;

private TextView city;

...

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

weatherModel = new WeatherModelImpl();

initView();

}

//初始化View

private void initView() {

cityNOInput = findView(R.id.et_city_no);

city = findView(R.id.tv_city);

...

findView(R.id.btn_go).setOnClickListener(this);

}

//显示结果

public void displayResult(Weather weather) {

WeatherInfo weatherInfo = weather.getWeatherinfo();

city.setText(weatherInfo.getCity());

...

}

@Override

public void onClick(View v) {

switch (v.getId()) {

case R.id.btn_go:

weatherModel.getWeather(cityNOInput.getText().toString().trim(), this);

break;

}

}

@Override

public void onSuccess(Weather weather) {

displayResult(weather);

}

@Override

public void onError() {

Toast.makeText(this, 获取天气信息失败, Toast.LENGTH_SHORT).show();

}

private T findView(int id) {

return (T) findViewById(id);

}

}



在代码中onClick(View v)内的代码就负责了业务层的部分逻辑,并且很明显可以看到Model层的引用weatherModel。

上述代码中只要有一个按钮点击的事件,当Activity页面存在多个事件操作时,Activity的代码必定会显得非常臃肿,这导致的结果就是破坏了MVC原本的分层逻辑设计,任何一处代码的修改都会带来较大的代码改动。

2.2 Android中MVP设计模式的引入

鉴于MVC在Android设计上出现的缺陷,因此在设计框架上引入了MVP,MVP的UML图下


从UML图可以看出,在MVP设计模式中,Presenter层完全隔离了View层和Model层,主要的程序逻辑在Presenter里进行实现。

以Google提供的MVP代码([https://github.com/googlesamples/android-architecture/tree/todo-mvp/](https://github.com/googlesamples/android-architecture/tree/todo-mvp/))来进行分析。

View层的代码如下



public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {

@NonNull

private static final String ARGUMENT_TASK_ID = "TASK_ID";

@NonNull

private static final int REQUEST_EDIT_TASK = 1;

private TaskDetailContract.Presenter mPresenter;

private TextView mDetailTitle;

private TextView mDetailDescription;

private CheckBox mDetailCompleteStatus;

public static TaskDetailFragment newInstance(@Nullable String taskId) {

Bundle arguments = new Bundle();

arguments.putString(ARGUMENT_TASK_ID, taskId);

TaskDetailFragment fragment = new TaskDetailFragment();

fragment.setArguments(arguments);

return fragment;

}

@Override

public void onResume() {

super.onResume();

mPresenter.start();

}

@Nullable

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState) {

View root = inflater.inflate(R.layout.taskdetail_frag, container, false);

setHasOptionsMenu(true);

mDetailTitle = (TextView) root.findViewById(R.id.task_detail_title);

mDetailDescription = (TextView) root.findViewById(R.id.task_detail_description);

mDetailCompleteStatus = (CheckBox) root.findViewById(R.id.task_detail_complete);

// Set up floating action button

FloatingActionButton fab =

(FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task);

fab.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

mPresenter.editTask();

}

});

return root;

}

@Override

public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {

mPresenter = checkNotNull(presenter);

}

@Override

public boolean onOptionsItemSelected(MenuItem item) {

switch (item.getItemId()) {

case R.id.menu_delete:

mPresenter.deleteTask();

return true;

}

return false;

}

...

@Override

public void showCompletionStatus(final boolean complete) {

Preconditions.checkNotNull(mDetailCompleteStatus);

mDetailCompleteStatus.setChecked(complete);

mDetailCompleteStatus.setOnCheckedChangeListener(

new CompoundButton.OnCheckedChangeListener() {

@Override

public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

if (isChecked) {

mPresenter.completeTask();

} else {

mPresenter.activateTask();

}

}

});

}

...

}



Presenter层的代码设计



package com.example.android.architecture.blueprints.todoapp.taskdetail;

import android.support.annotation.NonNull;

import android.support.annotation.Nullable;

import com.example.android.architecture.blueprints.todoapp.data.Task;

import com.example.android.architecture.blueprints.todoapp.data.source.TasksDataSource;

import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository;

import com.google.common.base.Strings;

import static com.google.common.base.Preconditions.checkNotNull;

/**

* Listens to user actions from the UI ({@link TaskDetailFragment}), retrieves the data and updates

* the UI as required.

*/

public class TaskDetailPresenter implements TaskDetailContract.Presenter {

private final TasksRepository mTasksRepository;

private final TaskDetailContract.View mTaskDetailView;

@Nullable

private String mTaskId;

public TaskDetailPresenter(@Nullable String taskId,

@NonNull TasksRepository tasksRepository,

@NonNull TaskDetailContract.View taskDetailView) {

mTaskId = taskId;

mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");

mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");

mTaskDetailView.setPresenter(this);

}

@Override

public void start() {

openTask();

}

private void openTask() {

if (Strings.isNullOrEmpty(mTaskId)) {

mTaskDetailView.showMissingTask();

return;

}

mTaskDetailView.setLoadingIndicator(true);

mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {

@Override

public void onTaskLoaded(Task task) {

// The view may not be able to handle UI updates anymore

if (!mTaskDetailView.isActive()) {

return;

}

mTaskDetailView.setLoadingIndicator(false);

if (null == task) {

mTaskDetailView.showMissingTask();

} else {

showTask(task);

}

}

@Override

public void onDataNotAvailable() {

// The view may not be able to handle UI updates anymore

if (!mTaskDetailView.isActive()) {

return;

}

mTaskDetailView.showMissingTask();

}

});

}

@Override

public void editTask() {

if (Strings.isNullOrEmpty(mTaskId)) {

mTaskDetailView.showMissingTask();

return;

}

mTaskDetailView.showEditTask(mTaskId);

}

@Override

public void deleteTask() {

if (Strings.isNullOrEmpty(mTaskId)) {

mTaskDetailView.showMissingTask();

return;

}

mTasksRepository.deleteTask(mTaskId);

mTaskDetailView.showTaskDeleted();

}

@Override

public void completeTask() {

if (Strings.isNullOrEmpty(mTaskId)) {

mTaskDetailView.showMissingTask();

return;

}

mTasksRepository.completeTask(mTaskId);

mTaskDetailView.showTaskMarkedComplete();

}

@Override

public void activateTask() {

if (Strings.isNullOrEmpty(mTaskId)) {

mTaskDetailView.showMissingTask();

return;

}

mTasksRepository.activateTask(mTaskId);

mTaskDetailView.showTaskMarkedActive();

}

private void showTask(@NonNull Task task) {

String title = task.getTitle();

String description = task.getDescription();

if (Strings.isNullOrEmpty(title)) {

mTaskDetailView.hideTitle();

} else {

mTaskDetailView.showTitle(title);

}

if (Strings.isNullOrEmpty(description)) {

mTaskDetailView.hideDescription();

} else {

mTaskDetailView.showDescription(description);

}

mTaskDetailView.showCompletionStatus(task.isCompleted());

}

}



从代码的设计上可以看出View层逻辑上相关的代码都交由Presenter的引用在进行处理,在Presenter层中持有View层和Model层的引用,Presenter层可以回调更新UI界面。在Google提供代码的Data目录下,并没有看到持有Presenter层的引用,这一点并不违反MVP的设计框架,MVP的设计框架的思想是为了对view、model和presenter进行分层,能够在代码设计中实现这一点已经足够了。另外在代码设计中为了秉承接口隔离的设计原则,在MVP的代码中采用很多接口进行耦合。

三、MVP设计的总结

MVP是在MVC上的改进,这并非代表着在Android设计中MVP设计模式一定比MVC好,比如在某些项目中,数据从Model层经过Presenter层拷贝到View层的开销比较大,这种情况下可能需要使用MVC的更为合适,通过定义一个接口也可以起到一定的隔离和分层左右。在代码设计中需要做的是尽可能的秉承设计的六项基本原则,根据项目的大小合理的选择框架就够了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,386评论 25 707
  • OEC H2O-> O2 NQE proton or H Mn valence alters rate exp ~...
    Liming_Liu阅读 178评论 0 0
  • 夜色像一张烤糊的煎饼 在窗户摊开 秋风驮着越来越重的寒凉 骤雨撞击着玻璃 犹如狂奔的马蹄掠过我孤独的内心 想你了,...
    致远_8493阅读 223评论 0 2
  • 偶然因工作需要,在爬取数据时 selenium+phantomJS 并不能做到我们想要的效果(猜想可能phanto...
    Mercury今阅读 315评论 1 4
  • 你们不相信的 我相信 你们不理解的 我理解 校园本来应该是干净的 被一些人的谩骂声诅咒 现在应该清扫清扫了 落叶归...
    蔷薇飒阅读 207评论 0 0