MVP源码实战解析——告别MVC,初识MVP

最近在筹备新项目的开发,打算使用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

导入到本地之后,我们先分析它的包结构:


其中功能分别为:
addedittask: 添加编辑任务
data:            数据操作包
statistics:     统计数据包
task detail:   任务详情包
tasks:         主要部分,包含定义视图和presenter的接口规范以及部分实体类
我们可以看到google的模式是按项目功能进行分包,国内大部分项目可能是按类别进行分包,比如Aty一个包,bean一个包,不过我现在感觉还是按功能分包比较好,原因就是好找。。。可以做个简单的计算,比如每次从一个包里寻找一个类的时间为t,一个功能线需要的类为5,如果按类别分包,我们需要从不同的包比如aty,bean,adapter等里面找到需要的类,总共花费时间至少5t,然而如果按功能分包,我们只需要一开始浏览一下需要的功能在哪个包里,然后东西就全在里面了。。。花费时间为t!
我们再来看看每个包的具体类:

可以看到每个包大致都包含四个部分:
Contract:定义View与Presenter的接口。
Activity:  负责fragment的创建和presenter的初始化。
Fragment:继承了Contract.view接口,MVP模式中View的存在。
Presenter:继承了Contract.presenter接口,MVP模式中presenter的存在。
至于Model层是在MVP中操作的数据的部分,这里主要集中在data包中。

下面选取一个比较有代表性的包进行具体的MVP逻辑分析:
addedittaskactivity包:
首先我们先来分析接口类AddEditTaskContract:
/**
 * 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();
    }
}

可以看到其中定义了View和Presenter层的接口,其中View接口只有基本的显示与设置方法,改变UI的显示文字,还有一个isActive方法判断Fragment是否已经被添加到主Activity中。Presenter接口有两个方法,一个saveTask负责储存数据,一个populateTask从本地数据源获取任务。

Activity:AddEditTaskActivity
/**
 * 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)。
Fragment:AddEditTaskFragment
/**
 * 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进行操作时带来的视图改变,代码很简单。
presenter:AddEditTaskPresenter
/**
 * 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模式最宝贵的思想精华。



********************************************************************************
最近发生了很多事情,有开心的事有不开心的事。
但是无论如何那有怎么样呢,生活还是要继续,人还是要向前看。
现在还远远不够的,我还是要努力,变得更强~





发布了70 篇原创文章 · 获赞 75 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/qq_22770457/article/details/52400050
MVP
今日推荐