美文网首页程序员
学习笔记| (一) MVP

学习笔记| (一) MVP

作者: yanerly | 来源:发表于2018-09-20 19:13 被阅读25次

    前言:

    前几天去面试,有一家公司问到会不会mvp,说说对他的理解,你的项目中是是如何实现的,然而我只是看了很多讲mvp的文章,并没有真正的在项目中用过,毕竟也都是小项目......然后的然后就让我现场写mvp.....

    推荐一篇通俗易懂的文章

    https://www.jianshu.com/p/389c9ae1a82c
    之前也看了很多的文章,但是看得越多就越乱,看到最后也没有真正弄懂mvp到底是怎么个回事,结合google的todomvp和这篇文章,大致理清了mvp是咋回事了。

    什么是mvp?

    • mvp就是model/view/presenter三者
    • 关系:presenter相当于是model和view之前的桥梁,他俩是不能直接交互的,得通过presenter来连接。
    • 那这样的话,view只能和presenter交互,那怎么才能在view中操作presenter呢?只需要把presenter作为参数传给view就行了;那要在presenter中操作view怎么办呢,也是同样的方法,只需要把view传给view就行了。因为要在presenter中获取数据,所以还需要在Presenter中传入model。

    既然说presenter是中间层,那在哪实现的presenter呢?

    这个是当时问到我的一个题目。准确的说应该是在Activity中创建的presenter实例,将view和model作为参数传过去了。

    什么是view?

    根据google官方的例子,view其实是Fragment,Activity只是一个全局的控制者,负责创建view以及presenter实例。

    view和presenter是怎么关联的?

    在presenter的构造函数中调用view的setPresenter(this)就可以将二者绑定了。

    前面做了这么多铺垫,接下来分析一下google官方demo(参考上面的链接分析的):

    • 功能页面:


      主页.jpg 详情页.jpg
    编辑页.jpg 统计页.jpg
    • 项目结构


      项目结构.png
    • 代码分析

      • 在最底下有两个基类:
    public interface BasePresenter {
    
        //presenter开始获取数据并调用view中方法改变界面显示,其调用时机是在Fragment类的onResume方法中。
        void start();
    
    }
    
    public interface BaseView<T> {
    
        //view要持有presenter的引用,在将presenter实例传入view中,其调用时机是presenter实现类的构造函数中。
        void setPresenter(T presenter);
    }
    
    • 定义了契约类(接口)
    /**
     * 契约类来统一管理view与presenter的所有的接口
     */
    public interface TaskDetailContract {
    
        interface View extends BaseView<Presenter> {
            //定义了该界面(功能)中所有的UI状态情况
            // 设置数据加载状态
            void setLoadingIndicator(boolean active);
    
            // 处理task加载失败的情况
            void showMissingTask();
    
            // 隐藏待办事项title
            void hideTitle();
    
            // 显示待办事项title
            void showTitle(String title);
    
            // 隐藏待办事项的描述
            void hideDescription();
    
            // 显示待办事项的描述
            void showDescription(String description);
    
            //显示待办事项的状态,是否已完成
            void showCompletionStatus(boolean complete);
    
            //显示要编辑的待办事项
            void showEditTask(String taskId);
    
            //显示已删除的待办事项
            void showTaskDeleted();
    
            //显示已完成的待办事项
            void showTaskMarkedComplete();
    
            //显示未完成的点半事项
            void showTaskMarkedActive();
    
            //是否未完成
            boolean isActive();
        }
    
        interface Presenter extends BasePresenter {
            //定义了该界面(功能)中所有的用户操作事件
            // 修改待办事项
            void editTask();
    
            // 删除待办事项
            void deleteTask();
    
            // 标记完成
            void completeTask();
    
            // 标记未完成
            void activateTask();
        }
    }
    

    这样写了之后,这个功能有哪些功能操作,以及ui上会有哪些变化都很清楚

    • View
    public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {
    private TaskDetailContract.Presenter mPresenter;
         .....
      @Override
        public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {
           //判空,在Presenter中会将view和presenter进行绑定,这里是检查传入的presenter是否为空
            mPresenter = checkNotNull(presenter);
        }
          ......
        @Override
        public void onResume() {
            super.onResume();
            //开始获取model中的数据
            mPresenter.start();
        }
          ......
      @Override
        public void hideDescription() {
            //隐藏待办事件的描述
            mDetailDescription.setVisibility(View.GONE);
        }
          ......
    }
    

    TaskDetailFragment 作为view层,主要负责ui相关的状态更新,实现了契约类中的View接口后,在相关的代码中实现具体的内容。

    • Presenter
      Activity在mvp中的作用:
    /**
     * Activity在项目中是一个全局的控制者,负责创建view以及presenter实例,并将二者联系起来。
     */
    public class TaskDetailActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState){
       //创建view
      TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()
                    .findFragmentById(R.id.contentFrame);
    
     //创建presenter实例
            new TaskDetailPresenter(
                    taskId,
                    Injection.provideTasksRepository(getApplicationContext()),
                    taskDetailFragment);
     }
    }
    

    创建Presenter

    
    /**
     * 作为Presenter层,实现了该接口,如此 TaskDetailPresenter 则只关注业务层的逻辑相关,UI的更新只需调用View的状态方法。
     */
    public class TaskDetailPresenter implements TaskDetailContract.Presenter {
       /**
         * 构造函数,presenter作为中间层链接model和view,为了相互交互,则要持有他们的引用
         * @param taskId : 待办事项id
         * @param tasksRepository : model
         * @param taskDetailView : view
         */
        public TaskDetailPresenter(@Nullable String taskId,
                                   @NonNull TasksRepository tasksRepository,
                                   @NonNull TaskDetailContract.View taskDetailView) {
            mTaskId = taskId;
            mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
    
            // 保持对View(TaskDetailFragment)的引用
            mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");
    
            // 使View(TaskDetailFragment)也保持对自身(TaskDetailPresenter)的引用
            mTaskDetailView.setPresenter(this);
        }
    
      @Override
        public void start() {
            //开始获取数据
            openTask();
        }
    }
    
    • Model层:
      model层中主要是数据的一些操作,数据的获取,数据的添加,数据的删除等
      在Presenter的start()中获取数据:
    /**
         * 获取数据
         */
        private void openTask() {
            // 判空处理
            if (Strings.isNullOrEmpty(mTaskId)) {
                //加载数据的时候,如果taskId为空,则调用view的加载失败的情况
                mTaskDetailView.showMissingTask();
                return;
            }
    
            // 更新状态,加载成功的时候,设置加载的状态,
            mTaskDetailView.setLoadingIndicator(true);
    
            // modele,获取该条Task数据
            mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
                @Override
                public void onTaskLoaded(Task task) {
                    //数据加载成功
                    // View已经被用户回退(已销毁)
                    if (!mTaskDetailView.isActive()) {
                        return;
                    }
    
                    // 获取到task数据,并更新UI
                    mTaskDetailView.setLoadingIndicator(false);
                    if (null == task) {
                        //如果没有加载到task,则显示加载失败的ui
                        mTaskDetailView.showMissingTask();
                    } else {
                        //否则,显示这个待办事项
                        showTask(task);
                    }
                }
    
                @Override
                public void onDataNotAvailable() {
                    // 显示数据获取失败时的状态
                    if (!mTaskDetailView.isActive()) {
                        return;
                    }
                    mTaskDetailView.showMissingTask();
                }
            });
        }
    

    再看一下TasksRepository的getTask():

    @Override
        public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
            // 判空处理
            checkNotNull(taskId);
            checkNotNull(callback);
    
            // 获取缓存数据
            Task cachedTask = getTaskWithId(taskId);
    
            // Respond immediately with cache if available
            if (cachedTask != null) {
                callback.onTaskLoaded(cachedTask);
                return;
            }
    
            // Load from server/persisted if needed.
    
            // Is the task in the local data source? If not, query the network.
            // 从本地数据源(SQLite数据库)中获取,没有获取到,再从网络数据源获取
            mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
                @Override
                public void onTaskLoaded(Task task) {
                    // Do in memory cache update to keep the app UI up to date
                    if (mCachedTasks == null) {
                        mCachedTasks = new LinkedHashMap<>();
                    }
                    mCachedTasks.put(task.getId(), task);
                    // 成功,则回调
                    callback.onTaskLoaded(task);
                }
    
                @Override
                public void onDataNotAvailable() {
                    // 失败,则从远程数据源(网络)中获取
                    mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
                        @Override
                        public void onTaskLoaded(Task task) {
                            // Do in memory cache update to keep the app UI up to date
                            if (mCachedTasks == null) {
                                mCachedTasks = new LinkedHashMap<>();
                            }
                            mCachedTasks.put(task.getId(), task);
                            // 回调成功时的方法
                            callback.onTaskLoaded(task);
                        }
    
                        @Override
                        public void onDataNotAvailable() {
                            // 回调失败时的方法
                            callback.onDataNotAvailable();
                        }
                    });
                }
            });
        }
    
    • 分析Model(TasksRepositor)代码实现:
      • TasksDataSource 接口分析:
        里面包含了所有对数据的操作
    public interface TasksDataSource {
    
        //加载数据的回调
        interface LoadTasksCallback {
    
            void onTasksLoaded(List<Task> tasks);
    
            void onDataNotAvailable();
        }
    
        //获取数据的回调
        interface GetTaskCallback {
    
            void onTaskLoaded(Task task);
    
            void onDataNotAvailable();
        }
    
        /**
         * 获取所有task
         * @param callback
         */
        void getTasks(@NonNull LoadTasksCallback callback);
    
        /**
         * 根据id获取单个task
         * @param taskId
         * @param callback
         */
        void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
    
        /**
         * 保存task
         * @param task
         */
        void saveTask(@NonNull Task task);
    
        /**
         * 标记为完成
         * @param task
         */
        void completeTask(@NonNull Task task);
    
        /**
         * 标记为完成
         * @param taskId
         */
        void completeTask(@NonNull String taskId);
    
        /**
         * 标记为未完成
         * @param task
         */
        void activateTask(@NonNull Task task);
    
        /**
         * 标记为未完成
         * @param taskId
         */
        void activateTask(@NonNull String taskId);
    
        /**
         * 删除所有已完成task
         */
        void clearCompletedTasks();
    
        /**
         * 刷新task
         */
        void refreshTasks();
    
        /**
         * 删除所有task
         */
        void deleteAllTasks();
    
        /**
         * 删除单个task
         * @param taskId
         */
        void deleteTask(@NonNull String taskId);
    }
    
    • TasksRepository实现了TasksDataSource接口,维护了两个数据源。一个是本地(SQLite数据库),一个是远程(网络服务器),他们都是TasksDataSource类型。

    可以把TasksRepository理解为一个大粮仓,里面又有2个小粮仓,有我的粮食库,也有你的粮食库,我们需要粮食的时候都要到对应的粮食库中取。所有的粮食库都实现TasksDataSource接口

       //远程数据源
        private final TasksDataSource mTasksRemoteDataSource;
    
        //本地数据源
        private final TasksDataSource mTasksLocalDataSource;
    
    • TasksRepository是一个单例模式,在构造函数中要有两个数据源:
      private static TasksRepository INSTANCE = null;
    
      // Prevent direct instantiation.
        private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
                                @NonNull TasksDataSource tasksLocalDataSource) {
            mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
            mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
        }
    
        public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,
                                                  TasksDataSource tasksLocalDataSource) {
            if (INSTANCE == null) {
                INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);
            }
            return INSTANCE;
        }
    
    • 实现接口中的方法:
      ......
    @Override
        public void deleteTask(@NonNull String taskId) {
         //根据id删除Task
            ① mTasksRemoteDataSource.deleteTask(checkNotNull(taskId));
            ② mTasksLocalDataSource.deleteTask(checkNotNull(taskId));
    
            mCachedTasks.remove(taskId);
        }
      ......
    
    • ①和②中又执行了各自的操作
    • 这时候会想到一个问题,这些数据到底是在哪里进行操作的呢?
      继续看TasksLocalDataSource这个类,这是本地数据源
    public class TasksLocalDataSource implements TasksDataSource {
        private static volatile TasksLocalDataSource INSTANCE;
    
      // Prevent direct instantiation.
        private TasksLocalDataSource(@NonNull AppExecutors appExecutors,
                @NonNull TasksDao tasksDao) {
            mAppExecutors = appExecutors;
            mTasksDao = tasksDao;
        }
    
        public static TasksLocalDataSource getInstance(@NonNull AppExecutors appExecutors,
                @NonNull TasksDao tasksDao) {
            if (INSTANCE == null) {
                synchronized (TasksLocalDataSource.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new TasksLocalDataSource(appExecutors, tasksDao);
                    }
                }
            }
            return INSTANCE;
        }
    }
    

    这个类是一个单例,实现了TasksDataSource ,刚刚在上面说过"本地数据源"就相当于一个小粮仓,要从里面取粮食就要实现TasksDataSource 这个接口才行。

    然后实现接口后做具体的实现:

      ......
     @Override
        public void saveTask(@NonNull final Task task) {
            checkNotNull(task);
            Runnable saveRunnable = new Runnable() {
                @Override
                public void run() {
                  //在数据库中插入数据
                    mTasksDao.insertTask(task);
                }
            };
            mAppExecutors.diskIO().execute(saveRunnable);
        }
        ......
        @Override
        public void completeTask(@NonNull final Task task) {
            Runnable completeRunnable = new Runnable() {
                @Override
                public void run() {
                      //在数据库中更新数据
                    mTasksDao.updateCompleted(task.getId(), true);
                }
            };
    
            mAppExecutors.diskIO().execute(completeRunnable);
        }
        ......
    
    • 知道了这个是对本地数据库的具体实现后,那在哪里调用的它呢?
      按住TasksLocalDataSource ,找到它在Injection这个类中用到了,接下来看一下Injection:
    public class Injection {
    
        /**
         * 返回的是model
         * @param context
         * @return
         */
        public static TasksRepository provideTasksRepository(@NonNull Context context) {
            checkNotNull(context);
            //创建数据库
            ToDoDatabase database = ToDoDatabase.getInstance(context);
    
            //创建TasksRepository :远程数据源、本地数据源
            return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
                    TasksLocalDataSource.getInstance(new AppExecutors(), database.taskDao()));
        }
    }
    

    它的代码不多,提供了一个静态方法供外部调用,而这个方法居然返回的是 TasksRepository ,根据上面的分析,知道他相当于是model,为什么要这么写呢?

    • 接下来看 Injection 的这个方法在哪里使用了?
    //创建presenter实例
            new TaskDetailPresenter(
                    taskId,
                    Injection.provideTasksRepository(getApplicationContext()),
                    taskDetailFragment);
    

    原来在一开始创建Presenter的时候就使用了,Presenter要连接view和model,所有就要传入他们的引用。

    现在整个逻辑就很清楚了。

    • 本地数据源分析完了,还有一个远程数据源,接下来看一下 TasksRemoteDataSource这个类。和上面一样,他也要实现 TasksDataSource接口:
    public class TasksRemoteDataSource implements TasksDataSource {
    private static TasksRemoteDataSource INSTANCE;
    
     // Prevent direct instantiation.
    private TasksRemoteDataSource() {}
    
    public static TasksRemoteDataSource getInstance() {
            if (INSTANCE == null) {
                INSTANCE = new TasksRemoteDataSource();
            }
            return INSTANCE;
        }
    }
    

    其余的和本地数据源一样,只是它是从服务端读取数据

    • 总结:
      1.写一个契约类管理所有的view和presenter接口,这样所有的方法和ui状态都会很清楚了
      2.Acitivity或Fragment相当于是View层,要实现契约类中的view接口,并在对应方法中写具体的实现,因为view层可以和Presenter层交互,所以得在View层拿到Presenter,在setPresenter()方法中可以将绑定的Presenter传进来,比如点击了删除按钮,就要调用Presnter中的删除事件。
      3.Model层主要是对数据的一些操作,可以写一个接口,里面写上对数据的一些操作,model实现这个接口后做具体的实现(进行本地数据源和远程数据源的增删改查)
      4.Presenter是中间层,在创建的时候要传入View和model的引用,在Presenter的构造方法中通过 view.setPresenter(this)传入当前Presenter进行绑定

    over.....

    相关文章

      网友评论

        本文标题:学习笔记| (一) MVP

        本文链接:https://www.haomeiwen.com/subject/xqpnnftx.html