聊聊Android开发中的MVP设计模式

MVP设计模式

Android中的设计模式很多,常用的有 MVC、MVP和MVVM,MVP是目前比较流行的一种设计模式,全称为Model-View-Presenter。从上图可以看到,MVP 设计模式解除了Model和View的耦合。这种设计的优点是有效地降低View的复杂性,避免业务逻辑被塞入View中,使得View变得更为简单专一。不仅如此,MVP 还带来了良好的可拓展性、可测试性,保证系统整洁性、灵活性。对于一个复杂的应用来说,MVP模式是一种良好的架构模式,它可以非常好地组织应用结构,使得应用变得灵活,拥抱变化。

MVP模式下,项目中会多出很多类和接口,这是为了实现Model和View的解耦,View和数据的交互通过接口来实现。虽然多出了这些类和接口,但是这样便于我们维护,使用MVP模式来写项目,可以说是面向接口编程,调用的方法基本都是抽象的,直接使用接口来调用,提高可维护性。在MVP模式中,一般我们都会将要调用的方法抽象成接口,Model、View、Presenter接口,在各实现类中,直接对接口进行操作。

MVP设计模式的好处有以下几点

  1. Activity只处理生命周期的任务,代码变得更加简洁
  2. 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性
  3. Presenter 可以复用,一个 Presenter 可以用于多个 View,不用去改 Presenter
  4. 利于单元测试。模块分明,那么我们编写单元测试就变得很方便了,而不用特别是特别搭平台,人工模拟用户操作等等耗时耗力的事情。

总之,使用了 MVP 设计模式能让代码逻辑更清晰,改动代码的时候更具目的性,而不是改动一片代码


MVP基本框架搭建

使用方法上,View 是 Activity 和 Fragment,同时 View 持有 Presenter 实例,可以调用 Presenter 实现业务逻辑。而 Presenter 中持有 Model 和 View 实例,业务逻辑就在 Presenter 中实现。Model 中做数据处理,例如操作数据库或者从网络获取数据,一般采用异步实现。

定义 Presenter 基类,这里有个问题要注意,因为 Presenter中持有 View 实例,也就是 Presenter 持有 Activity 实例,当我们在 Presenter 中调用 Model 的数据请求时,某些情况下,在我们的数据请求还没有完成时,我们就退出了这个Activitiy,但是 Model 还在进行着数据请求,这是因为 Presenter 对 View 是强引用,所以,当 Activity 结束的时候,该Activity 实例还被 Presenter 持有,此刻将出现内存泄漏问题。为此,我们要在 View 的生命周期结束时及时释放这个引用。

首先,我们来编写 Presenter 基类,在这里面要编写解除对 View 引用的方法

1
2
3
4
5
6
7
8
9
10
11
open class BasePresenter<T> {
protected var mView: T? = null

fun attachView(view: T) {
mView = view
}

fun detachView() {
mView = null
}
}

编写 View 基类,在 View 的生命周期结束的时候,要解除 Presenter 对 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
abstract class BaseMvpActivity<V, T : BasePresenter<V>> : AppCompatActivity() {
protected lateinit var mPresenter: T

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getContentId())
processLogic()
}

open fun initWidget() {}

open fun processLogic() {
mPresenter = createPresenter()
mPresenter.attachView(this as V)
}

// 生命周期结束时释放 Presenter 对 View 的引用
override fun onDestroy() {
super.onDestroy()
mPresenter.detachView()
}

protected abstract fun getContentId(): Int
protected abstract fun createPresenter(): T
}

Model 基类与 Presenter 的交互通过回调实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface IModel {

interface OnCompletedListener {
/**
* 成功时的回调
*/
fun onSuccess()

/**
* 失败时的回调
*/
fun onFail(msg: String)
}
}

MVP 简单例子

这是一个简单的例子,仅仅是作为如何使用 MVP ,实际项目中的使用应该根据需求做定制或者使用成熟的开发框架。

实现效果:

通过点击注册按钮,可以向数据库中插入用户注册信息,点击查询按钮,可以显示用户数据。

准备工作:

Project Detail Value
Application name MVPDemo
Company domain Use your website name
Kotlin support Yes
Form factor Phone and Tablet only
Minimum SDK API 24 Nougat
Type of activity Empty
Activity name MainActivity
Layout name activity_main
Backward compatibility Yes.AppCompat

目录结构:

1、编写契约类UserContract,规定 ViewPresenter 接口

1
2
3
4
5
6
7
8
9
10
interface UserContract {
interface View {
fun showDialog(msg: String)
}

interface Presenter {
fun load()
fun register(username: String, password: String)
}
}

2、Model层实现,为了简单,使用 List 存储数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class UserModel : IModel {

fun load(listener: IModel.OnCompletedListener<List<User>>) {
try {
listener.onSuccess(UserDao.getAllUser())
} catch (e: Exception) {
listener.onFail("${e.stackTrace}")
}
}

fun register(username: String, password: String,
listener: IModel.OnCompletedListener<*>) {
try {
UserDao.saveUser(User(username, password))
listener.onSuccess()
} catch (e: Exception) {
listener.onFail("${e.stackTrace}")
}

}
}

3、View层实现,实现类是 MainActivity,初始化点击事件还有实现契约类中的 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class MainActivity : BaseMvpActivity<UserContract.View, UserPresenter>(),
UserContract.View {
private var mUsername: EditText? = null
private var mPassword: EditText? = null

private var mRegisterBtn: Button? = null
private var mLoadBtn: Button? = null

override fun getContentId(): Int {
return R.layout.activity_main
}

override fun createPresenter(): UserPresenter {
return UserPresenter()
}

override fun initWidget() {
super.initWidget()
mUsername = username_et
mPassword = password_et

mRegisterBtn = bn_register
mLoadBtn = bn_query
}

override fun processLogic() {
super.processLogic()

mRegisterBtn?.setOnClickListener {
mPresenter.register(mUsername?.text.toString(),
mPassword?.text.toString())

}

mLoadBtn?.setOnClickListener {
mPresenter.load()
}
}


override fun showDialog(msg: String) {
AlertDialog.Builder(this)
.setTitle("Result")
.setMessage(msg)
.setPositiveButton("确定", null)
.setNegativeButton("取消", null)
.create()
.show()
}
}

4、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
39
class UserPresenter : BasePresenter<UserContract.View>(),
UserContract.Presenter {
private val model = UserModel()

override fun load() {
model.load(object : IModel.OnCompletedListener<List<User>> {
override fun onSuccess(data: List<User>) {
var result = ""
data.forEach {
result += "${it.username} : ${it.password}\n"
}
mView?.showDialog(result)
}

override fun onSuccess() {}

override fun onFail(msg: String) {
mView?.showDialog("获取数据失败")
}
})
}

override fun register(username: String, password: String) {

model.register(username, password, object : IModel.OnCompletedListener<Any> {
override fun onSuccess(data: Any) {
}

override fun onSuccess() {
mView?.showDialog("Save Data Success")
}

override fun onFail(msg: String) {
mView?.showDialog("Save Data Failed")
}
})
}

}

最后

大概讲讲这个实现逻辑,添加新用户信息时,在MainActivity中调用 UserPresenterregister 接口,然后实现过程就在 UserPresenter 中的 register 方法里(通过传入的用户名和密码构建一个 User 对象,然后调用 UserModel 中的 register 方法完成数据的存储,最后通过回调 OnCompletedListener 告诉 UserPresenter 存储是否成功)来实现添加用户。 显示用户数据的过程也是类似的。

这里用一个极简单的例子展示了MVP设计模式的使用,通过它,我们可以简单地理解MVP设计模式的使用。实际项目中还是要灵活运用,具体情况具体分析,例如可以结合 RxKolin 使我们的回调更加优雅。

文章作者: Sshpark
文章链接: http://sshpark.com.cn/2019/04/06/聊聊Android开发中的MVP设计模式/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Sshpark