本文不对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的基类。
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中有哪些功能,一目了然,便于维护。我们将通过详情界面(功能)来分析:
-
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维护着它自己的数据源和实现。
用一张图来看它们的关系:
我们对Google官方示例解读完了,关键的事来了,我们要怎么架构一个属于自己的MVP。就让我手把手教大家三步架构MVP。持续关注下篇继续介绍。
参考:
http://www.jianshu.com/p/389c9ae1a82c
答谢粉丝免费送一次书籍:
相关推荐哆啦投稿:https://www.jianshu.com/p/aa9fe307f915
作者:哆啦(谈恋爱的猴子)
最新粉丝互动请看:跳槽前给你的多重福利,让这个冬天不再冷。
免费小密圈请点击:免费小密圈资格邀您加入。