欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

浅谈Android中的架构模式——MVP(二)

程序员文章站 2024-02-25 14:55:33
...

  接上一篇博客讲到,MVC框架的耦合性使得Model层和View层仍然存在关联性,虽说分离了业务代码,但是这种耦合性的存在会随着业务的扩增导致维护的成本很高。那么为了实现解耦,也就是彻底把View层和Model层之间的这条关联线扯开,我们不妨试用一下MVP这个框架。

MVP框架

 浅谈Android中的架构模式——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进行更新。

  这里为了便于对比,同样拿登录模块来做例子进行分析。

  1. 用户在输入用户名和密码以及点击登录的操作这一步是属于View层的工作模块。
  2. 接而在点击登录后,会调用Presenter层的方法。
  3. Presenter层接收到信息后会调用Model层的业务代码。
  4. Model层接收到指令后,完成任务,并通过接口回调给外部,Model层完成其工作。
  5. Presenter层在Model层的接口回调时,会通知View来进行更新,Presenter层完成了其工作。
  6. View层在接口回调时,会对UI进行更新,View层完成了其工作。

  说了那么多,接下来看看代码如何实现吧!

  浅谈Android中的架构模式——MVP(二)

  工程代码的话,可以对比一下前一篇博客,这里主要讲下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即可。

浅谈Android中的架构模式——MVP(二)