【设计框架】Android 中的 MVP 模式

: Jun 13, 2016

: Framework

: onlylemi/notes/tree/master/Framework


MVC模式可以看这里:MVC介绍

结构

  • Presenter —— 交互中间人。主要沟通View和Model的桥梁,它从Model层检索数据之后,返回给View层,使得View和Model之间没有耦合,也讲业务逻辑从View角色上抽离出来。
  • View —— 用户界面。View通常指Activity、Fragment或者某个View空间,它包含一个Presenter成员变量。通常View需要实现一个逻辑接口,将View上的操作交给Presenter进行实现,最后,Presenter调用View的逻辑接口将结果返回给View对象。
  • Model —— 数据的存取。主要提供数据的存取功能,Presenter需要通过Model层存储、获取数据,model就像一个数据仓库。更直白的说,Model封装了数据库DAO或者网络获取数据的角色,或者两种数据获取方式的集合。

示例

Google官方提供的MVP示例为例分析:https://github.com/googlesamples/android-architecture 中的 todo-mvp

项目结构

todo-mvp
	addedittask
		AddEditTaskActivity.java
		...Contract.java
		...Fragment.java---------------------- View
		...Presenterjava---------------------- Presenter
	data-------------------------------------- Model 
		source
			local...
			remote...
		TasksDataSource.java
		TasksRepository.java
	statistics
		StatisticsActivity.java
		...Contract.java
		...Fragment.java
		...Presenterjava
	taskdetail...
	tasks...
	util...
	BasePresenter.java
	BaseView.java

没有列出所有类,只是部分做参考,可以看出项目结构是按功能模块进行划分(也可以使用activity、adapter、fragment、contract、presenter进行划分)。不过其中多了 Contract 一个类来管理View与Presenter,使得View与Presenter中的功能一目了然,同时维护起来也比较方便。

下面以 addedittask 功能分析,其他功能类似

基类

BaseView 是View的基类,包含 setPresenter() 方法将Presenter实例传到View层,调用时机在Presenter的实现类的构造函数中调用,相当于在Activity中生成一个Presenter对象时,该方法就会被调用,设置给View层中的Presenter对象。

public interface BaseView<T> {
    void setPresenter(T presenter);
}

BasePresenter 是Presenter的基类,包含 start() 方法,其作用是Presenter开始获取数据并调用View中的方法改变界面的显示,调用时机在View层实现类中也就是Fragment类的 onResume() 方法中。

public interface BasePresenter {
    void start();
}

Contract类统一管理View与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();
    }
}

Activity的作用

Activity 在项目中是一个全局控制着,负责创建View以及Presenter,并将两者联系起来

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);

        // Set up the toolbar.
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar ab = getSupportActionBar();
        ab.setDisplayHomeAsUpEnabled(true);
        ab.setDisplayShowHomeEnabled(true);

        // Get the requested task id
        String taskId = getIntent().getStringExtra(EXTRA_TASK_ID);

        // Fragment作为View层,在Activity中创建
        TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()
                .findFragmentById(R.id.contentFrame);

        if (taskDetailFragment == null) {
            taskDetailFragment = TaskDetailFragment.newInstance(taskId);

            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
                    taskDetailFragment, R.id.contentFrame);
        }

        // Presenter对象
        new TaskDetailPresenter(
                taskId,
                Injection.provideTasksRepository(getApplicationContext()),
                taskDetailFragment);
    }

    @Override
    public boolean onSupportNavigateUp() {
        onBackPressed();
        return true;
    }

    @VisibleForTesting
    public IdlingResource getCountingIdlingResource() {
        return EspressoIdlingResource.getIdlingResource();
    }
}

在Activity中,创建后的Fragment实例作为Presenter构造函数的参数传入,这样Presenter中就会有View层的实例;在Presenter的构造函数中会将此对象设置给Fragment中的Presenter对象,这样View层就有Presenter实例了,就保证了相互间的交互。

Fragment(ViewC层)

实例中将fragment作为view层的实现类,为什么是fragment呢?有两个原因:

  • 第一个原因是我们把activity作为一个全局控制类来创建对象,把fragment作为view,这样两者就能各司其职。
  • 第二个原因是因为fragment比较灵活,能够方便的处理界面适配的问题。
public class AddEditTaskFragment extends Fragment implements AddEditTaskContract.View {

    public static final String ARGUMENT_EDIT_TASK_ID = "EDIT_TASK_ID";

    // Presenter 实例
    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) {
    	// 通过该方法,view获得了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();
    }
}

AddEditTaskPresenter(Presenter层)

presenter构造函数中调用了view得setPresenter方法将自身实例传入,start方法中处理了数据加载与展示。如果需要界面做对应的变化,直接调用view层的方法即可,这样view层与presenter层就能够很好的被划分。

public class AddEditTaskPresenter implements AddEditTaskContract.Presenter,
        TasksDataSource.GetTaskCallback {

    @NonNull
    private final TasksDataSource mTasksRepository;

    @NonNull
    private final AddEditTaskContract.View mAddTaskView;

    @Nullable
    private String mTaskId;

    public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
            @NonNull AddEditTaskContract.View addTaskView) {
        mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository);
        mAddTaskView = checkNotNull(addTaskView);

        // 设置View层中的Presenter对象
        mAddTaskView.setPresenter(this);
    }

    @Override
    public void start() {
    	// 此方法在View层中的onResume方法中调用
        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.
    }
}

data(Model层)

data下的就都是model层了。项目中model层最大的特点是被赋予了数据获取的职责,与我们平常model层只定义实体对象截然不同,实例中,数据的获取、存储、数据状态变化都是model层的任务,presenter会根据需要调用该层的数据处理逻辑并在需要时将回调传入。这样model、presenter、view都只处理各自的任务,此种实现确实是单一职责最好的诠释。


如果我的文章对您有所帮助,就请我喝杯咖啡吧^^


Messages