Android 官方架构示例android-architecture之todo-mvp深入解析

google在GitHub上开源了android-architecture项目,包含了MVP、MVVM等架构的示例项目,今天我们从todo‑mvp开始入手,研究里面代码的具体实现

项目地址

todo-mvp项目地址

应用功能介绍

了解一个项目的主要功能最快的方法就是直接安装,然后运行,就可以知道主要有哪些页面,有哪些功能

todo示例项目是一个代办事项的简单App,有四个页面

  • 待办列表页
  • 待办详情页
  • 待办编辑页
  • 待办统计页

      

      

项目结构

我们主要研究MVP代码的实现,androidTest、androidTestMock、mock、test下是测试的代码,不在我们本次的讨论范围内,下次会另开一篇文章讨论。

项目分包主要以业务模块来划分

  • tasks包:待办列表模块
  • taskdetail包:待办详情模块
  • addedittask包:新建/编辑待办模块
  • statistics包:待办统计模块
  • data包:跟MVP中V有关的模块
  • util包:工具模块
  • BaseView接口:MVP中V的基础接口
  • BasePresenter包接口:MVP中P的基础接口

我们先看下BaseView的定义,每一个具体的业务模块的View都要实现该接口,通过setPresenter方法持有Presenter的对象引用,然后通过Presenter对象调用具体的业务逻辑

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

我们再看下BasePresenter的定义,每一个具体业务模块的Presenter都需要实现该接口,需实现start()方法做一些初始化的业务,而该方法一般是在Fragment的onResume中调用(单不是绝对的,视具体业务而定)

public interface BasePresenter {
    void start();
}

MVP架构概览

如下图所示,我们具体展开每一个业务模块,可以发现每个模块的设计都是一样的,主要有四个类型的类

  • Contract:合同类,这是一个接口,里面又定义了两个子接口View和Presenter
  • Activity:只是一个Fragment的容器,具体的View实现交个Fragment
  • Fragment:实现Contract中定义的View接口,承担View的角色,负责页面的显示刷新交互,持有Presenter的引用,调用Presenter的相关业务实现
  • Presenter:实现Contract中定义的Presenter接口,承担Presenter角色,负责具体的业务逻辑,在Presenter中会可能会调用Model对数据进行操作,然后通过持有的View对象的引用回调View,操作更新页面

V和P具体代码实现

由于每一个具体业务模块的实现代码都大同小异,我们选取待办编辑这一业务模块来看看具体的实现代码

待办列表模块的业务功能比较多,代码量也最多,为了快速入门,我们选择待办编辑代码量比较适中,也不会像待办统计也代码量太少。

待办编辑模块主要有两个功能,一个是新建待办然后编辑保存,一个是编辑已有的待办然后保存

待办编辑模块主要有如下四个类,我们按顺序讲解。

  • AddEditTaskContract
  • AddEditTaskActivity
  • AddEditTaskFragment
  • AddEditTaskPresenter

AddEditTaskContract

Contract定义了View和Presenter之间的一组协议

View继承BaseView,负责UI的操作更新

Presenter继承BasePresenter,负责具体的业务逻辑,有可能会调用Model的功能

public interface AddEditTaskContract {
    interface View extends BaseView<Presenter> {
        // 保存待办时如果是空任务的情况的显示提醒
        void showEmptyTaskError();
        // 保存成功后返回待办列表页面
        void showTasksList();
        // 设置待办标题
        void setTitle(String title);
        // 设置待办说明
        void setDescription(String description);
        // 当前View是否已销毁,一般在Presenter中更新UI前都要调用该方法判断
        boolean isActive();
    }

    interface Presenter extends BasePresenter {
        // 保存待办
        void saveTask(String title, String description);
        // 查询已有的待办事项,在初始进入的时候且是已有待办的情况下调用
        void populateTask();
        // 返回一个标志字段判断是否需要重新加载数据
        boolean isDataMissing();
    }
}

AddEditTaskActivity

Activity的功能比较简单,主要是负责加载Fragment和创建Presenter对象

不承担View和Presenter的功能

public class AddEditTaskActivity extends AppCompatActivity {

    public static final int REQUEST_ADD_TASK = 1;
    public static final String SHOULD_LOAD_DATA_FROM_REPO_KEY = "SHOULD_LOAD_DATA_FROM_REPO_KEY";
    private AddEditTaskPresenter mAddEditTaskPresenter;
    private ActionBar mActionBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.addtask_act);

        // 一些ToolBar初始化设置
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        mActionBar = getSupportActionBar();
        mActionBar.setDisplayHomeAsUpEnabled(true);
        mActionBar.setDisplayShowHomeEnabled(true);

        // 如果是从待办列表页面点击某个具体待办跳转过来,会传过来待办的id
        String taskId = getIntent().getStringExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID);
        // 设置标题,如果是新建待办显示“New TO-DO”,如果是编辑已有待办,显示“Edit TO-DO”
        setToolbarTitle(taskId);

        
        AddEditTaskFragment addEditTaskFragment = (AddEditTaskFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
        if (addEditTaskFragment == null) {
            addEditTaskFragment = AddEditTaskFragment.newInstance();
            // 编辑已有任务需将待办id传递给Fragment
            if (getIntent().hasExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID)) {
                Bundle bundle = new Bundle();
                bundle.putString(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId);
                addEditTaskFragment.setArguments(bundle);
            }
            // 将Fragment添加到Activity中
            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), addEditTaskFragment, R.id.contentFrame);
        }

        boolean shouldLoadDataFromRepo = true;
        // 防止设置更改(如横竖屏切换)情况下又重复请求一次数据
        if (savedInstanceState != null) {
            // Data might not have loaded when the config change happen, so we saved the state.
            shouldLoadDataFromRepo = savedInstanceState.getBoolean(SHOULD_LOAD_DATA_FROM_REPO_KEY);
        }

        // 实例化Presenter,传入addEditTaskFragment持有View对象
        mAddEditTaskPresenter = new AddEditTaskPresenter(
                taskId,
                Injection.provideTasksRepository(getApplicationContext()),
                addEditTaskFragment,
                shouldLoadDataFromRepo);
    }

    private void setToolbarTitle(@Nullable String taskId) {
        if(taskId == null) {
            mActionBar.setTitle(R.string.add_task);
        } else {
            mActionBar.setTitle(R.string.edit_task);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        // Save the state so that next time we know if we need to refresh data.
        outState.putBoolean(SHOULD_LOAD_DATA_FROM_REPO_KEY, mAddEditTaskPresenter.isDataMissing());
        super.onSaveInstanceState(outState);
    }

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

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

AddEditTaskFragment

AddEditTaskFragment功能很简单,就是编辑待办事项,可以是编辑新建的待办,也可以是编辑已有的待办,然后最后保存返回待办列表页面。

AddEditTaskFragment通过setPresenter持有AddEditTaskPresenter对象示例

在onResume的地方调用Presenter请求已有的任务(如果是已有任务的情况下)

在右下角完成按钮点击的时候调用Presenter的saveTask()方法保存任务

View的主要功能就是:

  1. 事件触发后调用Presenter处理具体的业务
  2. 实现Contract中定义的方法操作显示UI(等待Presenter业务处理完的回调)
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();
        // Presenter.start的具体实现中,如果是已有的待办事项,会先查询
        mPresenter.start();
    }

    // 设置Presenter,持有该对象的引用
    @Override
    public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
        mPresenter = checkNotNull(presenter);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // 右下角的完成编辑保存按钮
        FloatingActionButton fab = 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) {
                // 调用Presenter实现具体保存业务逻辑
                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 = root.findViewById(R.id.add_task_title);
        mDescription = root.findViewById(R.id.add_task_description);
        setHasOptionsMenu(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);
    }

    // 当前View是否已销毁,一般在Presenter中更新UI前都要调用该方法判断
    @Override
    public boolean isActive() {
        return isAdded();
    }
}

AddEditTaskPresenter

AddEditTaskPresenter实现了AddEditTaskContract.Presenter中定义的接口

在View中触发事件,然后为了View跟Model层的解耦,甩锅给Presenter让Presenter调用Model执行一些增删改查等操作(本地数据库操作或者网络请求操作),最后再通过Presenter回调View层处理更新UI。整个过程View跟Model是解耦的。

所以Presenter就是View和Model之间的桥梁,是个跑腿的,举个栗子:

View说:喂,Presenter,去Model那边帮我买一瓶酱油回来

Presenter:屁颠屁颠的跑去Model那边买了瓶酱油,然后再跑回去拿给View

View:拿到酱油后炒了一盘炒饭发到朋友圈显示出来

/**
 * 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;

    private boolean mIsDataMissing;

    /**
     * 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
     * @param shouldLoadDataFromRepo whether data needs to be loaded or not (for config changes)
     */
    public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
            @NonNull AddEditTaskContract.View addTaskView, boolean shouldLoadDataFromRepo) {
        mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository);
        mAddTaskView = checkNotNull(addTaskView);
        mIsDataMissing = shouldLoadDataFromRepo;

        mAddTaskView.setPresenter(this);
    }

    // 如果是已有的待办,查询该待办的信息
    @Override
    public void start() {
        if (!isNewTask() && mIsDataMissing) {
            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.");
        }
        // 通过Repository数据仓库根据taskId查询该待办信息,然后在callback中回调UI显示待办信息
        mTasksRepository.getTask(mTaskId, this);
    }

    @Override
    public void onTaskLoaded(Task task) {
        // 查询成功后回调UI显示,显示前先判断View是否已销毁
        // The view may not be able to handle UI updates anymore
        if (mAddTaskView.isActive()) {
            mAddTaskView.setTitle(task.getTitle());
            mAddTaskView.setDescription(task.getDescription());
        }
        mIsDataMissing = false;
    }

    @Override
    public void onDataNotAvailable() {
        // 查询失败后回调UI显示,显示前先判断View是否已销毁
        // The view may not be able to handle UI updates anymore
        if (mAddTaskView.isActive()) {
            mAddTaskView.showEmptyTaskError();
        }
    }

    @Override
    public boolean isDataMissing() {
        return mIsDataMissing;
    }

    // 判断是已有待办或者是新建待办
    private boolean isNewTask() {
        return mTaskId == null;
    }

    // 通过Model保存新创建的待办,然后返回待办列表页面
    private void createTask(String title, String description) {
        Task newTask = new Task(title, description);
        if (newTask.isEmpty()) {
            mAddTaskView.showEmptyTaskError();
        } else {
            mTasksRepository.saveTask(newTask);
            mAddTaskView.showTasksList();
        }
    }

    // 通过Model保存已有待办的更新,然后返回待办列表页面
    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.
    }
}

Model设计

本示例中Mode层在data包下,又分为local和remote两个包。

local表示本地数据库数据

remote表示远程服务端数据,但是本示例并未真正实现服务端接口的请求,只是用一个LinkedHashMap增删改查待办任务,然后用Handler.postDelay来模拟异步请求

TaskDatasource定义了数据源的接口,包括获取所有待办事项、获取单个待办事项、创建待办事项、完成待办事项等

TaskLocalDataSource实现了TaskDatasource,实现了本地的数据操作

TaskRemoteDataSource实现了TaskDataSource,模拟了网络异步的数据操作

TaskRepository代表的就是View层,Presenter中持有的View对象就是TaskRepository,TaskRepository负责提供数据和对数据进行操作,然后把结果返回给Presenter,Presenter再回调View更新视图。

TaskRepository也实现了TaskDatasource接口,内部同时持有TaskLocalDataSource和TaskRemoteDataSource两种数据源,同时还有一个Map内存缓存,对待办数据进行增加、删除、修改等操作是会同时对内存缓存、TaskLocalDataSource、TaskRemoteDataSource三种数据源进行操作。

总结

盗用一张百科的图片,如有侵权请联系删除

  • View和Model彻底解耦
  • Presenter是View和Model之间的桥梁

一个完整流程包含以下四个步骤:

  1. View中事件触发
  2. Presenter调用Model请求处理数据
  3. Model处理完数据返回给Presenter
  4. Presenter回调View更新UI

 

 

posted @ 2019-06-08 17:13  野猿新一  阅读(54)  评论(0编辑  收藏  举报