Android MVP 架构实践

一、前言

首先声明一下,没有完美的架构,只要适合自己的项目,那就是最好的架构。

本例子是 MVP + Retrofit + RxJava 结合的例子,但本文的重点在于讲解 MVP 架构,所以涉及 Retrofit 和 RxJava 的部分将直接略过,默认读者已了解这两部分内容,如有需要,请自行查阅相关资料,网上资料很多。

史上最全 MVP 资料合集: Android MVP 详解(上)

RxJava 学习参考资料: 是时候学习 RxJava 了

二、MVC

早期项目中,我们会使用 MVC 架构来构建我们的项目,但是 MVC 架构的缺陷很明显,V 层和 C 层的职责混淆不清,很容易就会写成万能的 Activity,把业务逻辑、View 操作等一系列功能全放到 Activity 中来实现。

三、MVP

MVP 是 MVC 的进化版,它把 Controller 的职责从 Activity/Fragment 中拆分出来,作为 Presenter,这样就实现了 Activity/Fragment 和业务逻辑的解耦,更好地解决了数据与界面的关系。

  • View 层: 对应于 Activity/Fragment,负责 View 的绘制以及与用户交互
  • Presenter 层: 负责完成 View 与 Model 间的交互
  • Model 层: 实体模型、与数据进行交互,对数据进行加工处理

1. 架构图

MVP 架构图

(上图由 ProcessOn 在线工具绘制)

2. 类图


(上图由 StarUML 绘制)

四、MVP 实践

1. 两个基类接口

首先定义两个接口,这两个接口分别是所有 View 和 Presenter 的基类: IBaseViewIBasePresenter

  • IBaseView 中主要定义一些通用的界面方法,如显示/隐藏进度条、显示提示信息等。
  • IBasePresenter 中也可以定义一些通用的方法,如初始化方法等。
1
2
3
4
5
public interface IBaseView {
void showLoading();
void hideLoading();
void showMessage(String msg);
}
1
2
3
public interface IBasePresenter {
...
}

2. 定义契约类(接口)

使用契约类来统一管理 View 与 Presenter 的所有接口,这种方式使得 View 与 Presenter 中有哪些功能,一目了然,维护起来也很方便。

1
2
3
4
5
6
7
8
9
public interface CookDetailContract {
interface IView extends IBaseView {
void updateCookDetail(CookDetail cookDetail);
}

interface IPresenter extends IBasePresenter {
void getCookDetail(String apikey, String id);
}
}
  • CookDetailContract 中的 IView 接口定义了该界面(功能)中所有的 UI 状态情况,MainAcitivty 作为 View 层,实现了该接口,这样 MainActivity 就只关注 UI 相关的状态更新。
  • IPresenter 接口则定义了该界面(功能)中所有的用户操作事件,CookDetailPresenter 作为 Presenter 层,实现了该接口,这样 CookDetailPresenter 就只关注业务层的相关逻辑,UI 的更新只需调用 IView 的状态方法。

3. View 层(Activity/Fragment)

Activity/Fragment 是一个全局的控制者,负责创建 View 以及 Presenter 实例,并将二者联系起来。

在本例中,MainActivity 实现了 CookDetailContract.IView 接口,并在 onResume() 回调中创建 CookDetailPresenter 实例,CookDetailPresenter 的构造函数中实现了 View 和 Presenter 的关联。

在创建完 Presenter 后,调用 Presenter 的 getCookDetail() 方法获取相应的数据(如上图步骤①)。

在获取到 Model 层的数据后,Presenter 通过 IView 中的 updateCookDetail() 方法返回数据(如上图步骤④),Activity 获取数据后,将结果展示到界面上反馈给用户。

1
2
3
4
5
6
7
8
mCookDetailPresenter = new CookDetailPresenter(MainActivity.this, this);
mCookDetailPresenter.getCookDetail(Config.API_KEY, (id++) + "");

@Override
public void updateCookDetail(CookDetail cookDetail) {
tvName.setText(cookDetail.getName());
Picasso.with(this).load(Config.IMAGE_URL_PREFIX + cookDetail.getImg()).into(ivImage);
}

4. Presenter 层

它实现了契约类中的 IPresenter 接口。

Presenter 翻译过来是主持人的意思,它做为 MVP 架构中最关键的一层,负责连接 View 层和 Model 层。比如控制显示/隐藏进度框、显示/隐藏空布局、错误布局,调用相应的 Model 层方法进行数据的获取,并在 Model 层返回数据后,将数据适配到 View 中展示。这样,便可以让 Model 层只关注数据相关的操作、也让 View 层只专注于界面的展示,让各个层级各司其职,相互协作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class CookDetailPresenter implements CookDetailContract.IPresenter {

private Context mContext;
private CookDetailContract.IView mView;
private CookDetailManager mCookDetailManager = CookDetailManager.getInstance();

public CookDetailPresenter(Context context, CookDetailContract.IView view) {
this.mContext = context;
this.mView = view;
}

@Override
public void getCookDetail(String apikey, String id) {
mView.showLoading();

mCookDetailManager.getCookDetail(apikey, id, new Callback<CookDetail>() {
@Override
public void onSuccess(CookDetail object) {
mView.updateCookDetail(object);
mView.hideLoading();
}

@Override
public void onFail(int errorNo, String errorMsg) {
ErrorUtil.processErrorMessage(mContext, errorNo, errorMsg, mView);
mView.hideLoading();
}
});
}
}

5. Model 层

Model 层不只包含实体对象,更主要的功能是处理一切与数据相关的操作,如数据的获取、存储、数据状态变化都是 Model 层的任务,Presenter 会根据需要调用该层的数据处理逻辑(如上图步骤②),如有需要,Model 层会使用回调将数据传回 Presenter 层(如上图步骤③)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class CookDetailManager {

private volatile static CookDetailManager instance;

private CookDetailManager() {
}

public static CookDetailManager getInstance() {
if (instance == null) {
synchronized (CookDetailManager.class) {
if (instance == null) {
instance = new CookDetailManager();
}
}
}
return instance;
}

public void getCookDetail(String apikey, String id, final Callback<CookDetail> callback) {
if (callback == null) {
return;
}

Retrofit retrofit = RetrofitClient.INSTANCE.getRetrofit();
ApiService apiService = retrofit.create(ApiService.class);

Observable<CookDetail> observable = apiService.getCookDetail(apikey, id);
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<CookDetail>() {
@Override
public void onCompleted() {
}

@Override
public void onError(Throwable e) {
callback.onFail(Constants.ErrorNo.ServerError, "");
e.printStackTrace();
}

@Override
public void onNext(CookDetail respEntity) {
callback.onSuccess(respEntity);
}
});
}
}

五、总结

使用 MVP 架构,缺点在于需要增加很多接口类、实现类,对于刚开始接口 MVP 架构的人来说,增加了不少的学习成本,看着一堆的类、一堆的接口,调来调去的,刚开始肯定会看晕。

但是当你熟悉了 MVP 架构,并掌握了它的精髓后,会发现虽然增加了很多代码,但是整体架构变得非常清晰,代码也可以多处复用。各个类和层的职责都非常明确且单一,后期的扩展,维护都会更加容易。整体的可测试性非常的好,UI 层和业务层可以分别进行单元测试。

项目代码已共享到 Github:AndroidMVPArchitecture

六、效果图

七、参考资料

Android 官方 MVP 架构项目解析

Android:“万能” Activity 重构篇-牛晓伟

Android 高仿微信之 mvp 实现(一)

RxJava 与 Retrofit 结合的最佳实践

Android MVP 详解(下)

Android MVP 实战经验

PS:欢迎关注 SherlockShi 个人博客

感谢你的支持,让我继续努力分享有用的技术和知识点!