Android MVP 实战经验
一、表现层模式架构的演变
三层架构通常是指表现层(Presentation Layer)、业务逻辑层(Business Layer)和数据访问层(Data Access Layer)。表现层是用户和系统之间交流的桥梁,它一方面提供与用户交互的界面,另一方面也提供了与数据交互的逻辑,便于协调用户与系统的操作。我们所谈论的 MVC、MVP、MVVM 等设计模式都属于表现层的设计模式。
1. MVC(Model-View-Controller)模式
MVC如果把 MVC 模式套用在 Android 中,那么:
- View 对应 xml 布局,实现数据的展示
- Controller 对应 Activity / Fragment ,处理业务逻辑
- Model 对应数据源,包括网络接口数据、数据库、缓存等
使用 MVC 模式看似分工明确,但是应用到 Android 中,会带不少的问题:
- Activity / Fragment 实现了多重职责,即是 View,又是 Controller,导致代码复杂臃肿,难以复用
- 把 Activity / Fragment 作为 Controller,无法对 Controller 进行单元测试
所以,在 Android 中,MVC 模式不太适用。
2. MVP(Model-View-Presenter) 模式
MVP 模式是 MVC 的进化版,它把 Controller 的职责从 Activity/Fragment 中拆分出来,作为 Presenter,这样就实现了 Activity/Fragment 和业务逻辑的解耦,更好地解决了数据与界面的关系。
二、细说 MVP
1. 职责划分
MVP 各自的职责分别是:
-
View
- 对应 Activity / Fragment / Custom View
- 与用户交互,响应用户操作,分派事件行为给 Presenter 处理
- 响应 Presenter 回调,对数据进行显示
-
Presenter
- 是连接 VIew 和其它代码的胶水
- 用于转换 Model 的数据以便于 VIew 显示
- Presenter 不做 UI 相关处理,也不包含上下文对象(Context)
-
Model
- 与数据进行交互,对数据进行加工处理
- 通常是与 Android 无关的,不会用到 Android SDK
- 关注从哪里拿数据(Retrofit、Sqlite etc.)
2. 架构实现
MVP谷歌官方已经给出了一个 MVP 架构的实践示例[googlesamples/android-architecture](上面图中的 REPOSITORIES 就是指 Model 层)。
接下来,我们以它为例来看一下具体的实现。我们简化一下代码,更直观地看一下它们之间的关系。
首先官方的例子定义了一个契约(Contract)类:
public interface TasksContract {
interface View extends BaseView<Presenter> {
void setLoadingIndicator(boolean active);
void showTasks(List<Task> tasks);''
void showLoadingTasksError();
// ....
}
interface Presenter extends BasePresenter {
void loadTasks();
void addNewTask();
// ....
}
}
契约类就是把 MVP 所需要定义的几个接口都写在一个类里面。在项目实现中,我会把 Model 接口也写到契约类里,定义契约类的好处除了可以少写几个 Java 文件外,也比较直观。比如先在 Presenter 定义一个 loadTasks 方法,那么相应地,Model 就接口需要定义一个 getTasks 方法来为 Presenter 提供数据,View 接口则需要定义一个 showTasks 来显示获取到 Tasks 数据,以及 setLoadingIndicator、showLoadingTasksError 等方法来更新 UI 状态。
- Model 层的实现代码:
public class TasksRepository implements TasksDataSource {
@Inject
TasksRepository(@Remote TasksDataSource tasksRemoteDataSource,
@Local TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = tasksRemoteDataSource;
mTasksLocalDataSource = tasksLocalDataSource;
}
@Override
public Observable<List<Task>> getTasks() {
// 从缓存或者网络接口等数据源获取数据
}
}
Model 层的代码维护了两个数据源(mTasksRemoteDataSource 和 mTasksLocalDataSource),用来为 Presenter 提供数据,Presenter 无需关心从哪里拿数据。代码中的注解
@Inject
标记了该构造方法可以被注入,如果你使用了 Google 的 Dagger 框架,Dagger 可以提供 TasksRepository 所需的依赖,并创建一个 TasksRepository 对象。
- Presenter 层的实现代码:
public class TasksPresenter implements TasksContract.Presenter {
@Inject
TasksPresenter(TasksRepository tasksRepository, TasksContract.View tasksView) {
mTasksRepository = tasksRepository;
mTasksView = tasksView;
}
@Override
public void loadTasks() {
mTasksRepository.getTasks()
.observeOn(mSchedulerProvider.ui())
.subscribe(new Observer<List<Task>>() {
@Override
public void onCompleted() {
mTasksView.setLoadingIndicator(false);
}
@Override
public void onError(Throwable e) {
mTasksView.showLoadingTasksError();
}
@Override
public void onNext(List<Task> tasks) {
mTasksView.showTasks(tasks);
}
});
}
}
这里我们使用了 Dagger 和 RxJava(官方例子是分开两个独立的分支),Dagger 注入所需的依赖,并创建 TasksPresenter 对象。View 通过调用 Presenter 的 loadTasks 方法来获取便于 VIew 展示的数据。Presenter 就是用于响应 View 分派的事件,校验数据并提交给 Model 处理,最后把 Model 处理的结果转交给 View。
另外,Presenter 也承担了一部分 Activity / Fragment 的业务逻辑,这样也减轻了 Activity / Fragment 作为 View 的负担。
- View 层的实现代码
public class TasksFragment extends Fragment implements TasksContract.View {
private TasksContract.Presenter mPresenter;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
swipeRefreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh() {
mPresenter.loadTasks();
}
});
}
@Override
public void showLoadingTasksError() {
showMessage(getString(R.string.loading_tasks_error));
}
@Override
public void showTasks(List<Task> tasks) {
mListAdapter.replaceData(tasks);
mTasksView.setVisibility(View.VISIBLE);
mNoTasksView.setVisibility(View.GONE);
}
}
View 就比较简单了,纯粹做 UI 相关的处理,不关注业务处理。它将用户的行为传递给 Presenter,同时接收 Presenter 的调用来更新界面。在上面的代码中,当 TasksFragment 初始化之后,会调用 swipeRefreshLayout.setRefreshing 来触发 Presenter 的 loadTasks() 方法。之后,当 Presenter 处理完后,调用 View 的 showTasks 或者 showLoadingTasksError 方法,TasksFragment 只需要关注界面更新的具体实现。
从上面的例子来看,Model-View-Presenter 三种角色之间分工明确,使得数据与界面之间的耦合更低,代码复用性更高,也更方便于测试。
三、总结
使用 MVP 模式来开发 Android 应用给我们带来了很多好处,只是需要多定义 M-V-P 这三个接口(可以写在一个 Contract 类中),正是因为这些接口,才使得类的组织结构更加清晰,每一层实现对应的接口,只关注其本身单一的职责。这样层与层的耦合底非常低,可维护性提高。
一路走来,我们也在不断地尝试,持续地改进现有的架构。我们结合了目前流行的框架来提高生产力;比如,使用 Dagger 来为 Presenter 注入依赖(不需要再用new
关键字创建各种对象),还使用了 Retrofit、RxJava、Realm 等框架更好地去组织数据层的接口,以及使用 DataBinding 来简化 UI 界面与数据实体的绑定。
架构的作用是为解决痛点,适合自己的才是最好的。希望本文对你找到适合自己项目的架构组织方式有所帮助。
// 能力一般,水平有限。文中有不妥或谬误之处在所难免,请大家批评指正。