已授权开发者技术前线原创发布。
这里我就不对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中有哪些功能,一目了然,便于维护。我们将通过详情界面(功能)来分析:
/**
* 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维护着它自己的数据源和实现。用一张图来看它们的关系:我们对Google官方示例解读完了,关键的事来了,我们要怎么架构一个属于自己的MVP。就让我手把手教大家三步架构MVP。Android架构之路--三步实现MVP架构(基础篇-下)
参考: