前言
前段时间,公司由个同事分享的时候,提到了MVP模式,自己之前也了解过,但是真正在自己的编码过程中使用的非常少。最近在帮助一个朋友做毕业设计,心想这是一个很好的机会练习一把。网上也找了很多有关MVP的博客,说的也都差不多,就想找一个比较权威的,当然应该是google官网啦,就找到了Google在Github上开源项目,真找到了MVP例子,就记一篇博文,慢慢回味。
Android MVP
MVP(MVP模式):一种软件设计模式
全称为Model-View-Presenter,Model提供数据,View负责显示,Controller/Presenter负责逻辑的处理。MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。
goto 维基百科
goto 百度百科
MVP模式,一方面是体现单一职责原则,降低Android中类之间逻辑的耦合,使之高内聚低耦合;二是让代码更清晰,更简洁,但是简洁并不代表代码少,更易扩展等。
#Android MVP(Google Samples/aandroid-architecture)
前言中提到过,Android官方提供了MVP Sample,这也是Google发现问题,给开发者提供的一种思路吧。
goto Google Sample
点击上述链接,可以看到Google官方为大家推荐的应用开发框架,Android 架构蓝图,当然有心的你还会从Google Github发现更多有用的开源项目。
概述
简单翻译下(如有不妥,欢迎评论提意见哈)
在组织和构建Android app时,虽然Android框架提供了很大的灵活性,这种灵活性,非常有价值,但是也可能导致app出现非常多的类,命名不一致,缺乏整体测试架构,难以维护和扩展等问题,
所有提供开发者一些设计架构模式,解决这些问题。安卓架构蓝图项目是为了证明可能的方式来帮助解决这些常见的问题。在这个项目中,提供使用不同的架构和工具实现相同的应用程序。
官方只是将这些作为参考,具体是否合适大家放入项目中,就要具体情况具体分析了,重点是代码结构,体系结构,方便测试和可维护,可拓展性。
Samples
在android-architecture项目下,提供了7个例子[稳定版],分别对应项目不同的分支下,大家可以下载下来,跑一跑,看一看。
1. todo-mvp/ - Basic Model-View-Presenter architecture.
2. todo-mvp-loaders/ - Based on todo-mvp, fetches data using Loaders.
3. todo-databinding/ - Based on todo-mvp, uses the Data Binding Library.
4. todo-mvp-clean/ - Based on todo-mvp, uses concepts from Clean Architecture.
5. todo-mvp-dagger/ - Based on todo-mvp, uses Dagger2 for Dependency Injection.
6.todo-mvp-contentproviders/ - Based on todo-mvp-loaders, fetches data using Loaders and uses Content Providers.
7.todo-mvp-rxjava/ - Based on todo-mvp, uses RxJava for concurrency and data layer abstraction.
8.todo-mvp-tablet/
很清晰的可以看出,Sample1就是单纯的介绍MVP设计模式的,2-7是在1的基础上,添加了一些快速开发的一些框架,本文主要记录MVP,其他将后续跟进; 8官方正在开发中,应该还不算稳定版本。
TODO-MVP
todo-mvp
如何下载,以及运行参考官方文档即可,这个也是学习的一个过程。
它展示了一个简单的MVP模式,没有使用任何框架和架构。它使用手动依赖注入,以提供一个本地和远程数据源的存储库。异步任务处理回调。
首先看下整体框架,左半边是Model,右边是View和Presenter,官方善意的注释了下,图片中的VIEW并不是android框架中的View,是MVP场景中的一个抽象,在代码中就能体会到。途中蓝色字体相当于抽象概念。
之所以使用Fragment:
- Activity与fragment的分离,非常符合MVP的实现:Activity是创建并连接Views和Presenter的整体控制器
- Table layout和多视图界面使用Fragment框架将非常有优势
依赖
- Common Android support libraries (com.android.support.*)
- Android Testing Support Library (Espresso, AndroidJUnitRunner…)
- Mockito
- Guava (null checking)
源码分析
首先看下整体的源码结构:
从上图可以很清晰的看出整个app的功能模块,包括(Tasks, AddEditTask, TaskDetail, Statistics)
每个功能包含四大文件:Activity, Fragment, Contract, Presenter
就像上述提及的Activity相当于一个控制器;Fragment相当于MVP中的View;Contract是一个功能中View和Presenter间的协议,通俗的理解就是,这两个是协同工作的;而Presenter就是具体的业务逻辑实现代码。首先会根据当前的功能模块进行分解成多个业务逻辑,然后制定View和Presenter的协议。
运行效果如下:
以Task功能来分析下具体的MVP模式的实现:
TasksContract:主要是定义相关的业务逻辑接口,还有UI的更新接口,可以很清晰的查看该功能界面的具体业务功能,不过当页面比较复杂时,这个文件会不会很大???或者说我们就不该把一个页面搞太复杂。网上看过不少例子,都没有提到这个文件,个人感觉这个Contract还是很重要的!!!
/**
* 指定View和Presenter之间的协议
*/
public interface TasksContract {
interface View extends BaseView<Presenter> {
void setLoadingIndicator(boolean active);
void showTasks(List<Task> tasks);
void showAddTask();
void showTaskDetailsUi(String taskId);
void showTaskMarkedComplete();
void showTaskMarkedActive();
void showCompletedTasksCleared();
void showLoadingTasksError();
void showNoTasks();
void showActiveFilterLabel();
void showCompletedFilterLabel();
void showAllFilterLabel();
void showNoActiveTasks();
void showNoCompletedTasks();
void showSuccessfullySavedMessage();
boolean isActive();
void showFilteringPopUpMenu();
}
interface Presenter extends BasePresenter {
void result(int requestCode, int resultCode);
void loadTasks(boolean forceUpdate);
void addNewTask();
void openTaskDetails(@NonNull Task requestedTask);
void completeTask(@NonNull Task completedTask);
void activateTask(@NonNull Task activeTask);
void clearCompletedTasks();
void setFiltering(TasksFilterType requestType);
TasksFilterType getFiltering();
}
}
TasksActivity:主要是创建View(TasksFragment), 创建Presenter(TasksFragment), 并将View在Presenter构造过程中传递过去。
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
}
// Create the presenter
mTasksPresenter = new TasksPresenter(
Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
TasksFragment:进行Presenter的初始化, 根据用户交互,调用View接口定义的相关协议接口,执行Presenter中定义的逻辑接口
/**
* Display a grid of {@link Task}s. User can choose to view all, active or completed tasks.
*/
public class TasksFragment extends Fragment implements TasksContract.View {
private TasksContract.Presenter mPresenter;
...
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
@Override
public void setPresenter(@NonNull TasksContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mPresenter.result(requestCode, resultCode);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mNoTaskAddView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showAddTask();
}
});
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.addNewTask();
}
});
// Set up progress indicator
final ScrollChildSwipeRefreshLayout swipeRefreshLayout =
(ScrollChildSwipeRefreshLayout) root.findViewById(R.id.refresh_layout);
swipeRefreshLayout.setColorSchemeColors(
ContextCompat.getColor(getActivity(), R.color.colorPrimary),
ContextCompat.getColor(getActivity(), R.color.colorAccent),
ContextCompat.getColor(getActivity(), R.color.colorPrimaryDark)
);
// Set the scrolling view in the custom SwipeRefreshLayout.
swipeRefreshLayout.setScrollUpChild(listView);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
mPresenter.loadTasks(false);
}
});
setHasOptionsMenu(true);
return root;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_clear:
mPresenter.clearCompletedTasks();
break;
case R.id.menu_filter:
showFilteringPopUpMenu();
break;
case R.id.menu_refresh:
mPresenter.loadTasks(true);
break;
}
return true;
}
@Override
public void showFilteringPopUpMenu() {
PopupMenu popup = new PopupMenu(getContext(), getActivity().findViewById(R.id.menu_filter));
popup.getMenuInflater().inflate(R.menu.filter_tasks, popup.getMenu());
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.active:
mPresenter.setFiltering(TasksFilterType.ACTIVE_TASKS);
break;
case R.id.completed:
mPresenter.setFiltering(TasksFilterType.COMPLETED_TASKS);
break;
default:
mPresenter.setFiltering(TasksFilterType.ALL_TASKS);
break;
}
mPresenter.loadTasks(false);
return true;
}
});
popup.show();
}
/**
* Listener for clicks on tasks in the ListView.
*/
TaskItemListener mItemListener = new TaskItemListener() {
@Override
public void onTaskClick(Task clickedTask) {
mPresenter.openTaskDetails(clickedTask);
}
@Override
public void onCompleteTaskClick(Task completedTask) {
mPresenter.completeTask(completedTask);
}
@Override
public void onActivateTaskClick(Task activatedTask) {
mPresenter.activateTask(activatedTask);
}
};
...
@Override
public void showTasks(List<Task> tasks) {
mListAdapter.replaceData(tasks);
mTasksView.setVisibility(View.VISIBLE);
mNoTasksView.setVisibility(View.GONE);
}
@Override
public void showNoActiveTasks() {
showNoTasksViews(
getResources().getString(R.string.no_tasks_active),
R.drawable.ic_check_circle_24dp,
false
);
}
@Override
public void showNoTasks() {
showNoTasksViews(
getResources().getString(R.string.no_tasks_all),
R.drawable.ic_assignment_turned_in_24dp,
false
);
}
@Override
public void showNoCompletedTasks() {
showNoTasksViews(
getResources().getString(R.string.no_tasks_completed),
R.drawable.ic_verified_user_24dp,
false
);
}
@Override
public void showSuccessfullySavedMessage() {
showMessage(getString(R.string.successfully_saved_task_message));
}
private void showNoTasksViews(String mainText, int iconRes, boolean showAddView) {
mTasksView.setVisibility(View.GONE);
mNoTasksView.setVisibility(View.VISIBLE);
mNoTaskMainView.setText(mainText);
mNoTaskIcon.setImageDrawable(getResources().getDrawable(iconRes));
mNoTaskAddView.setVisibility(showAddView ? View.VISIBLE : View.GONE);
}
@Override
public void showActiveFilterLabel() {
mFilteringLabelView.setText(getResources().getString(R.string.label_active));
}
@Override
public void showCompletedFilterLabel() {
mFilteringLabelView.setText(getResources().getString(R.string.label_completed));
}
@Override
public void showAllFilterLabel() {
mFilteringLabelView.setText(getResources().getString(R.string.label_all));
}
@Override
public void showAddTask() {
Intent intent = new Intent(getContext(), AddEditTaskActivity.class);
startActivityForResult(intent, AddEditTaskActivity.REQUEST_ADD_TASK);
}
@Override
public void showTaskDetailsUi(String taskId) {
// in it's own Activity, since it makes more sense that way and it gives us the flexibility
// to show some Intent stubbing.
Intent intent = new Intent(getContext(), TaskDetailActivity.class);
intent.putExtra(TaskDetailActivity.EXTRA_TASK_ID, taskId);
startActivity(intent);
}
@Override
public void showTaskMarkedComplete() {
showMessage(getString(R.string.task_marked_complete));
}
@Override
public void showTaskMarkedActive() {
showMessage(getString(R.string.task_marked_active));
}
@Override
public void showCompletedTasksCleared() {
showMessage(getString(R.string.completed_tasks_cleared));
}
@Override
public void showLoadingTasksError() {
showMessage(getString(R.string.loading_tasks_error));
}
private void showMessage(String message) {
Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).show();
}
@Override
public boolean isActive() {
return isAdded();
}
...
}
TasksPresenter:监听用户的UI操作,实现Presenter协议中定义的业务逻辑,和Model层进行交换,然后更新UI。
/**
* Listens to user actions from the UI ({@link TasksFragment}), retrieves the data and updates the
* UI as required.
*/
public class TasksPresenter implements TasksContract.Presenter {
...
public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");
...
其中TasksContract.View实现了BaseView,实现setPresenter接口
TasksContract.Presenter实现了BasePresenter,实现了start接口
MyMVP
//TODO