Android官方MVP架构项目解析

作者: spiritTalk | 来源:发表于2016-04-23 20:38 被阅读24257次

    前段时间Google在Github开源了一个项目Android Architecture Blueprints,在项目中展示了使用不同的实现架构和工具来实现相同的app。

    先来看项目说明:

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

    目前已经完成的示例有:

    仍在进展中的示例有:

    这对于一直困惑于到底该用何种架构的android开发者来说是好事,开发者只要根据自己项目的情况,在不同的实现中进行选择(app规模、团队状况、维护工作量的大小、平板是否支持、代码简洁程度偏好等等,这些都会影响你的选择),重点是代码结构,整体架构、可测试性和可维护性。

    ** 本文是基于todo-mvp项目的分析。**

    再来看App结构:

    app主要包括以下四个界面(功能):


    代办事项(列表&详情)界面 代办事项(新建&统计)界面

    分别对应着代码的包结构,也就是说src目录的代码组织方式是按照功能来组织的,包中又分为Activity、Fragment、Contract、Presenter四种类文件。

    androidTest(UI层测试)、androidTestMock(UI层测试mock数据支持)、test(业务层单元测试)、mock(业务层单元测试mock数据支持):

    总的来说,app界面、功能代码结构以及测试代码结构非常清晰。

    源码解析

    1、首先来看两个Base接口类,BaseView与BasePresenter,两类分别是所有View和Presenter的基类。

    public interface BaseView<T> {
        // 规定View必须要实现setPresenter方法,则View中保持对Presenter的引用。
        void setPresenter(T presenter);
    }
    

    setPresenter的调用时机是presenter实现类的构造函数中,如此View中的事件请求便通过调用presenter来实现。

    public interface BasePresenter {
        // 规定Presenter必须要实现start方法。
        void start();
    }
    

    该方法的作用是Presenter开始获取数据并调用View的方法来刷新界面,其调用时机是在Fragment类的onResume方法中。

    2、定义了契约类(接口)。
    使用契约类来统一管理view与presenter的所有的接口,这种方式使得view与presenter中有哪些功能,一目了然,维护起来也很方便。以下通过详情界面(功能)来分析:

    /**
     * This specifies the contract between the view and the presenter.
     */
    public interface TaskDetailContract {
    
        interface View extends BaseView<Presenter> {
            // 设置数据加载状态
            void setLoadingIndicator(boolean active);
            // 处理task加载失败的情况
            void showMissingTask();
            // 隐藏待办事项title
            void hideTitle();
            // 显示待办事项title
            void showTitle(String title);
            // 隐藏待办事项的描述
            void hideDescription();
            // 显示待办事项的描述
            void showDescription(String description);
            ……
        }
    
        interface Presenter extends BasePresenter {
            // 修改待办事项
            void editTask();
            // 删除待办事项
            void deleteTask();
            // 标记完成 
            void completeTask();
            // 标记未完成
            void activateTask();
        }
    }
    
    • TaskDetailContract中的View接口定义了该界面(功能)中所有的UI状态情况,TaskDetailFragment作为View层,实现了该接口,如此 TaskDetailFragment 只关注UI相关的状态更新,所有事件操作都调用 TaskDetailPresenter 来完成。
    • Presenter 接口则定义了该界面(功能)中所有的用户操作事件,TaskDetailPresenter 作为Presenter层,实现了该接口,如此 TaskDetailPresenter 则只关注业务层的逻辑相关,UI的更新只需调用View的状态方法。

    3、Activity在mvp中的作用。
    Activity在项目中是一个全局的控制者,负责创建view以及presenter实例,并将二者联系起来。TaskDetailActivity 的onCreate()回调中创建TaskDetailPresenter 实例,TaskDetailPresenter 的构造函数中实现了View和Presenter的关联。

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ……
            // Create the presenter
            new TaskDetailPresenter(
                    taskId,
                    Injection.provideTasksRepository(getApplicationContext()),
                    taskDetailFragment);
        }
    
        public TaskDetailPresenter(@Nullable String taskId,
                                   @NonNull TasksRepository tasksRepository,
                                   @NonNull TaskDetailContract.View taskDetailView) {
            this.mTaskId = taskId;
            mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
            // 保持对View(TaskDetailFragment)的引用
            mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");
    
            // 使View(TaskDetailFragment)也保持对自身(TaskDetailPresenter)的引用
            mTaskDetailView.setPresenter(this);
        }
    

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

    我们来看TaskDetailPresenter 的 start() 方法:

        @Override
        public void start() {
            openTask();
        }
    
        private void openTask() {
            // 判空处理
            if (null == mTaskId || mTaskId.isEmpty()) {
                mTaskDetailView.showMissingTask();
                return;
            }
            // 更新状态
            mTaskDetailView.setLoadingIndicator(true);
            // 获取该条Task数据
            mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
                @Override
                public void onTaskLoaded(Task task) {
                    // The view may not be able to handle UI updates anymore
                    // View已经被用户回退
                    if (!mTaskDetailView.isActive()) {
                        return;
                    }
                    // 获取到task数据,并更新UI
                    mTaskDetailView.setLoadingIndicator(false);
                    if (null == task) {
                        mTaskDetailView.showMissingTask();
                    } else {
                        showTask(task);
                    }
                }
    
                @Override
                public void onDataNotAvailable() {
                    // The view may not be able to handle UI updates anymore
                    // 显示数据获取失败时的状态
                    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) {
                    // 成功,则回调
                    callback.onTaskLoaded(task);
                }
    
                @Override
                public void onDataNotAvailable() {
                    // 失败,则从远程数据源(网络)中获取
                    mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
                        @Override
                        public void onTaskLoaded(Task task) {
                            // 回调成功时的方法
                            callback.onTaskLoaded(task);
                        }
    
                        @Override
                        public void onDataNotAvailable() {
                            // 回调失败时的方法
                            callback.onDataNotAvailable();
                        }
                    });
                }
            });
        }
    

    我们发现 TasksRepository 维护了两个数据源,一个是本地(SQLite数据库),一个是远程(网络服务器)。

        private final TasksDataSource mTasksRemoteDataSource;
    
        private final TasksDataSource mTasksLocalDataSource;
    

    我们发现他们(包括TasksRepository类)都实现了 TasksDataSource 接口:

    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);
    
        void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
    
        void saveTask(@NonNull Task task);
    
        void completeTask(@NonNull Task task);
    
        void completeTask(@NonNull String taskId);
    
        void activateTask(@NonNull Task task);
    
        void activateTask(@NonNull String taskId);
    
        void clearCompletedTasks();
    
        void refreshTasks();
    
        void deleteAllTasks();
    
        void deleteTask(@NonNull String taskId);
    }
    

    这样一来我们就很容易扩展新的数据源(获取数据的方式),毕竟我们在TaskDetailActivity中初始化TasksRepository就是调用的如下方法,其实我们很容易把FakeTasksRemoteDataSource替换为TasksRemoteDataSource,把TasksLocalDataSource 替换为TasksContentProviderDataSource,这就是针对接口编程的好处吧。

        public static TasksRepository provideTasksRepository(@NonNull Context context) {
            checkNotNull(context);
            return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
                    TasksLocalDataSource.getInstance(context));
        }
    
    总结

    最后,我们再来看这张图。Fragment作为View,View和Presenter通过Activity来进行关联,Presenter对数据的调用是通过TasksRepository来完成的,而TasksRepository维护着它自己的数据源和实现。

    OK,通过以上的分析,我们可以看到:

    • 工程的整体架构和代码结构非常清晰(不再是所有的业务和逻辑都糅合在Activity、Fragment里了),易于理解和上手。
    • 由于将UI代码与业务代码进行了拆分,整体的可测试性非常的好,UI层和业务层可以分别进行单元测试。
    • 由于架构的引入,虽然代码量有了一定的上升,但是由于界限非常清晰,各个类和层的职责都非常明确且单一,后期的扩展,维护都会更加容易。

    但以上毕竟是架构的Sample,是为了说明架构思想,因此有些地方我们在实际运用中需要注意:数据库和网络的操作都应该放在工作线程,用户回退后需要取消网络请求、回调接口置为null等。


    转载请标明出处:http://www.jianshu.com/p/389c9ae1a82c

    相关文章

      网友评论

      • 奉强:View和Presenter相互引用不会内存泄露吗?
      • f8906f57703f:很强 看懂了 谢谢
      • c84a6998a6c6:想问一下,下载下来怎么能把项目运行起来,不会修改gradle
      • Alien的小窝:你这篇文章写的真TNND太好了!不点赞手都是痒的!
      • code农:写的真的很详细。现在让我有了一点对mvp的理解,和思路,这篇文章绝对算是MVP引路章。感谢作者。
      • 4b2d37f9514b:分析的很好,我自己也是看了一遍,但是没能捋的这么清晰,赞作者。看完之后,切实的体会到了mvp的妙处。不过也确实增加了不少代码。
      • e9a7130b03c1:看了你的讲解,犹如醍醐灌顶,瞬间恍然大悟.原来是这样分模块的.
      • Knight_Davion:对于一些功能简单的界面我觉得可以抛弃fragment,直接把activity当做View,在activity中初始化presenter并关联。
        spiritTalk:@shiwei_bsw 嗯嗯,activity也可以抽象为view。
      • 大鹏的鹏:言简意赅.相当好的一篇文章,学习了
        spiritTalk:@大鹏的鹏 谢谢支持。
      • 97e8afe1ba71:写的很详细,是我看过最好的官方demo讲解! :+1:
        spiritTalk:@路人乙去流浪 谢谢支持。
      • fef3a2360375:挺不错的,感谢分享。
        大家可以参考下下面项目dagger2+mvp的框架,希望支持下
        https://github.com/CarlLu/MVPframe
      • 196f6e72efd5:跑了下,挺好的,但是感觉有bug,android 5.0以上本地的数据库好像没保存嘛
        c84a6998a6c6:想问一下,怎么能让项目跑起来,请教一下怎么改gradle
      • 060a04b87948:分析的很到位,赞一个
      • sing_song:言简意赅,很好.
      • KingJA:感谢博主分享。架构的意义还是重在思想领悟,而不是API层面。博主抽取了重点,直奔主题,赞。
      • 程序员Anthony:我在前两天也发了一篇分析谷歌架构的文章。总感觉讲不清楚。看了你的文章,清晰多了,学习了:smile:
      • 一杯茶一本书:你好,想知道最后的这个图是用什么工具画的 ,谢谢。
        轲叶:@一杯茶一本书 这是官方github上的图
      • 聪聪的豆芽:Model呢??、
        Yet_land:DataRespository就是Model啊
        spiritTalk:@聪聪的豆芽 建议你把源码pull下来,以上文字可以辅助你阅读、思考。
      • Chenstyle:先马克一下,看代码的话手机很难看啊
      • Jerry_vs:我竟浑然不觉😂
      • wphper:如果一个activity里有viewPager,里面有3-5个fragment,怎么做比较好呢?是要把每个fragment都实现view接口,再分别定义一个Presenter吗?
        spiritTalk:Fragment是作为View的一个单元,当然要实现View接口,Presenter是可以对应多个View的,具体可以根据实际情况进行封装和重用。
        小feng子:@wphper 每个Fragment都是单独的业务处理单元,你为每个Fragment建立一个单独的Presenter是应该的
        晓峰残月:@wphper 是的呢,我的理解是每个Fragment都相当于view

      本文标题:Android官方MVP架构项目解析

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