一、MVP介绍
- Model: 数据层,负责与网络层和数据库层的逻辑交互。
- View: UI层,显示数据, 并向Presenter报告用户行为。
- Presenter: 从Model拿数据,应用到UI层,管理UI的状态,响应用户的行为。
MVP是Model, View和Presenter的简称。是非常有名的MVC模式的演化版。MVP模式把显示逻辑和从业务逻辑层中分离出来,理想状况下,MVP模式中,在替换不同的视图(View)的情况下,可以实现完全相同的业务逻辑。
MVP与MVC区别
- Presenter代替了MVC中Controller,它比Controller担当更多的任务,也更加复杂。Presenter处理事件,执行相应的逻辑,这些逻辑映射到Model的Command以操作Model。那些处理UI如何工作的代码基本上都位于Presenter。Presenter如同一个乐队的指挥家,表现和协调整个Application,它负责创建和协调其它对象。
- 在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller。
MVP优势
-
分离了视图逻辑和业务逻辑,降低了耦合。
-
Activity只处理生命周期的任务,代码变得更加简洁。
-
视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中,提高代码的阅读性。
-
Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试。
-
把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM。
二、 基于todo-mvp分析
该示例有四个界面功能:任务列表、任务详情、任务添加编辑、任务统计
1.项目结构:
从上图可以看出,todo-mvp是按照功能模块划分的。
其中tasks, taskdetail, addedittask, statistics是四个业务模块。
data是数据模块,其中具体的类TasksRepository
担任Model层,负责远程和本地数据的获取。
BasePresenter
和BaseView
是presenter 和 view 的基类,在具体模块承担实际功能。最后,util是工具类集合。
2. 源码分析
1. 基类
首先看下两个Base接口的基类:BaseView与BasePresenter,分别是所有View与Presenter的基类
// 接受一个泛型参数,泛型类为Presenter的具体实现类
public interface BaseView<T> {
// View必须实现setPresenter方法,View持有对Presenter的引用
void setPresenter(T presenter);
}
setPresenter的调用时机在Presenter具体实现的构造方法中,这样View与Presenter关联起来了。
public interface BasePresenter {
// 规定Presenter必须要实现start方法
void start();
}
该start方法的作用:Presenter开始获取数据并调用View的方法刷新UI,调用时机是在Activity(或者Fragment)的onResume方法。
2. 定义契约类(合同)
Google引用契约类,主要作用是用来统一管理View和Presenter接口,使得View和Presenter中有哪些功能,一目了然,便于维护。下面通过任何列表界面来分析:
public interface TasksContract {
interface View extends BaseView<Presenter> {
void showTasks(List<Task> tasks);
// ... 省略其它的UI操作
}
interface Presenter extends BasePresenter {
void loadTasks(boolean forceUpdate);
// ... 省略其它的业务操作
}
}
- TasksContract(代表契约、合同)中的View接口定义了该界面中所有UI操作;TasksFragment作为View层,实现了该接口(也就是View接口),TasksFragment只需要关心UI相关的操作,所有的事件操作都通过TasksPresenter完成
- Presenter接口定义了该界面中所有操作事件;TasksPresenter作为Presenter层,实现了该接口(也就是Presenter接口),TasksPresenter只关心业务相关的逻辑,UI状态更新通过View层的方法
3. Model层
它的作用主要用来获取数据、存取数据、数据状态更新等。先看下TasksRepository的getTasks方法
// 远程端数据源
private final TasksDataSource mTasksRemoteDataSource;
// 本地SQLite数据源
private final TasksDataSource mTasksLocalDataSource;
@Override
public void getTasks(@NonNull final LoadTasksCallback callback) {
checkNotNull(callback); // 判空处理,提前发现异常的方式
// 如果内存中有缓存数据并且不是脏数据,立即返回
if (mCachedTasks != null && !mCacheIsDirty) {
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
return;
}
if (mCacheIsDirty) {
// 如果缓存中是脏数据,需要从网络获取新的数据
getTasksFromRemoteDataSource(callback);
} else {
// 如果本地SQLite中缓存数据可用,返回;否则从网络获取新的数据
mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
refreshCache(tasks); // 更新内存中缓存数据
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values())); // 成功回调
}
@Override
public void onDataNotAvailable() {
// 回调该方法,说明缓存中的数据不可用,从网络获取新的数据
getTasksFromRemoteDataSource(callback);
}
});
}
}
TasksRepository中维护着两个数据源:mTasksRemoteDataSource(从网络中获取的数据源)和 mTasksLocalDataSource(从本地SQLite获取的数据源)
这两个数据源都实现了TasksDataSource接口,其中TasksRepository也实现了该接口
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);
}
4. Presenter层
TasksPresenter实现TasksContract.Presenter接口,重写BasePresenter的start()方法。当收到View层的数据请求后,Presenter层控制Model层进行业务逻辑处理,Model层处理完毕后,把数据返回给Presnerer层,然后通知View层进行UI更新。
// TasksPresenter.java
@Override
public void start() {
loadTasks(false);
}
private void loadTasks(boolean forceUpdate, final boolean showLoadingUI) {
if (showLoadingUI) {
mTasksView.setLoadingIndicator(true);
}
if (forceUpdate) {
mTasksRepository.refreshTasks();
}
mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
List<Task> tasksToShow = new ArrayList<Task>();
// We filter the tasks based on the requestType
for (Task task : tasks) {
switch (mCurrentFiltering) {
case ALL_TASKS:
tasksToShow.add(task);
break;
case ACTIVE_TASKS:
if (task.isActive()) {
tasksToShow.add(task);
}
break;
case COMPLETED_TASKS:
if (task.isCompleted()) {
tasksToShow.add(task);
}
break;
default:
tasksToShow.add(task);
break;
}
}
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive()) {
return;
}
if (showLoadingUI) {
mTasksView.setLoadingIndicator(false);
}
processTasks(tasksToShow);
}
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive()) {
return;
}
mTasksView.showLoadingTasksError();
}
});
}
5. View层
负责View视图和Presenter的创建,并将二者关联起来;View层持有Presenter后,通过它进行一系列的操作
// TasksActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tasks_act);
// ...
// 创建Presenter对象,将当前的View传给Presenter层
mTasksPresenter = new TasksPresenter(
Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
}
// TasksPresenter.java
// TasksPresenter的构造方法
public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");
// View与Presenter建立关联
mTasksView.setPresenter(this);
}
注意事项:
1.View只负责简单的视图更新、界面跳转的代码,只负责初始化自身,根据需要,给Presenter指派任务,然后进入“瞌睡”状态
因为相比起来,如果是Presenter太臃肿,可以根据功能不同,轻易拆分成两个甚至更多的Presenter。但是如果Activity/Fragment(View)太臃肿的话,可能就不好拆分了。
2.View和Presenter的交互,是用接口来实现的。
Activity/Fragment实现XxxContract.View接口,Presenter实现XxxContract .Presenter接口。
3.Presenter异步任务回来后,通知View更新UI之前,要先判断Activity是否为null,或者Fragment是否已经从activity中移除。
因为Presenter的生命周期,通常与Activity/Fragment是不相同的。所以Presenter在执行异步操作后,在结束的时候,都要判断View是否还处于正确的状态,在执行同步操作时不需要进行这种判断。
4.Model,作为数据源,要确保它的可复用性。
Model不应该持有Presenter的引用。
5.如果要在Presenter和Model里面使用Context,应该使用Application的Context。
尽量隔离、减少使用Android类的Presenter、Model层,会更便于我们写相关单元测试,Application的Context足够应付Presenter和Model里面的需求了。
6.Presenter、Model的构造方法,要使用依赖注入
VersionModel versionModel = new
VersionModel(getApplicationContext());
mPresenter = new SplashPresenter(versionModel);
7.关于Presenter、Model的划分
Model是“数据处理”,Presenter是“根据业务逻辑,对Model获得的结果进一步处理”。Presenter、Model同样都会进行数据处理
其实MVP三层的关系可以说是层层递进的关系
三、总结
通过对todo-mvp分析,再次了解学习了MVP。从google提供的例子中可以看出,MVP的实现较为简单,model、view和presenter各个职责明确,便于扩展维护。contract契约类的出现,使得model和presenter结构更加清晰明了。Activity和Fragment的配合,使得Activity职能更为简化,同时View的实现更加灵活。