美文网首页Android爬楼梯android_它山之石Android开发
(新瓶旧酒)谷歌官方MVP项目学习--浅入源码

(新瓶旧酒)谷歌官方MVP项目学习--浅入源码

作者: hongjay | 来源:发表于2017-09-30 11:12 被阅读1061次

    人笨学的慢啊。。抓紧学习MVP

    网上MVP的学习资料也是多如牛毛,来看看谷歌爸爸是怎么教我们MVP的吧

    项目介绍


    Google把这个项目命名为:Android架构蓝图。

    这个项目也是金诚先生推荐的2017年Android百大框架排行榜中的一个

    android-architecture
    一句话介绍:google提供的Android当下各种基本框架
    上榜理由:看完它,mvp,mvvm都将入切瓜砍菜,秋风扫落叶一般...
    github https://github.com/googlesamples/android-architecture
    作者:google

    项目的目的是通过展示各种架构app的不同方式来帮助开发者解决架构问题。项目中通过不同的架构概念及方式实现了功能相同的app。你可以用示例来当做参考,或是干脆拿来当做创建app项目的基础。项目中,希望大家能把关注点集中到代码结构、整体架构、可测试性、可维护性这四个方面。当然实现app有很多种方式,千万不要把它当做定式。

    项目中有哪些示例


    目前已经稳定的示例有

    • todo-mvp(mvp基础架构示例)
    • todo‑mvp‑clean (基于mvp基础架构项目,使用了clean架构的概念)
    • todo‑mvp‑dagger (基于mvp基础架构项目,使用了dagger2进行依赖注入)
    • todo‑mvp‑rxjava (基于mvp基础架构项目,使用了rxjava2进行依赖注入)
    • todo-mvp-databinding(基于mvp基础架构项目,使用了数据绑定组件)

    仍在进展中的示例有

    • dev‑todo‑mvp‑tablet (Adds a master and detail view for tablets.)
    • dev‑todo‑mvvm‑rxjava (Based on the todo-rxjava sample, this version incorporates the Model‑View‑ViewModel pattern.)
    • dev‑todo‑mvvm‑live (Uses lifecycle-aware Architecture Components (except Room), and the Data Binding library with an MVVM architecture.)
    • dev‑todo‑mvp‑room (Uses Room, the persistence library from the Architecture Components, as a local data source with an MVP architecture.)
    • dev-todo-mvp-kotlin (Conversion of todo-mvp to Kotlin.)

    显而易见的,本渣只能从todo-mvp(mvp基础架构示例)项目中探索谷歌爸爸告诉我们的,对MVP架构的最本源的揭示。

    todo-mvp

    应用程序的名字是todo-mvp(待办清单-mvp),为此项目中的其他示例提供了基础。该样本旨在:

    • 提供基本的Model-View-Presenter(MVP)架构,而不使用任何架构框架。
    • 作为比较和对比本项目其他样本的参考点。

    todo-mvp示例使用以下依赖关系:

    • 常见的Android支持库 - com.android.support。*命名空间中的软件包提供向后兼容性和其他功能。
    • Android测试支持库 - 用于支持UI测试的框架,使用Espresso和AndroidJUnitRunner。
    • Mockito - 用于实施单元测试的框架。
    • Guava - 谷歌的一组核心库,通常用于Android应用程序。

    设计app


    该应用程序由四个UI页面组成:

    • Tasks - 用于管理任务列表。
    • TaskDetail - 用于读取或删除任务。
    • AddEditTask - 用于创建或编辑任务。
    • Statistics - 显示与任务相关的统计信息。

    在这个应用程序以及其他基于它的版本中,每个功能页面都使用以下类和接口:

    • 一个Activity用来管理fragment和presenter的创建;
    • 一个定义View和Presenter接口的Contract接口;
    • 一个实现了View接口的Fragment;
    • 一个实现了Presenter接口的presenter.

    曾经的架构

    追溯到2012年我们的代码库使用的是基本结构,那个时候我们没有使用任何第三方网络类库,而且AsyncTask也是我们的好朋友。当时的架构可以大致表示为下图。


    代码被划分为两层结构:

    • Data Layer(数据层)负责从REST API或者持久数据存储区检索和存储数据;
    • View Layer(视图层)的职责是处理并将数据展示在UI上。

    APIProvider提供了一些方法,使Activity和Fragment能够很容易的实现与REST API的数据交互。这些方法使用URLConnection和AsyncTask在一个单独的线程内执行网络请求,然后通过回调将结果返回给Activity。

    按照同样的方式,CacheProvider 所包含的方法负责从SharedPreferences和SQLite数据库检索和存储数据。同样使用回调的方式,将结果传回Activity。

    存在的问题:

    • 使用这种结构,最主要的问题在于View Layer持有太多的职责。Activitty和Fragment变得非常庞大并且难以维护。

    MVP架构

    MVP 是如何建立起关系来的?

    首先,M 只在 P 中使用,与 V 无关,因此 M 只要传入 P 中即可。
    P 与 V 之间的关系是这样的:V 和 P 互相保存对方的实例。V 在需要进行数据操作逻辑的时候不自己做,而是交给 P 来做,P 完成之后调用 V 中的方法实现界面更新。

    所以PRESENTER 的作用是承担业务逻辑和相应的UI逻辑。 而View层几乎没有任何逻辑操作,它只是将presenter 的命令转换为UI操作,并且监听用户的操作,然后传递给Presenter 。

    可以看到这里的View指的是Fragment,一是因为Google推荐使用Fragment而不是Activity来显示内容,二是Fragment作为View既能解决layout作为View导致的鸡肋,也不会使Activity功能太过膨胀(这里Activity是一个总体的Controller,让Fragment和Presenter进行连接)
    左边那块就是Model了,Presenter先到内存中的缓存进行查询,如果没有,才到本地数据源或者远程数据源请求

    项目包结构


    除BaseView、BasePresenter两个接口,其他package都以业务功能来划分的:
    addedittask —— 添加任务
    data —— 数据源
    statistics —— 任务统计
    taskdetail —— 任务详情
    tasks —— 任务列表
    util —— 工具类

    项目MVP实现方式

    这节我们就具体来看官方示例到底是如何实现mvp的。这里我们先看下总体的轮廓,关于项目中业务代码我们仅列出了添加任务页(addedittask )的相关类,其他业务代码类似。(手绘,诸兄勿弃)

    基类

    Presenter基类:

    public interface BasePresenter {
        void start();
    }
    
    // 例子中这个start()方法都在Fragment的onResume()中调用,作用是presenter开始获取数据并调用view中方法改变界面显示。
    
    @Override
        public void onResume() {
            super.onResume();
            mPresenter.start();
        }
    
    

    View基类:

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

    BaseView中含方法setPresenter,该方法作用是在将presenter实例传入view中,其调用时机是presenter实现类的构造函数中。

    Contract 契约类

    不同于其他的MVP项目,官方的MVP架构中都定义有xxContract契约类,把P层和V层的接口统一写在契约类中,能够更清晰的看到在Presenter层和View层中有哪些功能,方便我们以后的维护。

    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();
    
            boolean isDataMissing();
        }
    }
    

    addedittask功能模块分析


    来具体看一个模块

    AddEditTaskActivity —— Activity
    AddEditTaskContract —— 在这里定义了两个子接口View和Presenter,和他们的方法。实现了BaseView、BasePresenter两个接口
    AddEditTaskFragment —— 实现AddEditTaskContract.View接口
    AddEditTaskPresenter —— 实现AddEditTaskContract.Presenter接口

    从Acitivty入手
    public class AddEditTaskActivity extends AppCompatActivity {
      protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.addtask_act);
    
            // Set up the toolbar.
            ...
            // Add Fragment  
            ...
           // Create the presenter
            mAddEditTaskPresenter = new AddEditTaskPresenter(
                    taskId,
                    Injection.provideTasksRepository(getApplicationContext()),
                    addEditTaskFragment,
                    shouldLoadDataFromRepo);
         }
    }
    

    我们可以看到一共做了三件事,初始化toolbar,添加fragment,创建一个presenter
    在创建presenter的时候,把fragment传了进去,实现了V和P的绑定
    因此Activity在项目中是一个全局的控制者,负责创建view以及presenter实例,并将二者联系起来。

    View实现

    AddEditTaskFragment 中通过实现BaseView中的setPresenter(),将Presenter和View关联起来。

    @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);
    
            //fab属于view控件,也归fragment控制
            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) {
                    //响应点击事件 分发给P层
                    mPresenter.saveTask(mTitle.getText().toString(), mDescription.getText().toString());
                }
            });
        }
    
    
    • Fragment作为每一个View接口的实现, 主要负责数据显示和在用户交互时调用Presenter, 但是例子代码中也是有一些直接操作的部分, 比如点击开启另一个Activity, 点击弹出菜单(菜单项的点击仍然是调用presenter的方法)。
    • View接口中定义的方法多为showXXX()方法。
    @Override
    public boolean isActive() {
      return isAdded();
    }
    
    • 在Presenter中数据回调的方法中, 先检查View.isActive()是否为true, 来保证对Fragment的操作安全。

    Presenter实现

    AddEditTaskPresenter是AddEditTaskContract.Presenter的实现

    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);
        }
    
    • 从Presenter的构造函数可以看出,传入了TasksRepository 和addEditTaskFragment,也就是M和V。
    • 构造中先用guava的checkNotNull() 检查是否为空, 然后赋值到字段; 之后再调用View的setPresenter()方法把Presenter传回View中引用。
    • New Presenter的操作是在每一个Activity的onCreate()里做的: 先添加了Fragment(View), 然后把它作为参数传给了Presenter. 这里并没有存Presenter的引用。
    @Override
        public void start() {
            if (!isNewTask() && mIsDataMissing) {
                populateTask();
            }
        }
    @Override
        public void populateTask() {
            if (isNewTask()) {
                throw new RuntimeException("populateTask() was called but task is new.");
            }
            mTasksRepository.getTask(mTaskId, this);
        }
    
    • Presenter的start()方法在Fragment 的onResume() 的时候调用, 这时候通过M层的mTasksRepository 新建或者修改数据; 其他方法均对应于用户在UI上的交互操作。

    Model实现细节

    该项目中Model层最大的特点是被赋予了数据获取的职责,与我们平常Model层只定义实体对象截然不同。实例中,数据的获取、存储、数据状态变化都是Model层的任务,Presenter会根据需要调用该层的数据处理逻辑并在需要时将回调传入。

    • Model是在P构造的时候传入,继承的接口是TasksDataSource,实例是TaskRepository

    TasksDataSource是一个接口. 接口中定义了Presenter查询数据的回调接口, 还有一些增删改查的方法。

    public interface TasksDataSource {
        // 加载数据的回调
        interface LoadTasksCallback {
            //成功回调
            void onTasksLoaded(List<Task> tasks);
            //失败回调
            void onDataNotAvailable();
        }
        // 获取数据的回调
        interface GetTaskCallback {
    
            void onTaskLoaded(Task task);
    
            void onDataNotAvailable();
        }
    
        void getTasks(@NonNull LoadTasksCallback callback);
        //通过id获得数据
        void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
        //保存task
        void saveTask(@NonNull Task task);
        ...
    }
    
    • Model只有一个类, 即TasksRepository, 它还是一个单例。
      它由手动实现的注入类Injection类提供:
    public class Injection {
    
        public static TasksRepository provideTasksRepository(@NonNull Context context) {
            checkNotNull(context);
            return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
                    TasksLocalDataSource.getInstance(context));
        }
    }
    

    TasksRepository的构造如下:

    private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
                            @NonNull TasksDataSource tasksLocalDataSource) {
        mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
        mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
    }
    
    • TaskRepository中首先定义了两个数据源,一个负责数据库操作mTasksLocalDataSource,另一个负责网络数据mTasksRemoteDataSoure。TaskRepository类中还有一个内存缓存的实现。

    • TasksLocalDataSource是TasksDataSource接口的实现,里面是一些对数据库的增删改查的操作。

    • 而在TasksDataSource的两个内部接口LoadTasksCallback和GetTaskCallback是Model层的回调接口。真正实现是在Presenter层。可以把成功获取到的数据传递给Presenter层,比如AddEditTaskPresenter就实现了TasksDataSource.GetTaskCallback接口。

    • AddEditTaskPresenter的start() 方法-->执行了Model的getTask() 方法。

    public void getTasks(@NonNull final LoadTasksCallback callback) {
            // 判空处理
            checkNotNull(callback);
    
            // 从内存缓存中读取数据
            if (mCachedTasks != null && !mCacheIsDirty) {
                callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
                return;
            }
    
            if (mCacheIsDirty) {
                // 如果缓存是脏数据,那么从网络获取数据
                getTasksFromRemoteDataSource(callback);
            } else {
                // 查询本地存储(如果有)。 如果没有,查询网络。
                mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
                    @Override
                    public void onTasksLoaded(List<Task> tasks) {
                        refreshCache(tasks);
                        //在P中通过回调得到M层查询到的数据
                        callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
                    }
    
                    @Override
                    public void onDataNotAvailable() {
                        getTasksFromRemoteDataSource(callback);
                    }
                });
            }
        }
    
    

    总结


    回顾整理,总结一下:

    • Activity作为管理者,负责创建Fragment(V)和Presenter(P),而Presenter(P)的创建需要用到Fragment(V)和Repository(M),Fragment(V)已经被Activity创建了。
    • Contract 作为契约类,能够更清晰的看到在Presenter层和View层中有哪些功能,方便我们以后的维护。
    • Fragment 作为V,负责的就是接收数据,更新界面。
    • Repository 作为M,负责的是对数据的处理和回调,通过依赖注入的形式创建,并且Repository(M)可以同时操作远程数据和本地数据,而且M中没有V的引用,而和P的联系则是通过callback,可以再看下官方给的图。
    • Presenter 作为P,V向它请求数据,然后P再向M请求数据,通过回调得到数据之后在调用V进行界面的更新。

    看完发现,好像还真的是一大堆的接口啊!!!

    参考文章
    http://www.jianshu.com/p/d54f82848bea
    http://blog.csdn.net/jjwwmlp456/article/details/54869179
    http://blog.csdn.net/u013400743/article/details/52205445
    http://www.jianshu.com/p/8ca27934c6e6
    http://blog.csdn.net/card361401376/article/details/51518605
    https://mp.weixin.qq.com/s?__biz=MzA4MjA0MTc4NQ==&mid=404088059&idx=3&sn=78dafacbca09b0d7345344c3eef24aff#rd
    http://www.jianshu.com/p/0bf5289ee8ce
    http://www.jianshu.com/p/389c9ae1a82c
    http://blog.csdn.net/ljd2038/article/details/51477475

    相关文章

      网友评论

      • winelx:人笨学的慢。。😂🙃

      本文标题:(新瓶旧酒)谷歌官方MVP项目学习--浅入源码

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