以下内容为原创,欢迎转载,转载请注明
来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5036289.html

经历过的客户端的架构分为这么几个阶段:

第一阶段

使用传统的MVC,其中的View,对应的是各种Layout布局文件,但是这些布局文件中并不像Web端那样强大,能做的事情非常有限;Controller对应的是Activity,而Activity中却又具有操作UI的功能,我们在实际的项目中也会有很多UI操作在这一层,也做了很多View中应该做的事情,当然Controller中也包含Controller应该做的事情,比如各种事件的派发回调,而且在一层中我们会根据事件再去调用Model层操作数据,所以这种MVC的方式在实际项目中,Activity所在的Controller是非常重的,各层次之间的耦合情况也比较严重,不方便单元测试。

第二阶段

使用MVC的进化版——MVP,MVP中把Layout布局和Activity作为View层,增加了Presenter,Presenter层与Model层进行业务的交互,完成后再与View层交互(也就是Activity)进行回调来刷新UI。这样一来,所有业务逻辑的工作都交给了Presenter中进行,使得View层与Model层的耦合度降低,Activity中的工作也进行了简化。但是在实际项目中,随着逻辑的复杂度越来越大,Activity臃肿的缺点仍然体现出来了,因为Activity中还是充满了大量与View层无关的代码,比如各种事件的处理派发,就如MVC中的那样View层和Controller代码耦合在一起无法自拔。

第三阶段

也是现在正在使用的架构,针对第二阶段进行优化,为了把View再次简化,想到两种方式:

  1. 通过使用一个Presenter代理的方式,在PresenterProxy中处理各种事件机制,View中维护一个PresenterProxy对象当然Presenter中同样实现了真实对象Presnter所实现的接口,这样,我们同样在View中通过代理对象调用真实对象的代码,结构图如下:

  2. 为MVP增加一层专门用于处理各种的事件派发Controller层,Controller的作用仅仅是处理事件并根据事件通过维护的Presenter对象派发到对应的业务中,也就是说View层只有一个Controller的对象,View层不会主动去调用Presenter层,但是Controller层和Presenter都可能会回调到View层来刷新UI,所以层次结构就变成了如下:

现在使用的是第2种方式,使用Controller来进行对Activity中事件代码的分离,下面使用登录的例子来讲解,其中代码使用的并不是Java,而是Kotlin。

在演示之前,先来看下实现MVP的几个基础的接口和类(点这里查看AKBMVPExt.kt):

/**
 * MVP的View层,UI相关,Activity需要实现该interface
 * 它会包含一个Presenter的引用,当有事件发生(比如按钮点击后),会调用Presenter层的方法
 */
public interface KViewer {//    val onClickListener: ((view: View) -> Unit)?val context: Context?;fun toast(message: String, duration: Int = Toast.LENGTH_SHORT) {context?.lets { Toast.makeText(this, message, duration).show() }}fun dialog(title: String? = null,message: String?,okText: String? = "OK",cancelText: String? = null,positiveClickListener: ((DialogInterface, Int) -> Unit)? = null,negativeClickListener: ((DialogInterface, Int) -> Unit)? = null) {context?.lets {AlertDialog.Builder(this).setTitle(title).setMessage(message).setPositiveButton(okText, positiveClickListener).setNegativeButton(cancelText, negativeClickListener).show()}}fun showLoading(message: String) {Log.w(KViewer::class.java.simpleName, "loadingDialog should impl by subclass")}fun cancelLoading() {Log.w(KViewer::class.java.simpleName, "cancelLoadingDialog should impl by subclass")}fun <T : View> findView(resId: Int): T;}

所有View层的Activity、Fragment或者View都要实现KViewer接口,该接口中有一个属性和一个函数需要被子类的Activity实现:

  • context属性:该属性需要被子类override,该属性用于一些接口公用的UI相关操作的方法,如toastdialogcancelDialog等。

  • fun <T : View> findView(resId: Int): T函数:该函数需要被子类Activity、Fragment或者View实现,这个方法用于从当前View层中根据id获取到对应的View,该方法在Activity、Fragment或者View中并不一致。

当然所有的重写都可以在BaseActivity、BaseFragment、BaseFrameLayout等中重写,之后使用它们的子类即可。

/**
 * MVP的Presenter层,作为沟通View和Model的桥梁,它从Model层检索数据后,返回给View层,它也可以决定与View层的交互操作。
 * 它包含一个View层的引用和一个Model层的引用
 */
public open class KPresenter<V : KViewer>(var viewer: V) {open public fun closeAll() {Log.w(KViewer::class.java.simpleName, "closeAll in KPresenter should impl by subclass")}}

KPresenter类是作为所有Presenter层的实现的基类的,它只有一个closeAll函数需要被重写,当Activity在被destory时,需要调用close函数停止到子线程的任务。

/**
 * Controller,用于派发View中的事件,它在根据不同的事件调用Presenter
 */
public abstract class KController<KV : KViewer, KP : KPresenter<*>>(val viewer: KV, presenterCreate: () -> KP) {protected val presenter: KP by lazy { presenterCreate() }private val viewCache: SparseArray<View> = SparseArray();/**
     * 注册事件
     */abstract fun bindEvents()public fun <T : View> getView(resId: Int): T {val view: View? = viewCache.get(resId)return view as T? ?: viewer.findView<T>(resId).apply {viewCache.put(resId, this)}}public fun closeAll() = presenter.closeAll()
}

同样KController是所有Controller类的基类,需要子类实现bindEvents()函数,在这个函数中,可以绑定各种View的事件。还提供了getView()方法来从Viewer中获取到对应的控件,并且会缓存找到的控件。

一、创建BaseActivity并实现KViewer

open class BaseActivity : AppCompatActivity(), KViewer {override fun <T : View> findView(resId: Int): T = findViewById(resId) as Toverride val context: Context = thisopen val controller: KController<*, *>? = nullprivate val loadingDialog: ProgressDialog by lazy { ProgressDialog(this) }override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 强制竖屏requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT}override fun showLoading(message: String) {loadingDialog.setMessage(message)loadingDialog.show()}override fun cancelLoading() {if (loadingDialog.isShowing) {loadingDialog.cancel()}}override fun onDestroy() {controller?.closeAll()super.onDestroy()}
}

这里我重写了KViewer中的findView()函数,函数实现是通过Activity::findViewById()的方式。

又实现了controller属性,设置为null,这个controller还需要子类再来重写。

然后重写了showLoadingcancelLoading,在onDestory中通过controller调用presenter中的closeAll函数。

二、实现LoginActivity

新建LoginViewer接口,继承KViewer,并定义各种逻辑回调:

interface LoginViewer : KViewer {fun onLogin()
}

里面所有的函数应该都是名字onXXX的函数,都是需要去操作UI的,这里定义的是一个onLogin()函数,表示登录成功后,我们现在是如果登录成功后,则跳转到主界面MainActivity

然后创建LoginActivity,实现我们的LoginViewer

class LoginActivity : BaseActivity(), LoginViewer {override val controller: LoginController by lazy { LoginController(this) }override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_login)controller.bindEvents()}override fun onLogin() {toActivity<MainActivity> { }finish()}
}

这里我们首先重写父类中的controller属性,通过lazy懒初始化LoginController,然后在onCreate中调用controllerbindEvents(),这样,我们在controller中的bindEvents()函数中就可以对各种View进行事件的绑定,甚至包括自定义的Dialog、PopupWindow等组件的回调。

然后实现onLogin函数,在这个函数中进行界面的跳转。

三、实现LoginController

创建LoginController,继承KController

class LoginController(viewer: LoginViewer) : KController<LoginViewer, LoginPresenter>(viewer, { LoginPresenter(viewer) }),View.OnClickListener {override fun bindEvents() {getView<Button>(R.id.activity_login_submit_btn).setOnClickListener(this)}override fun onClick(v: View?) {when (v?.id) {R.id.activity_login_submit_btn -> presenter.login(getView<EditText>(R.id.activity_login_username_et).text.toString(), getView<EditText>(R.id.activity_login_password_et).text.toString())}}}

实现KController中的bindEvents函数,在bindEvents中我们通过KControllergetView()函数获取到Id为R.id.activity_login_submit_btn的按钮,然后设置OnClickListener,在onClick回调方法中,Controller会根据事件派发到Presenter来进行真正的登录操作。

四、实现Presenter

创建Presenter,继承KPresenter

class LoginPresenter(viewer: LoginViewer) : KPresenter<LoginViewer>(viewer) {fun login(username: String, password: String) {viewer.showLoading(_resString(R.string.xr_hint_logging_in))HttpsUrl(HttpWebApi.System.LOGIN).rxRequest {it.posts("username" to username,"password" to password)}.map {_gson._fromJson<LoginHttpResponse>(it.body().string()) }.observeOnMain().doOnNextOrError { viewer.cancelLoading() }.subscribe ({if (it.success) {viewer.onLogin()} else {showHint(it.msg)}}) {Log.e("Login", "", it)viewer.toast(_resString(R.string.xr_error_default))}.bindPresenter(this)}
}

编写login()函数,然后执行登录请求,登录成功后,通过viewer回调到View层的onLogin()函数。

如此一来,View层中只负责UI部分的工作,UI所产生的各种事件绑定、派发等职责放在Controller中,PresenterModel还是与之前一样的职责。

关于Presenter的测试,只需mock一个LoginViewer实现类即可。

第四阶段:

MVVM,把Presenter改成ViewModel,它与View之间的交互可以使用Data Binding的方式双向进行,也就是说ViewViewModel任意一方的改变都会体现在另一方中,Google IO上提供的框架暂时还不成熟,只支持单向,所以暂时还没有在正式的项目中使用。

实质上MV*的思想都是一样的,解耦隔离视图(View)和模型(Model),在实际的应用中不需要给MVCMVPMVVM一个明确的界限,甚至可以把几者融合在一起。

[Android]对MVC和MVP的总结相关推荐

  1. android mvc mvp 区别,谈谈Android框架 MVC、MVP、MVVM的区别

    今天写写Android的MVC.MVP.MVVP三个框架的对比,并加深自己对这三个框架的理解. 548b9bea8dc18.gif 一 . MVC:Model-View-Controller MVC全 ...

  2. android中MVC,MVP和MVVM三种模式详解析

    我们都知道,Android本身就采用了MVC模式,model层数据源层我们就不说了,至于view层即通过xml来体现,而 controller层的角色一般是由activity来担当的.虽然我们项目用到 ...

  3. Android中MVC、MVP、MVVM具体解释

    前言 今天有时间就刚好有想写关于这几个名词.对于我来说.事实上这么多名词.思想归根究竟就是要依据项目实际.人员配置来做合理优化,既不能纸上谈兵.又不能畏惧不前.那么合理分阶段架构和完好代码才是关键,本 ...

  4. 手写Android中MVC、MVP、MVVM对比

    1. MVC.MVP.MVVM 1.1 MVC Model 模型层: 业务模型的数据与行为=数据+业务逻辑 View 展示层: 管理用户界面=组合模式的View集合 Controller: Model ...

  5. Android架构 - MVC、MVP、MVVM、MVI

    一.概念 MVC Activity类过于臃肿 MVP Presenter不仅要操作数据还要更新View MVVM 二.MVC(Model-View-Controller) 三.MVP(Model-Vi ...

  6. 【Android】Android安卓架构MVC、MVP、MVVM之间的区别和联系(图解+案例+源码)

    https://github.com/SETANDGET/AndroidArchitectureDemo 代码 一.问题背景 二.Android安卓架构MVC.MVP.MVVM 1.MVC(Model ...

  7. [最全]Android安卓架构MVC、MVP、MVVM之间的区别和联系(图解+案例+源码)

    一.问题背景 博主最近在准备春招面试中介绍自己简历中Android项目的MVP架构,但是博主发现若自身不彻底弄懂Android安卓架构MVC.MVP.MVVM之间的区别和联系,博主将无法准确地向面试官 ...

  8. Android App的架构设计:从VM、MVC、MVP到MVVM

    随着Android应用开发规模的扩大,客户端业务逻辑也越来越复杂,已然不是简单的数据展示了.如同后端开发遇到瓶颈时采用的组件拆分思想,客户端也需要进行架构设计,拆分视图和数据,解除模块之间的耦合,提高 ...

  9. Android 老生常谈之MVC与MVP

    设计模式系列 创建型设计模式 Java 设计模式之单例模式 Java 设计模式之静态工厂方法模式 Java 设计模式之工厂方法模式 Java 设计模式之抽象工厂模式 Java 设计模式之Builder ...

最新文章

  1. 全球及中国沼气发电行业现状及项目发展动态调研报告2021年版
  2. 经典论文复现 | ICML 2017大热论文:Wasserstein GAN
  3. Google Hacking的用法
  4. C语言六边形蜂巢数组,android 六边形蜂巢布局控件
  5. 《Easy RL:强化学习教程》出版了!文末送书
  6. 使用PyCharm运行第一行python代码
  7. linux 挂载raid_linux初学者-磁盘阵列篇
  8. LunarCrush将比特币批评家Peter Schiff列为第二大比特币影响者
  9. span标签的取值与赋值
  10. Android编程之仿微信显示更多文字的View
  11. oracle 中的角色
  12. mysql教程泰牛程序员_mysql高级教程笔记.docx
  13. 兄弟j220怎么清零_兄弟Brother全系列打印机清零大全
  14. 反向延长线段什么意思_反向延长线是什么意思
  15. 路由器映射,端口映射?
  16. 如何给PDF加密码保护?这3种方法总有一个能用上
  17. Problem Set 2 Hangman Game字谜游戏
  18. cmd中cd命令使用
  19. Jmeter脚本录制:Jmeter5.0脚本录制
  20. 隐藏手机号码中间四位数

热门文章

  1. xxx is not mapped 错误 解决方案
  2. 功能暴强的页面验证js代码
  3. linux下远程访问Mysql
  4. VC 写 TXT 文件分割器 附代码
  5. CString转换为char*
  6. C++基础部分_C++文件操作_二进制文件的写操作---C++语言工作笔记078
  7. 在win10中使用任务计划程序_设置定时任务---Windows使用技巧工作笔记001
  8. C++_一维数组案例_五只小猪称体重_案例元素逆置(调换)_案例冒泡排序---C++语言工作笔记020
  9. org.springframework.boot:spring boot maven plugin丢失---SpringCloud Alibaba_若依微服务框架改造_--工作笔记012
  10. Rabbitmq学习笔记008---AmqpException: No method found for class java.lang.String