迟到的MVP

去年第一次使用mvp做了个小应用,也是干货集中营的安卓客户端,Material的风格,MVP的架构,也使用了rxjava和retrofit,不过这期间没有去做过关于mvp的这样的总结。

一些基本的对比

android上最为让我们熟知的三个框架就是MVC,MVP,MVVM。当然也有一些其他的框架和思想。初学时接触的基本就是MVC了,而在之后也出现了MVP和MVVM,他们的目的最主要的都是为了解耦

  1. MVC:由model层进行数据业务的处理,View层进行ui的展示,中间使用Controler作为两者交互的桥梁。
  2. MVP:依旧是model层的数据业务,Activity变为纯粹的View的ui展示,中间由Presenter进行逻辑处理与沟通。
  3. MVVM:与MVP类似,但其中间交互作用的从Presenter变为了ViewModel,并且ViewModel与View之间是使用DataBinding进行绑定的。

MVP实例

上面都是一些概念性的东西,当然还是需要动手练习一下才能真正理解。
MVP对于一个Activity或是Fragment都被解耦成3个部分,原本MVC集中在一个activity的繁杂代码,由Presenter,Contract,Activity3个部分组成,Presenter进行当前页面上逻辑的处理,Activity进行当前页面上ui的处理,Contract作为一个契约类对两方沟通构建方法。
3方分工明确,这样的代码易于维护,但是类文件数量确实也是增加了不少。

创建一个demo

在包中创建一个BasePresenter接口:

1
2
3
4
public interface BasePresenter {
void subscribe();
void unSubscribe();
}

以及BaseView接口:

1
2
3
public interface BaseView<T> {
void setPresenter(T 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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.xblydxj.mvpdemo.LoginActivity">

<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:layout_marginTop="100dp"
android:hint="@string/username"/>

<EditText
android:id="@+id/password"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:layout_marginTop="20dp"
android:hint="@string/password"/>

<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:layout_marginTop="20dp"
android:text="@string/login"/>

<Button
android:id="@+id/register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:text="@string/register"/>
</LinearLayout>

契约类的实现

首先确定几个逻辑和事件:

  • 事件1:点击登录时产生逻辑,需要判断账号与密码是否输入,然后做出相应ui变动。
  • 事件2:点击注册页面产生跳转逻辑。

简单的分割就是,关于ui的变动:

  • 登录成功提示与页面跳转(跳转省略)
  • 登录失败的提示
  • 点击注册的提示与跳转(跳转省略)

而关于逻辑的部分有:

  • 账号输入框内容的判断
  • 密码输入框内容的判断
  • 跳转逻辑(省略)

这些部分的定义便是属于Contract的功能,逻辑的内部实现属于Presenter,ui的内部实现属于View层,分工明确,解耦,并条例清晰,易于寻找。

LoginContract类:

1
2
3
4
5
6
7
8
9
10
11
public class LoginContract {
public interface View extends BaseView<Presenter>{
void loginSucceed(String succeed);
void loginFailed(String failed);
void register(String register);
}

public interface Presenter extends BasePresenter {
void valid(String username, String password);
}
}

View部分的实现

view部分即是activity的实现,首先需要继承Contract类中的View接口实现其关于ui的方法:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class LoginActivity extends AppCompatActivity implements LoginContract.View {
@Bind(R.id.username)
EditText username;
@Bind(R.id.password)
EditText password;
private LoginContract.Presenter mPresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
ButterKnife.bind(this);
mPresenter = new LoginPresenter(this);
}

@Override
public void setPresenter(LoginContract.Presenter presenter) {
mPresenter = presenter;
}

@Override
public void onResume() {
super.onResume();
mPresenter.subscribe();
}

@Override
public void onPause() {
super.onPause();
mPresenter.unSubscribe();
}

@Override
public void loginSucceed(String succeed) {
Toast.makeText(this, succeed, Toast.LENGTH_SHORT).show();
//跳转省略
}

@Override
public void loginFailed(String failed) {
Toast.makeText(this, failed, Toast.LENGTH_SHORT).show();
}

@Override
public void register(String register) {
Toast.makeText(this, register, Toast.LENGTH_SHORT).show();
//跳转省略
}

@OnClick({R.id.login, R.id.register})
public void onClick(View view) {
Log.d("ddd", "view");
switch (view.getId()) {
case R.id.login:
Log.d("ddd", "view2");
mPresenter.valid(username.getText().toString().trim(), password.getText().toString().trim());
break;
case R.id.register:
Log.d("ddd", "view3");
mPresenter.toRegister();
break;
}
}
}

实现契约中View接口的几个方法,为了方便使用了butterknife,首先是创建其对应的Presenter对象,使用setPresenter方法双向订阅,完成两方的链接,使其能够互相调用,而最初的BasePresenter中有两个订阅与解除订阅的方法,与activity生命周期进行绑定,在适当时候订阅,适当时候解除订阅清理内存。
页面上有2个按钮,在view中进行点击事件的实现,期间对应事件调用presenter中对应的逻辑方法,将逻辑的部分完全抛给presenter进行实现。

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
public class LoginPresenter implements LoginContract.Presenter{

private LoginContract.View loginView;

public LoginPresenter(LoginContract.View view) {
loginView = view;
loginView.setPresenter(this);
}

@Override
public void subscribe() {
//订阅
}

@Override
public void unSubscribe() {
//解除订阅
}

@Override
public void valid(String username, String password) {
if (TextUtils.isEmpty(username)) {
loginView.loginFailed("账号不能为空");
return;
}
if (TextUtils.isEmpty(password)) {
loginView.loginFailed("密码不能为空");
return;
}
loginView.loginSucceed("登录成功!");
}

@Override
public void toRegister() {
loginView.register("跳转至注册~");
//跳转省略
}
}

相对应的presenter中首先是其构造方法,将对应的view传入以便presenter中逻辑完成后调用对应的ui变动的方法。
依旧是先进行实现契约类中属于presenter的部分,然后实现其两个逻辑方法,以及BasePresenter中的两个订阅与解除订阅的方法,这两个方法,订阅中将一些初始化的方法在其内部实现,解除订阅中进行清理内存,清理一些需要清理的动作。


简单的MVP的实例就是以上的步骤,其中一些复杂页面也是由此进行引申,而首先MVP在coding时几个要点记住,写的也是会很快的:

  • ui部分与逻辑部分的完全分离
  • 中间连接契约类的定义,其中进行最先需要的方法的定义,实现交由view与presenter来做。是第一个需要写的类
  • 双向绑定的过程,presenter中先进行构造方法的定义,再由view层创建其对象进行调用,而presenter中也能够传入一个view的对象进行调用。
  • 剩下的就是清晰的逻辑实现