Android MVP模式介绍和讲解

版权声明:本文为博主原创文章,转载需注明出处。 https://blog.csdn.net/jay100500/article/details/79829489

作者:谭东

先给个MVP的出处链接:https://github.com/googlesamples/android-architecture/,出处就是google在github上的一个架构的开源例子,里面有个todo-mvp的sample项目,大概的展示了下它们的架构设计,也就是这个todo-mvp例子项目的架构:https://github.com/googlesamples/android-architecture/tree/todo-mvp/

这只是他们认为比较好的架构方式,被传开了了,也就出现了MVP模式。但是并不一定这个模式就非常好,只不过有它的好处的初衷:想让结构更加的清晰,逻辑耦合度降低。但相对来说也使开发变得逻辑复杂了些,一般大型的项目推荐使用,看你的需求和权衡了。

之前也看了一些MVP的文章,没有太认真看,也是云里雾里。后来索性抽空一上午的时间,直接看官方的todo-mvp的例子源码,一点点分析整理google的mvp的设计的思路和流程。这个Demo演示了谷歌github例子的Model-View-Presenter(MVP)的架构设计,不过说实话,官方的demo写的有点乱,类命名、方法命名等很不清晰。总体来说还好,下午的时间就按照这个mvp的架构规范重新写了一个版本的todo-mvp。原版的todo-mvp用的是sqlite,涉及到了RoomDatabase。让注重mvp阅读人看起来很乱,容易分散重点。所以我这里使用sharedpreferences替换了sqlite的数据增删改查。 整体说来,官方的例子选择的还算容易理解,实现的功能就是列表展示本地存储的任务Task数据。有增加一条、删除一条、获取一条、获取全部任务的功能。

先看原版的实现设计图:


淡绿色部分就是View和Presenter部分的双向交互,左侧白底部分的就是Model部分的逻辑,可以看出整个MVP就是Model和Presenter的交互及View和Presenter的双向交互,而View和Model之间并没有直接的交互,这也就避免了耦合吧。

下面这个是我改过重写的demo的实现,把SQLite换成了SharedPreference,去除了本地和网络的任务的判断。





我这里以todo-mvp的我精心改版整理后的项目进行分析讲解,MVP的整体架构分析:


官方的demo基本上是按照功能分类的,把很多类都混在一起了,我这里的结构整理,相对清晰一些。

先说MVP的Model:

Model里先有实体,这里是Task:

/**
 * Created by Tandong on 2018/4/2.
 * MVP中某个M的基础实体类
 */

public class Task {
    private String id;
    private String date;
    private String title;
    private String content;
    private boolean completed;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public boolean isCompleted() {
        return completed;
    }

    public void setCompleted(boolean completed) {
        this.completed = completed;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Task task = (Task) o;
        return id.equals(task.id);
    }
}

以及操作Task实体的定义的方法,TaskDao,它的作用就是预先定义对外暴漏操作Task的接口方法:

/**
 * Created by Tandong on 2018/4/2.
 * 对应的Task的Model的主要方法接口定义类
 */

public interface TaskDao {
    void getTasks(@NonNull LoadTasksCallback callback);

    void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);

    void saveTask(@NonNull Task task);

    void refreshTasks();

    void deleteAllTasks();

    void deleteTask(@NonNull Task task);

    interface LoadTasksCallback {

        void onTasksLoaded(List<Task> tasks);

        void onDataNotAvailable();
    }

    interface GetTaskCallback {

        void onTaskLoaded(Task task);

        void onDataNotAvailable();
    }
}

之后,再实现TaskDao里的定义的接口方法,也就是要有TaskImpl实现类:

/**
 * Created by Tandong on 2018/4/2.
 * Task的Model的接口方法的具体实现,采用单例模式
 */

public class TaskDaoImpl implements TaskDao {
    private static TaskDaoImpl INSTANCE = null;
    private static final int SERVICE_LATENCY_IN_MILLIS = 500;
    private static List<Task> TASKS_SERVICE_DATA = null;

    // 防止直接实例化
    private TaskDaoImpl() {
    }

    /**
     * 单例模式创建TaskDaoImpl
     *
     * @return
     */
    public static TaskDaoImpl getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new TaskDaoImpl();
        }
        return INSTANCE;
    }

    /**
     * 销毁回收实例对象
     */
    public static void destroyInstance() {
        INSTANCE = null;
    }

    @Override
    public void getTasks(@NonNull final LoadTasksCallback callback) {
        //逻辑的具体实现,这里获取数据
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                TASKS_SERVICE_DATA = Utils.getTasksFromSp();
                callback.onTasksLoaded(TASKS_SERVICE_DATA);
            }
        }, SERVICE_LATENCY_IN_MILLIS);
    }

    @Override
    public void getTask(@NonNull String taskId, @NonNull final GetTaskCallback callback) {
        int position = 0;
        TASKS_SERVICE_DATA = Utils.getTasksFromSp();
        for (int i = 0; i < TASKS_SERVICE_DATA.size(); i++) {
            if (TASKS_SERVICE_DATA.get(i).getId().equals(taskId)) {
                position = i;
            }
        }
        //逻辑的具体实现
        final Task task = TASKS_SERVICE_DATA.get(position);
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                callback.onTaskLoaded(task);
            }
        }, SERVICE_LATENCY_IN_MILLIS);
    }

    @Override
    public void saveTask(@NonNull Task task) {
        //逻辑的具体实现
        TASKS_SERVICE_DATA.add(task);
        Utils.addTaskToSp(task);
    }

    @Override
    public void refreshTasks() {
        //逻辑的具体实现
        TASKS_SERVICE_DATA = Utils.getTasksFromSp();
    }

    @Override
    public void deleteAllTasks() {
        //逻辑的具体实现
        TASKS_SERVICE_DATA.clear();
        Utils.deleteAllTaskToSp();
    }

    @Override
    public void deleteTask(@NonNull Task task) {
        //逻辑的具体实现
        Utils.deleteTaskToSp(task);
    }
}

那么可以看出,Model里一般有3个类,以这个todo-mvp例子来说,有实体类Task,接口方法类TaskDao,实现类TaskImpl。

接着定义我们的View:

根据情况,我们可以定义一个父类的View叫BaseView,当然直接写一个View也没问题。

这个View的类里我们定义好我们的Activity或者Fragment界面需要交互操作的一些方法。

interface View extends BaseView<Presenter> {
        /**
         * 这里定义操作View界面相关的回调方法
         */
        void setLoadingIndicator(boolean show);

        void showTasks(List<Task> tasks);

        void showAddTaskOk(Task task);

        void showTaskDetailsUi(String taskId);

        void showLoadingTasksError();

        void showNoTasks();

        boolean isActive();

        void showClearedAllTaskOk();

        void showClearedTaskOk(Task task);
    }

然后定义Presenter,Presenter的中文意思是主持人,顾名思义也就是整个Model和View的调度和指挥者。Presenter就是把Model的TaskImpl类和View的View类关联结合起来。里面定义的方法也是对应View和Model相连接调用的中间件接口方法,类似于一个中间转接接口。

    interface Presenter extends BasePresenter {
        /**
         * 这里定义操作加载处理数据的相关回调方法
         */

        void loadTasks(boolean showLoading);

        void addNewTask(@NonNull Task task);

        void openTaskDetails(@NonNull Task task);

        void clearAllTasks();

        void clearTask(@NonNull Task task);
    }

google官方的todo-mvp的例子中间又加了一层,Contract类,就是一个连接关联类Contract,把View和Presenter关联起来。

/**
 * Created by Tandong on 2018/4/2.
 * 一个连接关联类Contract,把View和Presenter关联起来
 */

public interface TasksContract {

    interface View extends BaseView<Presenter> {
        /**
         * 这里定义操作View界面相关的回调方法
         */
        void setLoadingIndicator(boolean show);

        void showTasks(List<Task> tasks);

        void showAddTaskOk(Task task);

        void showTaskDetailsUi(String taskId);

        void showLoadingTasksError();

        void showNoTasks();

        boolean isActive();

        void showClearedAllTaskOk();

        void showClearedTaskOk(Task task);
    }

    interface Presenter extends BasePresenter {
        /**
         * 这里定义操作加载处理数据的相关回调方法
         */

        void loadTasks(boolean showLoading);

        void addNewTask(@NonNull Task task);

        void openTaskDetails(@NonNull Task task);

        void clearAllTasks();

        void clearTask(@NonNull Task task);
    }

}

这一层我觉的可以调整,可有可无。把这个写成一个View就可以。

然后定义编写我们的真正的Presenter实现类,TasksPresenter类。

/**
 * Created by Tandong on 2018/4/2.
 * Presenter的业务真正实现,监听用户UI操作,回调和更新数据给UI
 */

public class TasksPresenter implements TasksContract.Presenter {
    private final TaskDaoImpl taskDaoImpl;
    private final TasksContract.View tasksView;

    public TasksPresenter(@NonNull TaskDaoImpl taskDaoImpl, @NonNull TasksContract.View tasksView) {
        this.taskDaoImpl = Utils.checkNotNull(taskDaoImpl, "tasksRepository cannot be null");
        this.tasksView = Utils.checkNotNull(tasksView, "tasksView cannot be null!");

        this.tasksView.setPresenter(this);
    }

    @Override
    public void start() {
        loadTasks(true);
    }

    @Override
    public void loadTasks(boolean showLoading) {
        toLoadTasks(showLoading);
    }

    /**
     * 添加数据操作
     */
    @Override
    public void addNewTask(@NonNull Task task) {
        //操作Model
        taskDaoImpl.saveTask(task);
        //操作View
        tasksView.showAddTaskOk(task);
    }

    @Override
    public void openTaskDetails(@NonNull Task requestedTask) {
        Utils.checkNotNull(requestedTask, "requestedTask cannot be null!");
        tasksView.showTaskDetailsUi(requestedTask.getId());
    }

    /**
     * 删除全部数据操作
     */
    @Override
    public void clearAllTasks() {
        //操作Model
        taskDaoImpl.deleteAllTasks();
        //操作View
        tasksView.showClearedAllTaskOk();
    }

    /**
     * 删除单条数据操作
     *
     * @param task
     */
    @Override
    public void clearTask(@NonNull Task task) {
        //操作Model
        taskDaoImpl.deleteTask(task);
        //操作View
        tasksView.showClearedTaskOk(task);
    }

    private void toLoadTasks(final boolean showLoadingUI) {
        if (showLoadingUI) {//显示加载中进度条布局
            tasksView.setLoadingIndicator(true);
        }
        taskDaoImpl.getTasks(new TaskDao.LoadTasksCallback() {
            @Override
            public void onTasksLoaded(List<Task> tasks) {
                // The view may not be able to handle UI updates anymore
                if (!tasksView.isActive()) {
                    return;
                }
                if (showLoadingUI) {//隐藏加载中进度条
                    tasksView.setLoadingIndicator(false);
                }
                processTasks(tasks);
            }

            @Override
            public void onDataNotAvailable() {
                // The view may not be able to handle UI updates anymore
                if (!tasksView.isActive()) {
                    return;
                }
                tasksView.showLoadingTasksError();
            }
        });
    }

    private void processTasks(List<Task> tasks) {
        if (tasks.isEmpty()) {
            // Show a message indicating there are no tasks for that filter type.
            processEmptyTasks();
        } else {
            //加载完毕后回调给View进行显示逻辑处理
            tasksView.showTasks(tasks);
        }
    }

    private void processEmptyTasks() {
        tasksView.showNoTasks();
    }

}

可以看出,这个Presenter实现类,实现了之前定义的Presenter接口里的方法,监听用户UI操作,回调和更新数据给UI。里面含有两个大类:TaskDaoImpl这个Model实现类和TasksContract.View这个View类,也就是把Model和View进行了关联和调度。从每个方法里也可以看出,每个方法基本上都有Model方法的调用和View的相应的操作调用。

好了,我们的Model、View、Presenter都写好了。接下来就是Activity或Fragment界面的调用了。

todo-mvp的demo里是Activity里调用了TaskFragment进行View的操作。那看下TaskFragment里的具体逻辑,TaskFragment先实现之前View里调用的接口TasksContract.View:

/**
 * Created by Tandong on 2018/4/2.
 * Fragment实现View的接口方法回调
 */

public class TaskFragment extends BaseFragment implements TasksContract.View, OnItemCallBack {
    @Bind(R.id.rv)
    RecyclerView recyclerView;
    @Bind(R.id.tv_tips)
    TextView tv_tips;
    private TaskAdapter taskAdapter;
    private LinearLayoutManager linearLayoutManager;
    private TasksContract.Presenter presenter;

    public TaskFragment() {
        // Requires empty public constructor
    }

    public static TaskFragment newInstance() {
        return new TaskFragment();
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_tasks, container, false);
        ButterKnife.bind(this, root);
        return root;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        linearLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
        taskAdapter = new TaskAdapter(getActivity(), Utils.getTasksFromSp());
        taskAdapter.setOnItemCallBack(this);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.setAdapter(taskAdapter);
    }

    @Override
    public void onResume() {
        super.onResume();
        presenter.start();
    }

    @Override
    public void setPresenter(TasksContract.Presenter presenter) {
        this.presenter = Utils.checkNotNull(presenter);
    }

    @Override
    public void setLoadingIndicator(boolean show) {
        if (show) {
            tv_tips.setVisibility(View.VISIBLE);
            tv_tips.setText("加载中...");
        } else {
            tv_tips.setVisibility(View.GONE);
        }
    }

    @Override
    public void showTasks(List<Task> tasks) {
        //view的操作具体逻辑实现
        taskAdapter.setDatas(tasks);
    }

    @Override
    public void showAddTaskOk(Task task) {
        //view的操作具体逻辑实现
        showToast("添加任务成功~");
        taskAdapter.addData(task);
    }

    @Override
    public void showTaskDetailsUi(String taskId) {
        //view的操作具体逻辑实现
    }

    @Override
    public void showLoadingTasksError() {
        //view的操作具体逻辑实现
        tv_tips.setVisibility(View.VISIBLE);
        tv_tips.setText("加载失败~");
    }

    @Override
    public void showNoTasks() {
        //view的操作具体逻辑实现
        tv_tips.setVisibility(View.VISIBLE);
        tv_tips.setText("没有数据~");
    }

    @Override
    public boolean isActive() {
        //view的操作具体逻辑实现
        return isAdded();
    }

    @Override
    public void showClearedAllTaskOk() {
        //view的操作具体逻辑实现
        tv_tips.setVisibility(View.VISIBLE);
        tv_tips.setText("没有数据~");
    }

    @Override
    public void showClearedTaskOk(Task task) {
        taskAdapter.removeData(task);
    }

    @Override
    public void onItemLongClick(View v, int posiotion) {
        showToast("长按移除  " + posiotion);
        presenter.clearTask(taskAdapter.getTasks().get(posiotion));
    }

    @Override
    public void onItemClick(View v, int position) {
        Task task = taskAdapter.getTasks().get(position);
        showToast(position + "  " + task.getTitle() + "  " + task.getContent());
    }
}

然后在实现的回调方法里,把返回的数据或者状态进行相应的操作和UI上的响应。也可以操作presenter里的一些方法。

那么整合MVP的todo-mvp的官方例子基本上这样,官方的todo-mvp里的功能多一点,有展示、添加、详情等分类。不过流程和操作都类似,MVP的官方例子就给大家讲解这么多。

整理后的Demo的github地址:https://github.com/jaychou2012/MVPDemo-todo-mvp


猜你喜欢

转载自blog.csdn.net/jay100500/article/details/79829489