前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android架构之路-三步实现MVP架构(上)

Android架构之路-三步实现MVP架构(上)

作者头像
开发者技术前线
发布2020-11-23 16:18:18
6260
发布2020-11-23 16:18:18
举报

本文不对MVC进行讲解,相信大家在项目中已经都用过。我直接开始介绍MVP。

1.初始MVP:

M:Model-模型:主要是实体模型,数据的存取与业务逻辑。 V:View-视图:对应Activity(或Fragment),负责View的绘制以及用户交互。 P: Presenter: 负责View与Model间交互,可以理解为媒介,就像媒婆那样的功能。

我用一张图来描述三者之间的关系。

优点:

1.降低耦合度,隐藏数据,使Activity(或Fragment)中代码更加简洁,只负责处理View的职责; 2.模块职责分工明确; 3.方便测试开发; 4.代码复用性较高。

我们都知道要学一个新东西,我们都会先看官方说明,所以我讲带领大家一起解读Google官方MVP示例。

2.解读Google官方MVP

已完成的示例有:

  • todo-mvp :mvp基础架构。
  • todo-mvp-loaders :基于todo-mvp,获取数据使用Loaders。
  • todo-mvp-databinding:基于todo-mvp,使用数据绑定组件。 仍进展中的示例有:
  • dev-todo-mvp-contentproviders:基于todo-mvp-loaders, 使用Content Providers。
  • dev-todo-mvp-clean:基于todo-mvp, 采用Clean架构的概念。
  • dev-todo-mvp-dagger:基于todo-mvp,使用Dagger2进行依赖注入。

对于采用哪种架构,取决于该项目的规模以及后期测试维护。

基于todo-mvp分析

该示例有四个界面(功能):

代码结构:按功能分包,包中又分为Activity、Fragment、Contract、Presenter四种类文件。

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

可以看到,该示例的app界面、功能代码结构,以及测试代码结构非常清晰。

源码分析

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

代码语言:javascript
复制
  1. public interface BaseView<T> {
  2. // 规定View必须要实现setPresenter方法,则View中保持对Presenter的引用。
  3. void setPresenter(T presenter);
  4. }

setPresenter的调用时机是presenter实现类的构造函数中,这样View中的事件请求通过调用presenter来实现。在这里大家特别要注意一个问题,那就是view持有presenter的强引用,处理不当,会导致内存泄漏。后面我们教大家处理这个问题。

代码语言:javascript
复制
public interface BasePresenter {
    // 规定Presenter必须要实现start方法。
    void start();
}

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

2、定义了契约类(接口) Google引入契约类,主要作用是用来统一管理view和present的接口,使得view和present中有哪些功能,一目了然,便于维护。我们将通过详情界面(功能)来分析:

  • TaskDetailContract中的View接口定义了该界面(功能)中所有的UI状态情况,TaskDetailFragment作为View层,实现了该接口,TaskDetailFragment 只关注UI相关的状态更新,所有事件操作都调用 TaskDetailPresenter 来完成。
  • Presenter 接口则定义了该界面(功能)中所有的用户操作事件,TaskDetailPresenter 作为Presenter层,实现了该接口,TaskDetailPresenter 则只关注业务层的逻辑相关,UI的更新只需调用View的状态方法。

3、Model层 它的任务是用来获取数、存储数据以及数据状态变化。我们来看TasksRepository 中的getTask() 方法

代码语言:javascript
复制
@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数据库)。

代码语言:javascript
复制
private final TasksDataSource mTasksRemoteDataSource;
 private final TasksDataSource mTasksLocalDataSource;

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

代码语言:javascript
复制
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);
}

这样一来我们就很容易扩展新的数据源(获取数据的方式)。

代码语言:javascript
复制
public static TasksRepository provideTasksRepository(@NonNull Context context) {
        checkNotNull(context);
        return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
                TasksLocalDataSource.getInstance(context));
    }

5、Presenter层

它继承上面定义的BasePresenter

代码语言:javascript
复制
@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();
            }
        });
    }

它接收到view数据请求,把该请求发送给Model,当Model请求后,把结果返回给presenter,presenter处理返回数据后,把它返回给view,最后view进行界面显示。这就是persenter的作用。

6、View层 它负责创建view视图与presenter实例,并将二者关联起来。然后presenter的方法对数据进行请求与返回。

代码语言:javascript
复制
@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);
    }

在这里我们要特别注意一个问题,因为Presenter经常需要执行一些耗时操作,例如请求网络数据。而presenter持有了Activity(或Fragment)的强引用,如果在请求结束之前Activity(或Fragment)被销毁了,那么由于网络请求还没有返回,导致presenter一直持有它们对象,对象无法被回收,此时就发生了内存泄漏。后面我将带领大家一起架构MVP的时候,解决该问题。

总结

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

用一张图来看它们的关系:

我们对Google官方示例解读完了,关键的事来了,我们要怎么架构一个属于自己的MVP。就让我手把手教大家三步架构MVP。持续关注下篇继续介绍。

参考:

http://www.jianshu.com/p/389c9ae1a82c

答谢粉丝免费送一次书籍:

哆啦投稿:https://www.jianshu.com/p/aa9fe307f915 作者:哆啦(谈恋爱的猴子) 最新粉丝互动请看:跳槽前给你的多重福利,让这个冬天不再冷。 免费小密圈请点击:免费小密圈资格邀您加入

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-02-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发者技术前线 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.初始MVP:
    • 优点:
    • 2.解读Google官方MVP
      • 基于todo-mvp分析
        • 源码分析
        • 总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档