传统的MVC架构是个非常经典的设计,它将系统的任务进行分层,将代码分割到模型(model)-视图(view)-控制器(controller)三个层面来实现解耦,从而简化开发流程,实现开发任务的分离。
而在android平台中,xml布局文件作为视图的承载能力并不强,通常会将一部分的view操作放在Activity/Fragment中来处理,而Activity/Fragment通常又担任了controller的角色,这就造成了V和C在Android中通常融合在一起,以至MVC的设计架构在Android中并不能进行很好的分层,形成了一种貌合神离的现象。
在这样的背景下,MVP设计架构应运而生。在MVP中单独抽离了一个P(Presenter)层来负责数据流向控制的角色,而xml布局文件和Activity则统一作为V(View)来单一的负责视图的生成和变更。
对于Android中MVP设计架构Google也提供了一些sample,本篇摘取一个待办事项的示例来初步解析Google官方对MVP的实现方式。
github:
https://github.com/googlesamples/android-architecture/tree/todo-mvp/
分支:
todo-mvp
todo-mvp-rxjava
todo-mvp-dagger
todo-mvp-loaders
todo-mvp-clean
todo-mvp-tablet
这个库有很多个分支,这里只采用todo—mvp来进行解析
这个项目分支的官方介绍是这样的
概述
这个示例是很多其他变体版本的基础,它展示了一个简单的没有其使用其他框架的MVP模式的实现。它使用手动依赖注入,以提供一个本地和远程数据源的存储。异步任务通过callback进行回调处理
在MVP中,”view”的含义是被重新定义的:
android.view.View类被称为Android View
在MVP的presenter中接受收指令的view则被简单的称作view
Fragments
使用Fragment有两个原因:
Activity和Fragment的分离对MVP的实现是非常合适的:Activity可以作为views和presenters的创建和关联的控制器
多个view的碎片化的视图充分利用了Fragment的优势
概念
在应用程序中有四个功能:
Tasks
TaskDetail
AddEditTask
Statistics
每个功能都有:
- 一个Contract作为view和presenter的约定
- 一个Activity负责Fragment和presenter的创建
- 一个Fragment实现view接口
- 一个presenter实现presenter接口
一般情况下业务逻辑存在于presenter中,并通过view操作UI视图
view几乎不含有任何逻辑:它将presenter的指令转换为UI操作,并监听用户操作传递给presenter
Contract是用来定义view和presenter之间通讯的一些接口
解析
这个sample有四个功能模块,分别是事项列表/事项详情/新增修改事项/数据统计。项目结构如下:
根据项目结构可以看到每个功能模块分别定义了一个包,而包下则会为这个功能把Activity/Contract/Fragment/Presenter各定义一份。
这里针对事项详情(taskdetail)来进行剖析。
Contract
首先我们查看TaskDetailContract文件,可以看到这是一个接口,而里面定义了另外两个接口分别是View和Presenter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| * This specifies the contract between the view and the presenter. */ public interface TaskDetailContract { interface View extends BaseView<Presenter> { void setLoadingIndicator(boolean active); void showMissingTask(); void hideTitle(); void showTitle(String title); void hideDescription(); void showDescription(String description); void showCompletionStatus(boolean complete); void showEditTask(String taskId); void showTaskDeleted(); void showTaskMarkedComplete(); void showTaskMarkedActive(); boolean isActive(); } interface Presenter extends BasePresenter { void editTask(); void deleteTask(); void completeTask(); void activateTask(); } }
|
View和Presenter两个接口分别提供了代办事件的View相关操作和数据相关任务处理的接口,而这两个接口分别继承了另外两个父接口,为所有的View和Presenter提供统一的行为约束。
1 2 3 4 5 6
| public interface BaseView<T> { void setPresenter(T presenter); } public interface BasePresenter { void start(); }
|
Fragment
在google为我们提供的这个示例项目中,作为view的并不是Activity而是Fragment。也就是说在这种模式下所有的Activity中都要存在一个Fragment来承载这个界面的UI元素。而Activity则只负责Fragment和Presenter的初始化和关联操作。
既然Fragment是作为view存在的,那么定义的Fragment理应实现上面定义的view接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| * Main UI for the task detail screen. */ public class TaskDetailFragment extends Fragment implements TaskDetailContract.View { public static final String ARGUMENT_TASK_ID = "TASK_ID"; public static final int REQUEST_EDIT_TASK = 1; ... public static TaskDetailFragment newInstance(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); 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 boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_delete: mPresenter.deleteTask(); ... }
|
view的回调方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Override public void showEditTask(String taskId) { Intent intent = new Intent(getContext(), AddEditTaskActivity.class); intent.putExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId); startActivityForResult(intent, REQUEST_EDIT_TASK); } @Override public void showTaskDeleted() { getActivity().finish(); } @Override public void showTitle(String title) { mDetailTitle.setVisibility(View.VISIBLE); mDetailTitle.setText(title); } @Override public void showDescription(String description) { mDetailDescription.setVisibility(View.VISIBLE); mDetailDescription.setText(description); } ... @Override public boolean isActive() { return isAdded(); } }
|
所有的view都会实现setPresenter方法来支持presenter的注入
1 2 3 4
| @Override public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) { mPresenter = checkNotNull(presenter); }
|
在Fragment中除了视图的控制/事件的监听/presenter对事件的发送外没有进行任何的业务逻辑处理,而是将任务交给presenter,处理结果则由presenter发起对view的ui控制接口的回调进行通知。
Presenter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| * 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) { this.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 (null == mTaskId || mTaskId.isEmpty()) { mTaskDetailView.showMissingTask(); return; } mTaskDetailView.setLoadingIndicator(true); mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() { @Override public void onTaskLoaded(Task task) { if (!mTaskDetailView.isActive()) { return; } mTaskDetailView.setLoadingIndicator(false); if (null == task) { mTaskDetailView.showMissingTask(); } else { showTask(task); } } @Override public void onDataNotAvailable() { if (!mTaskDetailView.isActive()) { return; } mTaskDetailView.showMissingTask(); } }); } @Override public void editTask() { if (null == mTaskId || mTaskId.isEmpty()) { mTaskDetailView.showMissingTask(); return; } mTaskDetailView.showEditTask(mTaskId); } @Override public void deleteTask() { mTasksRepository.deleteTask(mTaskId); mTaskDetailView.showTaskDeleted(); } ... private void showTask(Task task) { String title = task.getTitle(); String description = task.getDescription(); if (title != null && title.isEmpty()) { mTaskDetailView.hideTitle(); } else { mTaskDetailView.showTitle(title); } if (description != null && description.isEmpty()) { mTaskDetailView.hideDescription(); } else { mTaskDetailView.showDescription(description); } mTaskDetailView.showCompletionStatus(task.isCompleted()); } }
|
Activity
Activity在这里不担任MVP中的任何一个角色,只作为View和Presenter的创建和关联的控制器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| * Displays task details screen. */ public class TaskDetailActivity extends AppCompatActivity { public static final String EXTRA_TASK_ID = "TASK_ID"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.taskdetail_act); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); String taskId = getIntent().getStringExtra(EXTRA_TASK_ID); TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager() .findFragmentById(R.id.contentFrame); if (taskDetailFragment == null) { taskDetailFragment = TaskDetailFragment.newInstance(taskId); ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), taskDetailFragment, R.id.contentFrame); } new TaskDetailPresenter( taskId, Injection.provideTasksRepository(getApplicationContext()), taskDetailFragment); } }
|
这里我们看到Activity只是将上个界面传入的数据放入Presenter中,进而由presenter在onstar方法被调用的时候对view(fragment)中的数据进行初始化。
到此为止google为我们提供的MVP的实现方式也就串了一遍,这和现在网上一些文章中的实现可能并不是很一致,大部分开发人员会选择使用Activity作为View的载体,而不是在每个Activity中定义一个Fragment再来展现View。
google的做法可能更符合MVP的设计,Activity只作为路由来使用,而V和P则是游离的存在。
将Activity作为View来使用的话代码则会显得更加简洁,定义的类会少一些,对于view的生命周期控制也会更加简便。
这两种作法孰优孰劣博主也暂时无法定夺,至于想在项目中使用mvp设计架构则建议视情况而定。