我的 MVP 帝国发展史

接触 MVP 已经很久了,但我觉得我还没有真正掌握它。它总是在不经意间给我带来意想不到的。。惊,没有喜。最早接触 MVP 是在哪里早就已经忘记了 ,只是依稀还记得对 MVP 的理解与使用经过了好几个时期。

I 黑暗时代

这是我最早接触 MVP 的时候,应该还没有毕业,只是在自己的毕设里尝试运用,没有 Contract 类,Presenter 没有继承,只有 View 与 Model 有继承,毕竟还是知道依赖于接口,而不依赖于实现的嘛。

举一个例子吧:

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 InboxPresenter {
private InboxViewInterface mInboxView;
private InboxModel mInboxModel;

public InboxPresenter(InboxViewInterface mInboxView) {
this.mInboxView = mInboxView;
mInboxModel = new InboxModelDBImpl();
}

public void fetchInboxs() {

}

public Long insertInbox(InboxEvent inbox) {
return mInboxModel.insertInbox(inbox);
}

public void updateInboxInTx(List<InboxEvent> inboxEvents) {
mInboxModel.updateInboxInTx(inboxEvents);
}

public void deleteInbox(InboxEvent inbox) {
mInboxModel.deleteInbox(inbox);
}

public void deletedInboxInTx(List<InboxEvent> inboxEvents) {
mInboxModel.deleteInboxInTx(inboxEvents);
}

public void loadInboxFromDB() {
mInboxModel.loadInboxByCreateTime(new DataListener<List<InboxEvent>>() {
@Override
public void onComplete(List<InboxEvent> result) {
mInboxView.showInbox(result);
}
});
}

public void loadDeletedInboxFromDB() {
mInboxModel.loadDeletedInboxByDeletedTime(new DataListener<List<InboxEvent>>() {
@Override
public void onComplete(List<InboxEvent> result) {
mInboxView.showDeletedInbox(result);
}
});
}
}

看得出来那个时候还是很粗糙的。也没有抽取 BaseXXX 之类的。

II 封建时代

这段时期我开始稍微思考下,开始使用契约类 Contract ,也尝试抽取 BaseView, BasePresenter 等,但不是有句话叫做

我当然不是上帝,不过看到自己这段时期的代码,也不禁笑了。

BaseView :

1
2
3
4
5
6
7
public interface BaseView<T extends BasePresenter> {
@UiThread
void setPresenter(T presenter);

@UiThread
void showLoading(boolean isLoading);
}

BasePresenter :

1
2
3
4
5
6
public interface BasePresenter<T extends BaseView> {

void attachView(T view);

void detachView();
}

CitiesContract :

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
public interface CityContract {
interface View extends BaseView<Presenter> {
void setCities(List<City> cities);

void setSearchCities(List<City> cities);

void setLocCity(City city);

void setCurrentCity(City currentCity);
}

interface Presenter extends BasePresenter<View> {
void requestChangeCity(String cityId);

void requestCities();

void requestCitiesWithPara(String para);
}

interface Model {
void changeCity(String cityId, DataListener<City> listener);

void getCities(DataListener<List<City>> listener);

void getcities(String para, DataListener<List<City>> listener);
}
}

然后看一个 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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class CityPresenter implements CityContract.Presenter {

private CityContract.View mView;
private final CityContract.Model mModel;
private final MyTaskPool taskPool;

public CityPresenter(CityContract.View mView) {
this.mView = mView;
mView.setPresenter(this);
mModel = new CityModelImpl();
taskPool = MyTaskPool.getInstance();
}

@Override
public void attachView(CityContract.View view) {
mView = view;
}

@Override
public void detachView() {
mView = null;
}

@Override
public void requestChangeCity(String cityId) {
mModel.changeCity(cityId, new DataListener<City>() {
@Override
public void onCompleted(City result) {
mView.setCurrentCity(result);
}
});
}

@Override
public void requestCities() {
taskPool.execute(new Runnable() {
@Override
public void run() {
mModel.getCities(new DataListener<List<City>>() {
@Override
public void onCompleted(List<City> result) {
mView.setCities(result);
mView.setLocCity(new City("", "上海", "S"));
mView.setCurrentCity(new City("", "上海", "S"));
}
});
}
});
}

@Override
public void requestCitiesWithPara(final String para) {
taskPool.execute(new Runnable() {
@Override
public void run() {
mModel.getcities(para, new DataListener<List<City>>() {
@Override
public void onCompleted(List<City> result) {
mView.setSearchCities(result);
}
});
}
});
}
}

这其中最可笑的就是,上一次时期我还有点卖弄的接口依赖,这一时期就不完整了,Model 竟然是在构造方法中直接初始化的,我也不知道既然这样为什么契约类里我还要定义 Model 接口。

一脸懵逼

而且 BaseView 里的泛型也让我很迷惑,不知道这里为什么要限定一个 P 的实现,setPresenter 方法我也不知道是用来干嘛的,这些代码真的是我写的吗。。太可怕了

III 城堡时代

这个时期就是我毕业后的早期了,对于 MVP 算是有了一些比较深入的了解吧,开发语言也逐渐转向了 Kotlin 。不过话虽如此,事实上也还是换汤不换药,Java 变成了 Kotlin,然而 MVP 的实现却好像更差了。这段时间里,我碰到了一个 P 需要对应多个 V 的情况,当时也没有多想,直接给 P 增加了一个获取单例的方法,却没想到带来了更大的问题。

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
class LoginPresenter : LoginContract.Presenter {

override lateinit var mView: LoginContract.View

private val model: LoginContract.Model = LoginModel()

lateinit var user: String

companion object {
val instance: LoginPresenter = LoginPresenter()
}


override fun start() {}

override fun checkNumCorrect(number: String): Boolean {
val regex = "^1(3[0-9]|4[57]|5[0-35-9]|8[0-9]|7[0-9])\\\\d{8}\$"
val pattern = Pattern.compile(regex)
val matcher = pattern.matcher(number)
//return matcher.matches()todo
return number.startsWith("1") && number.length == 11
}

override fun hasRegistered(num: String) = TaskUtils.execute(Runnable {
mView.onRefresh(true)
TaskUtils.execute(Runnable {
model.hasPhoneRegistered(num, object : DataListener<AuthPhoneJson?> {
override fun onComplete(result: AuthPhoneJson?) {
mView.onRefresh(false)
if (result != null) {
mView.setUserInfo(result)
if (result.set_password) {
mView.next(BaseLoginActivity.Where.PASSWORD)
} else {
mView.next(BaseLoginActivity.Where.CODE)
}
}
}
})
})
})

override fun getSmsCode(num: String, action: String) = TaskUtils.execute(Runnable {
model.getSmsCode(num, action, object : DataListener<Int> {
override fun onComplete(result: Int) {
mView.getCodeFinish(result, "")
}
})
})

override fun setPassword(number: String, password: String, code: String, action: String) {
TaskUtils.execute(Runnable {
if (checkPassword(password)) {
model.setPassword(number, code, action, password, object : DataListener<Any?> {
override fun onComplete(result: Any?) {
if (result != null && result is LoginResult) {
mView.setPasswordFinish(true, "")
mView.next(BaseLoginActivity.Where.HOME)
} else {
mView.setPasswordFinish(false, result.toString())
}
}
})
} else {
mView.setPasswordFinish(false, "43006")
}
})
}

fun checkPassword(password: String): Boolean {

return password !in arrayListOf("123456", "654321", "111111", "222222", "333333", "444444",
"555555", "666666", "777777", "888888", "999999", "000000")
}

override fun verifyCode(number: String, code: String) = TaskUtils.execute(Runnable {
mView.verifyCodeFinish(200, "")
mView.next(BaseLoginActivity.Where.SET_PASSWORD)
})

override fun login(phone: String, password: String) = TaskUtils.execute(Runnable {
model.login(phone, password, object : DataListener<Any?> {
override fun onComplete(result: Any?) {
if (result != null && result is LoginResult) {
Prefs.put("user_id", result.user_id as String)
Prefs.put("access_token", result.access_token as String)
mView.loginFinish(true, "")
mView.next(BaseLoginActivity.Where.HOME)
} else {
mView.loginFinish(false, "")
}
}
})
})

override fun losePwd() = mView.next(BaseLoginActivity.Where.CODE)

override fun getContact() {
TaskUtils.execute(Runnable {
model.getContact(object : DataListener<Any?> {
override fun onComplete(result: Any?) {
if (result != null) {
mView.setContact(result)
}
}
})
})
}
}

一 P 对多 V 的情况其实是比较复杂的,页面的跳转需要修改 P 所持有的 View ,什么时候需要修改 view 的具体指代,需要认真思考下,不过也只能在 View 的生命周期发生变化的时候修改:

1
2
3
4
5
6
7
8
9
10
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter = LoginPresenter.instance
presenter.mView = this
}

override fun onResume() {
super.onResume()
presenter.mView = this
}

为什么没有在 view 销毁的时候置空呢?

1
2
3
4
5
6

interface Presenter : BasePresenter {
var mView: View

...
}

因为 Kotlin 的关系,这样的写法就是不可空,所以是没办法写 presenter.mView=null 的,只是,当初我为什么没有把 mView 定义成 View? 呢?

很明显,上述代码在这一个完整的 MVP 流程结束后,因为最后一个 V 仍然被 P 所引用而无法释放,会造成内存泄漏。然而在一个很长的时间内,我都没有意识到。

不仅如此,在一个 MVP 内,会有需要同时刷新多个 V 的时候,即时 V 处于未激活状态,那么这个时候一个单例的 P 该怎么来应对呢。我曾尝试过这样的做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private val mViewSet = LinkedHashSet<MineContract.View>()

override fun attachView(view: MineContract.View) {
mViewSet.add(view)
mRootView = view
}

override fun detachView(view: MineContract.View) {
if (mViewSet.contains(view)) {
mViewSet.remove(view)
if (mViewSet.isNotEmpty()) {
mRootView = mViewSet.elementAt(mViewSet.size - 1)
} else {
super.detachView(view)
}
}
}

保存所有处于存活状态的 View ,当一个 View 被销毁后,当前 View 就是上一个绑定的 View ,逻辑上来讲是没有问题的。但是对于同一个 Presenter 来说,要应对这么多 View 的请求,也有点应付不来,如果多个 View 调用了同一个方法,那么到底该把数据给谁呢?尤其是根据数据层的响应,需要刷新 UI 的时候,难道要遍历所有保存在 mViewList 的 View 来刷新 UI 吗?如果接口返回异常呢,那就全部显示接口异常的 UI 吗?这可能是一个比较难以决定的问题。

所以啊,我感觉这段时间才是 MVP 最黑暗的时候啊。

IV 帝王时代

黑暗过后,便是黎明了。认识到自己的问题,才能更好的解决问题。

或许回归初心,就是最好的结果了。当然肯定不是回到过去,而且带着一开始同样的心态,继续努力。

到了这一时期,几乎所有的问题都已浮出水面,就待解决了。而且还有了新的可能:一 V 对多 P,考验功力的时候到了…

首先解决单例 Presenter 无法同时应对多个 View 的问题:其实很简单,不要让 P 成为单例即可。虽然我们只写了一个 P 的实现,但并不代表多个 View 就要用同一个 P 对象啊,我们尽可以给每个 View 都初始化一个 P ,前提是 P 中不要有缓存数据的功能,如果想要缓存数据为什么不缓存在 Model 里呢,我觉得多个 P 使用一个 Model 完全没有问题啊。这也正是前面黑暗时代里的 MVP ,回归初心嘛。

同时这样也解决了 mViewList 最后一个 View 无法释放的问题,毕竟根本就不需要 mViewList 了。因为现在是一个 V 对应一个 P,V 被销毁的时候,关联的 P 对象便可以同时销毁,也就不会出现内存泄漏的问题了。

当然不要忘记把 mView 改成 View? 类型。

看一下现在的 MVP UML 图:

MVP

最后,期待一下后帝王时代吧。