接上一篇博客讲到,MVC框架的耦合性使得Model层和View层仍然存在关联性,虽说分离了业务代码,但是这种耦合性的存在会随着业务的扩增导致维护的成本很高。那么为了实现解耦,也就是彻底把View层和Model层之间的这条关联线扯开,我们不妨试用一下MVP这个框架。
MVP框架
这里可以和MVC框架的图对比一下,发现Model层和View层之间的连线是断开了,取而代之的是Model层业务在执行完任务后会告知Presenter,从而通过Presenter来控制View。也就是说,Model层并不会管View的后续操作,他只负责处理完业务,然后通知Presenter即完成了他的工作;至于View该如何变化(交互),那就交给Presenter来控制。所以,这里我的理解是Presenter充当的是一个中介的角色,负责两边协调。
分析完流程,再来看看Android工程中与框架的对应关系。
Model层:与MVC架构类似,是独立的业务模块,比如联网、访问数据库或者一些特定的业务操作;一般会有对应生成的接口供外部实现。
View层:这里的View层指代的是Activity/Fragment,因为原本调用的业务都全部移至Presenter层去处理了,使得Activity/Fragment操作的都是布局内的View,职责变得更有针对性了。
Presenter层:独立的业务逻辑代码,一般一个业务对应一个特定的Presenter,从而便于管理与维护;负责调用业务模块,同时持有View层的接口用来通知View进行更新。
这里为了便于对比,同样拿登录模块来做例子进行分析。
- 用户在输入用户名和密码以及点击登录的操作这一步是属于View层的工作模块。
- 接而在点击登录后,会调用Presenter层的方法。
- Presenter层接收到信息后会调用Model层的业务代码。
- Model层接收到指令后,完成任务,并通过接口回调给外部,Model层完成其工作。
- Presenter层在Model层的接口回调时,会通知View来进行更新,Presenter层完成了其工作。
- View层在接口回调时,会对UI进行更新,View层完成了其工作。
说了那么多,接下来看看代码如何实现吧!
工程代码的话,可以对比一下前一篇博客,这里主要讲下View层的实现以及Presenter层的实现。
首先我们要看下这个ILoginView的View层接口,这里定义了一系列关于LoginView的操作接口,从而方便Presenter层去调用。
interface ILoginView {
fun showLoading()
fun hideLoading()
fun onLoginSuccess(msg: String?)
fun onLoginError(errMsg: String?)
fun getLoginRequestBean(): LoginRequestBean
}
然后自然地,View层的Activity/Fragment要去实现这里的接口方法。
class MVPFragment : Fragment(), ILoginView {
companion object {
private const val TAG = "MVPFragment"
}
private lateinit var mRootView: View
private lateinit var mEtUsername: EditText
private lateinit var mEtPwd: EditText
private lateinit var mBtnLogin: Button
private lateinit var mProgressBar: ProgressBar
private lateinit var mFragmentTag: TextView
private lateinit var mLoginPresenter: LoginPresenter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mRootView = inflater.inflate(R.layout.layout_login, container, false)
initViews()
return mRootView
}
private fun initViews() {
mEtUsername = mRootView.findViewById(R.id.et_username)
mEtPwd = mRootView.findViewById(R.id.et_pwd)
mBtnLogin = mRootView.findViewById(R.id.btn_login)
mProgressBar = mRootView.findViewById(R.id.progress_bar)
mFragmentTag = mRootView.findViewById(R.id.fragment_tag)
mFragmentTag.text = TAG
mLoginPresenter = LoginPresenter(this)
mBtnLogin.setOnClickListener {
mLoginPresenter.doLogin()
}
}
override fun showLoading() {
activity?.runOnUiThread {
mProgressBar.visibility = View.VISIBLE
}
}
override fun hideLoading() {
activity?.runOnUiThread {
mProgressBar.visibility = View.GONE
}
}
override fun onLoginSuccess(msg: String?) {
activity?.runOnUiThread {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
}
}
override fun onLoginError(errMsg: String?) {
activity?.runOnUiThread {
Toast.makeText(context, errMsg, Toast.LENGTH_SHORT).show()
}
}
override fun getLoginRequestBean(): LoginRequestBean {
return LoginRequestBean(mEtUsername.text.toString(), mEtPwd.text.toString())
}
}
接下来看下Presenter的实现。Presenter主要关键还是持有这个IView,在业务回调后去回调给View层对应的方法来更新UI,从而实现解耦的目的。
class LoginPresenter(private val mIView: ILoginView) {
private lateinit var mLoginModel: LoginModel
fun doLogin() {
if (!paramChecked(mIView.getLoginRequestBean().username, mIView.getLoginRequestBean().pwd)) {
mIView.onLoginError("请先填写完整")
return
}
mIView.showLoading()
mLoginModel = LoginModel()
mLoginModel.login(mIView.getLoginRequestBean().username!!, mIView.getLoginRequestBean().pwd!!, object : NetCallback {
override fun onSuccess(res: LoginResponseBean) {
mIView.hideLoading()
mIView.onLoginSuccess(res.msg)
}
override fun onError(errMsg: String?) {
mIView.hideLoading()
mIView.onLoginError(errMsg)
}
})
}
private fun paramChecked(username: String?, pwd: String?): Boolean {
return !(username.isNullOrEmpty() || pwd.isNullOrEmpty())
}
}
对比MVC工程,MVP工程的特点就是将Model层和View层的联系断开了,改而通过Presenter这个中介来完成通信,从而实现了解耦的目的,便于整体工程的维护。缺点其实也比较容易想到,就是每个业务都对应一个Presenter、View等,那么类的定义就会非常非常多。
那么,到这里的话,登陆模块的MVP“非标准”架构工程就实现了~
之所以是非标准,因为Google官方的demo,是需要一个Contract的接口类对各业务模块的Presenter以及View接口进行管理;官方的考虑应该是为了便于业务进行一一对应,以免由于Presenter以及View的接口过多而导致维护成本变得巨大;同时,还会抽出一个BasePresenter(进行业务控制,如统一的联网操作接口由Base定义,具体Presenter实现)以及BaseView(进行一般的UI控制,如加载/取消加载等通用的操作接口的定义,由具体View实现)来供业务的Presenter/View进行继承。
这里主要还是为了方便与之前的MVC工程对比,具体的标准工程可以参考Google官方的MVP架构工程,项目地址为:https://github.com/googlesamples/android-architecture. 把branch切换为todo-mvp即可。