内容简介:我是最近才开始写Android文章,暂时不知道该写些什么东西。外加上一位朋友好像对mvp有点疑问。我本不想一开始就写这个,但是我又不耐烦的去给他讲什么mvp,mvp该怎么写。我想了一下,与其一点一点告诉他什么是mvp,还不如写下一篇文章来分享我关于MVP的一些理解。首先,在我的观点里面,阅读该源码是需要有一点Android的开发经验的。如果你只是一个初学者或者是没有基础的小伙子,我奉劝你别花费时间来阅读我这篇文章,可能对你的发展并没有多大的作用。然后谈到框架,其实首先映入眼帘的应该是
我是最近才开始写Android文章,暂时不知道该写些什么东西。外加上一位朋友好像对mvp有点疑问。我本不想一开始就写这个,但是我又不耐烦的去给他讲什么mvp,mvp该怎么写。我想了一下,与其一点一点告诉他什么是mvp,还不如写下一篇文章来分享我关于MVP的一些理解。
说在前面
首先,在我的观点里面,阅读该源码是需要有一点Android的开发经验的。如果你只是一个初学者或者是没有基础的小伙子,我奉劝你别花费时间来阅读我这篇文章,可能对你的发展并没有多大的作用。
然后谈到框架,其实首先映入眼帘的应该是 mvc框架
,这是最早在学习 java 的时候常见的。m是model层,v是view层、c是control层。这篇文章呢?我希望由mvc的概念讲起、延伸至mvp的概念,然后再简单的写一个mvp的demo、到最后实际来封装出一个在我掌控之内的mvp框架。结尾希望能结合我的一些开发经验谈一谈mvp的优劣势。
一、 mvc
首先mvc框架共分为三层。m是实体层用来组装数据的;v是视图层用来显示数据的;c是控制层用来分发用户的操作给视图层。总的来说,基本的流程应该是下图:
简单的来说mvc的运行流程就是:用户通过控制层去分发操作到实体层去组装数据,最后将数据展示到视图层的过程。
如果按照Android如今的分法的话,原本的实体层里面就应该还是实体层,然后fragment/activity里面就会富含生命周期、业务逻辑、视图的操作等等。这样做的好处呢?是代码量比较统一,易于查找。
但是当业务逻辑比较复杂的时候呢?就会出现代码量比较庞大,我甚至在之前的一个项目内看到了将近2000行的一个activity。当时我惊了个呆。由于刚接触那个项目,我调试、log等等一系列操作都用上了,硬是用了三天才搞清楚代码的流程。
作为一个有追求的程序员,也为了成为一个有责任心的程序员。我建议你看一看mvp。
二、 mvp
前面谈到在mvc里面,业务逻辑层和视图都会放在activity/fragment里面进行操作,并且本身activity就需要维护自己的生命周期。这会导致activity/fragment里面代码的臃肿,减少代码的可读性和代码的可维护性。
在我看来mvp框架其实是mvc框架变种产品。讲原本的activity/fragment的层次划分成present层和view层。m还是原来的实体层用来组装数据,p层则用来隔离view层,被称为中介层,v层还是view层主要用来展示数据的层。如下图所示:
有了present层之后呢?view层就专心在activity/fragment里面主要去处理视图层和维护自己的生命周期,将业务逻辑委托给present层,present层作为实体层和视图层的中介。实体层和视图层不直接进行交互,而是通过委托给persent层进行交互,这样做的好处是:
- 分离了视图逻辑和业务逻辑,降低了耦合
- Activity只处理生命周期的任务,代码变得更加简洁
- 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性
- Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
- 把业务逻辑抽到Presenter中去,避免后台线程引用着Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM
- 方便代码的维护和单元测试。
其实说了这么多,都是瞎说
Talk is cheap, let me show you the code!
三、 用mvp简单实现一个实例
我看了很多mvp都在模拟写一个登陆的界面,我也就来简单的模拟一个登陆的界面吧。
activity_main的代码:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white"> <android.support.constraint.Group android:id="@+id/login_group" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="visible" app:constraint_referenced_ids="edit_username,edit_password,guide_view,login_btn,clear_btn" /> <EditText android:id="@+id/edit_username" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入账号" android:inputType="text" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/edit_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:hint="请输入密码" android:inputType="textPassword" app:layout_constraintStart_toStartOf="@id/edit_username" app:layout_constraintTop_toBottomOf="@id/edit_username" /> <android.support.constraint.Guideline android:id="@+id/guide_view" android:layout_width="1dp" android:layout_height="match_parent" android:orientation="vertical" app:layout_constraintGuide_percent="0.5" /> <Button android:id="@+id/login_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="10dp" android:layout_marginTop="20dp" android:text="登陆" app:layout_constraintEnd_toStartOf="@id/guide_view" app:layout_constraintHorizontal_weight="1" app:layout_constraintTop_toBottomOf="@id/edit_password" /> <Button android:id="@+id/clear_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" android:text="重置" app:layout_constraintHorizontal_weight="1" app:layout_constraintStart_toEndOf="@id/guide_view" app:layout_constraintTop_toTopOf="@id/login_btn" /> <android.support.v4.widget.ContentLoadingProgressBar android:id="@+id/progress_bar" style="?android:attr/progressBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" app:layout_constraintBottom_toTopOf="parent" app:layout_constraintEnd_toStartOf="parent" app:layout_constraintStart_toEndOf="parent" app:layout_constraintTop_toBottomOf="parent" /> <Button android:id="@+id/retry_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="重试" android:visibility="gone" app:layout_constraintBottom_toTopOf="parent" app:layout_constraintEnd_toStartOf="parent" app:layout_constraintStart_toEndOf="parent" app:layout_constraintTop_toBottomOf="parent" /> <TextView android:id="@+id/login_success_tips" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="登陆成功!!" android:visibility="gone" app:layout_constraintBottom_toTopOf="parent" app:layout_constraintEnd_toStartOf="parent" app:layout_constraintStart_toEndOf="parent" app:layout_constraintTop_toBottomOf="parent" /> </android.support.constraint.ConstraintLayout> 复制代码
说明一下
:我里面用到了很多 ConstraintLayout
的新属性,如果你对这个有疑问,请翻阅我之前的文章 ConstraintLayout用法详解 .
MainActivity的代码(视图层):
class MainActivity : AppCompatActivity(), IView, View.OnClickListener { private var persent: IPresent? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) persent = MainPresent(this) login_btn.setOnClickListener(this) clear_btn.setOnClickListener(this) retry_btn.setOnClickListener(this) } override fun onClick(view: View?) { when (view?.id) { R.id.login_btn -> { persent?.checkFrom(edit_username.text.toString(), edit_password.text.toString()) } R.id.clear_btn -> { edit_username.setText("") edit_password.setText("") } R.id.retry_btn -> { retry_btn.visibility = View.GONE persent?.checkFrom(edit_username.text.toString(), edit_password.text.toString()) } } } override fun errorShowTips(tips: String) { toast(tips) } override fun onSubmit() { login_group.visibility = View.INVISIBLE progress_bar.visibility = View.VISIBLE } override fun showResult(loginSuccess: Boolean) { progress_bar.visibility = View.GONE if (loginSuccess) { login_success_tips.visibility = View.VISIBLE } else { retry_btn.visibility = View.VISIBLE } } } 复制代码
MainModel的代码(实体层):
class MainModel : IModel{ // 模拟请求数据 override fun login(username: String, password: String): Observable<Boolean> { return Observable.just(true) } } 复制代码
MainPresent的代码(中介层):
class MainPresent(view: IView) : IPresent { private var view: IView? = null private var model: IModel? = null init { model = MainModel() this.view = view } override fun checkFrom(username: String, password: String) { if (username.isEmpty()) { view?.errorShowTips("请输入用户名") return } if (password.isBlank()) { view?.errorShowTips("请输入密码") return } view?.onSubmit() // 模拟一下网络加载的延时 model?.run { login(username = username, password = password) .delay(2, TimeUnit.SECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribeBy( onNext = { view?.showResult(it) }, onError = { view?.showResult(false) } ) } } } 复制代码
IFeature的代码(封装接口):
interface IView { fun errorShowTips(tips:String) fun onSubmit() fun showResult(loginSuccess: Boolean) } interface IPresent { fun checkFrom(username:String,password:String) } interface IModel { fun login(username:String,password: String): Observable<Boolean> } 复制代码
四、 重新封装一下mvp
如果你是一个对自己的要求非常高的程序员,你会尽量去优化重复的代码。如果你对上面的代码已经纯熟了之后,你会发现:我们每次都会写想同的代码。好处就是增加了你对代码层面的熟悉程度,但是坏处就会造成大量的代码冗余。
所以此时我们就需要一个抽取想同的代码进行封装操作。当然我的经历也不算太过丰富,可能代码考虑面没有那么全。如果存在疑虑的呢?可以进行讨论、完善。
基类:IFeature.kt
interface IModel { fun onAttach() fun onDetach() } interface IView interface IPresenter<in V : IView, in M : IModel> { fun attach(view: V?, model: M?) fun onResume() fun onPause() fun detach() fun isAttached(): Boolean } 复制代码
Presenter:
abstract class Presenter : PresenterLifecycle, PresenterLifecycleOwner { protected open var mContext: Context? = null /** * mHandler is main thread handler */ protected val mHandler: Handler = Handler(Looper.getMainLooper()) /** * currentState is current present lifecycle state */ override var currentState: Event = Event.DETACH /** * mOnAttachStateChangedListeners contains listeners object who would be notified when this presenter's lifecycle changed */ private val mOnAttachStateChangedListeners: FastSafeIterableMap<OnAttachStateChangedListener, Unit> = FastSafeIterableMap() /** * isAttached is true after presenter has been invoked [onAttach] */ protected var mIsAttached: Boolean = false /** * isPaused is true when presenter's lifecycle is ON_PAUSE */ protected var mIsPaused: Boolean = false open fun onAttach(context: Context) { mContext = context mIsAttached = true currentState = Event.ATTACH synchronized(this) { mOnAttachStateChangedListeners.forEach { (listener, _) -> listener.onStateChanged(this, Event.ATTACH) } } } open fun onResume() { mIsPaused = false currentState = PresenterLifecycle.Event.ON_RESUME synchronized(this) { mOnAttachStateChangedListeners.forEach { (listener, _) -> listener.onStateChanged(this, Event.ON_RESUME) } } } open fun onPause() { mIsPaused = true currentState = PresenterLifecycle.Event.ON_PAUSE synchronized(this) { mOnAttachStateChangedListeners.forEach { (listener, _) -> listener.onStateChanged(this, Event.ON_PAUSE) } } } open fun onDetach() { mIsAttached = false currentState = PresenterLifecycle.Event.DETACH synchronized(this) { mOnAttachStateChangedListeners.forEach { (listener, _) -> listener.onStateChanged(this, Event.DETACH) mOnAttachStateChangedListeners.remove(listener) } } } override fun addOnAttachStateChangedListener(listener: PresenterLifecycle.OnAttachStateChangedListener) { synchronized(this) { mOnAttachStateChangedListeners.putIfAbsent(listener, Unit) } } override fun removeOnAttachStateChangesListener(listener: PresenterLifecycle.OnAttachStateChangedListener) { synchronized(this) { mOnAttachStateChangedListeners.remove(listener) } } override fun getLifecycle(): PresenterLifecycle { return this } } 复制代码
PresenterLifecycle
interface PresenterLifecycle { var currentState: Event fun addOnAttachStateChangedListener(listener: OnAttachStateChangedListener) fun removeOnAttachStateChangesListener(listener: OnAttachStateChangedListener) interface OnAttachStateChangedListener { fun onStateChanged(presenter: Presenter, event: Event) } enum class Event { ATTACH, ON_RESUME, ON_PAUSE, DETACH } } 复制代码
VMpresent:
abstract class VMPresenter<V : IView, M : IModel>(val context: Context) : Presenter(), IPresenter<V, M> { /** * viewRef is weak reference of view object */ private var viewRef: WeakReference<V>? = null /** * modelRef is weak reference of model object */ private var modelRef: WeakReference<M>? = null /** * Convenient property for accessing view object */ protected val view: V? get() = viewRef?.get() /** * Convenient property for access model object */ protected val model: M? get() = modelRef?.get() /** * isPaused is true when presenter's lifecycle is ON_PAUSE */ protected val isPaused: Boolean get() = mIsPaused override fun attach(view: V?, model: M?) { super.onAttach(context) viewRef = if (view != null) WeakReference(view) else null modelRef = if (model != null) WeakReference(model) else null } override fun detach() { super.onDetach() // clear the listeners to avoid strong retain cycle modelRef = null viewRef = null } override fun isAttached(): Boolean = mIsAttached } 复制代码
Model:
abstract class Model(context: Context) : IModel { protected val context: Context = context.applicationContext override fun onAttach() {} override fun onDetach() {} } 复制代码
以上就是我自己对mvp框架的一个封装,可能还存在着很多的漏洞。
五、 mvp的劣势以及介绍一下mvvm
首先对于mvp的优势,我想我就不用说了。至于mvp的劣势:是需要加入Presenter来作为桥梁协调View和Model,同时也会导致Presenter变得很臃肿,在维护时比较不方便。而且对于每一个Activity,基本上均需要一个对应的Presenter来进行对应。
如果外加上 自己封装的话,这种代码的框架性就会愈发明显。所以我觉得如果不是对逻辑有很大要求的情况之下,没必要使用mvp框架了。
当然除了mvp框架之外,还有mvvm,甚至还有更加出色的mvpvm框架。我在这里呢?就简单介绍一下:
-
MVVM MVVM其实是对MVP的一种改进,他将Presenter替换成了ViewModel,并通过双向的数据绑定来实现视图和数据的交互。也就是说只需要将数据和视图绑定一次之后,那么之后当数据发生改变时就会自动的在UI上刷新而不需要我们自己进行手动刷新。在MVVM中,他尽可能的会简化数据流的走向,使其变得更加简洁明了。示意图如下:
-
MVPVM
MVPVM即:Model-View-Presenter-ViewModel。此模式是MVVM和MVP模式的结合体。但是交互模式发生了比较大的变化。
Presenter同时持有View、Model、ViewModel,负责协调三方的之间的交互。
View持有ViewModel。ViewModel是View展示数据的一个映射,两者之间双向绑定: (1)当View的数据发生变化时,View将数据更改同步到ViewModel。比如用户在输入框输入了内容。 (2)View监听ViewModel的数据变化,当ViewModel的数据发生变化时,View根据ViewModel的数据更新UI显示。比如更新来自后端的数据列表。
Presenter持有View,并且View的动作响应传递至Presenter。当收到View的动作响应之后,Presenter通过Model获取后端或者数据库数据,请求参数来自于Presenter持有的ViewModel。
当Model请求到数据之后,将数据返回给Presenter,Presenter将返回的数据传递到ViewModel,由于View和ViewModel之间的绑定关系,View会根据ViewModel的数据更新UI显示。
说在最后
说到项目本身呢?我是用的最新的kotlin+anko配合rx的写法,这也是我认为我这篇文章不适合新手学习的原因。首先你能看懂这篇文章呢?可能要对kotlin有一定的了解,然后可能还需要对rx有一定的了解。这能看懂这篇文章。
至于后面的mvvm和mvpvm其实我基本上都只是有些了解,具体的我没有进行深究,如果后面有需要 我也会深究一下这里只是做简单的介绍罢了
我接触kotlin也有一年多了,也写了一个大的项目 对于这个语法有一定的心得,后续我会结合我自己的心得和体会给诸位读者一一讲述出来。好了,时间不早了,对于一个失眠的人,现在已经到极点了。先洗澡睡觉了。
以上所述就是小编给大家介绍的《谈一谈我对mvp框架的理解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 理解 Koa 框架中间件原理
- 深入理解express框架的匹配路由
- 框架基础:深入理解Java注解类型(@Annotation)
- 前端框架MVVM和VUE的理解和应用
- Gin 框架系列(三):换个姿势理解中间件
- Gin框架系列03:换个姿势理解中间件
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
R for Data Science
Hadley Wickham、Garrett Grolemund / O'Reilly Media / 2016-12-25 / USD 39.99
http://r4ds.had.co.nz/一起来看看 《R for Data Science》 这本书的介绍吧!