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

已授权开发者技术前线原创发布。
这里我就不对MVC进行讲解,相信大家在项目中已经都用过。我就直接开始介绍MVP。

1.初始MVP:

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

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

1-1

优点:

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

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

2.解读Google官方MVP

已完成的示例有:

仍进展中的示例有:

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

基于todo-mvp分析

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


2-1 代办事项(列表&详情)界面

2-2 代办事项(新建&统计)界面

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


2-3 代码结构

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


2-4 测试代码结构

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

源码分析

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

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

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

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

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

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

/**
 * 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、Model层
它的任务是用来获取数、存储数据以及数据状态变化。我们来看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);
}

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

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

5、Presenter层
它继承上面定义的BasePresenter

@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的方法对数据进行请求与返回。

@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维护着它自己的数据源和实现。用一张图来看它们的关系:
2-5 关系图

我们对Google官方示例解读完了,关键的事来了,我们要怎么架构一个属于自己的MVP。就让我手把手教大家三步架构MVP。Android架构之路--三步实现MVP架构(基础篇-下)

参考:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容