最近在筹备新项目的开发,打算使用google官方推荐的MVP配合Retrofit+RxAndroid打造一套新项目的框架。
先从MVP开始学习,然而网上关于MVP的博客以及学习资料实在是太多,所以打算删繁就简,先研究一番google官方的MVP实例。
谷歌的MVP框架源码已经发布五个多月了,如今已经成为了时下最火热的Android框架,其视图与模型完全分离的特性也受到了越来越多开发者的喜爱。
谷歌的MVP框架sample下载地址为https://github.com/googlesamples/android-architecture/tree/todo-mvp/
本文是基于该实例进行对MVP进行源码与概念的研究的。
MVP与MVP概念区分:
传统MVC概念分为:模型,视图,控制三个模块,如下图所示:
其中,View是可以直接访问Model的,所以也就造成了View还要承担一定的业务逻辑,不能作为单纯的视图层来使用,而且View和Controller也很难分开,很多时候我们经常会在View的响应时间里写很多Controller代码,这样直接导致代码的复用性大大降低,到处都是UI实现相同但是逻辑略微不同的代码,导致项目变得越来越臃肿不堪。
MVP的模式可由下图所示:
从图中可以清楚的看到,View仅仅只是“View”,不需要处理任何业务层的代码,一切View和Model的操作都在Presenter中执行。
View是指显示数据并且和用户交互的层。在安卓中,它们可以是一个Activity,一个Fragment,一个android.view.View或者是一个Dialog。
Model 是数据源层。比如数据库接口或者远程服务器的api。
Presenter是从Model中获取数据并提供给View的层,Presenter还负责处理后台任务。
这里需要说明的是Presenter与View是没有直接关联的,而是通过定义好的接口进行交互,从而使得在变更View的时候可以保持presenter的不边,即重用View!这是MVC无法做到的。
这样也使得我们在设计调试程序的时候变得更为简单,不必要先写出详细的View 布局才能进行逻辑功能测试,因为View在MVP中只是薄薄的一层显示功能,我们可以首先设计和开发Presenter,在这个时候View只需要显示一些基本的信息就可以了,在后面再根据需求更改View,这样对Presenter层也不会有任何影响。
绝对不能与Model发生关系,是View作为显示层的关键原则。但是有的时候可能业务逻辑比较复杂,需要用到Model层的相关数据,这个时候可以在View和Presenter之间放置一个adapter,由这个adapter来访问Model和View,避免两者之间发生直接关联,这个adapter也必须实现View的接口,保证了与Presenter之间接口的不变。这样便可以实现View与Model层之间的完全解耦。
最后也是最重要的一点,在MVP模式里,View应该只有简单的Get和Set方法,不需要有任何复杂的业务逻辑,只需要toshow就可以了。
MVP官方实例源码分析:
Github地址再发一遍:TODO MVP
导入到本地之后,我们先分析它的包结构:
/**
* This specifies the contract between the view and the presenter.
*/
public interface AddEditTaskContract {
interface View extends BaseView {
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();
}
}
/**
* Displays an add or edit task screen.
*/
public class AddEditTaskActivity extends AppCompatActivity {
public static final int REQUEST_ADD_TASK = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addtask_act);
// Set up the toolbar.
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
AddEditTaskFragment addEditTaskFragment =
(AddEditTaskFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
String taskId = null;
if (addEditTaskFragment == null) {
addEditTaskFragment = AddEditTaskFragment.newInstance();
if (getIntent().hasExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID)) {
taskId = getIntent().getStringExtra(
AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID);
actionBar.setTitle(R.string.edit_task);
Bundle bundle = new Bundle();
bundle.putString(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId);
addEditTaskFragment.setArguments(bundle);
} else {
actionBar.setTitle(R.string.add_task);
}
ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
addEditTaskFragment, R.id.contentFrame);
}
// Create the presenter
new AddEditTaskPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
addEditTaskFragment);
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
@VisibleForTesting
public IdlingResource getCountingIdlingResource() {
return EspressoIdlingResource.getIdlingResource();
}
}
根据传来的参数判断是添加还是编辑,并初始化presenter(39-42)与view(16-18)。
/**
* Main UI for the add task screen. Users can enter a task title and 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();
mPresenter.start();
}
@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());
}
});
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.addtask_frag, container, false);
mTitle = (TextView) root.findViewById(R.id.add_task_title);
mDescription = (TextView) root.findViewById(R.id.add_task_description);
setHasOptionsMenu(true);
setRetainInstance(true);
return root;
}
@Override
public void showEmptyTaskError() {
Snackbar.make(mTitle, getString(R.string.empty_task_message), Snackbar.LENGTH_LONG).show();
}
@Override
public void showTasksList() {
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish();
}
@Override
public void setTitle(String title) {
mTitle.setText(title);
}
@Override
public void setDescription(String description) {
mDescription.setText(description);
}
@Override
public boolean isActive() {
return isAdded();
}
}
其中包含一个presenter的回调接口实例,用于响应回调,并更新回调presenter进行操作时带来的视图改变,代码很简单。
/**
* 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 {
@NonNull
private final TasksDataSource mTasksRepository;
@NonNull
private final AddEditTaskContract.View mAddTaskView;
@Nullable
private String mTaskId;
/**
* 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);
}
}
@Override
public void populateTask() {
if (isNewTask()) {
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());
}
}
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
if (mAddTaskView.isActive()) {
mAddTaskView.showEmptyTaskError();
}
}
private boolean isNewTask() {
return mTaskId == null;
}
private void createTask(String title, String description) {
Task newTask = new Task(title, description);
if (newTask.isEmpty()) {
mAddTaskView.showEmptyTaskError();
} else {
mTasksRepository.saveTask(newTask);
mAddTaskView.showTasksList();
}
}
private void updateTask(String title, String description) {
if (isNewTask()) {
throw new RuntimeException("updateTask() was called but task is new.");
}
mTasksRepository.saveTask(new Task(title, description, mTaskId));
mAddTaskView.showTasksList(); // After an edit, go back to the list.
}
}
可以看到presenter体现了其控制器的核心作用,所有的数据交互与视图改变都在这里得到体现,在以前的MVC模式中这些代码一般都嵌套在Activity的各个角落,然而使用MVP模式使其完全抽离了出来,这才是MVP模式最宝贵的思想精华。