目录
- 1.什么是MVP?
- 2.Google官方的MVP
- 3.V1.1 My MVP V1
- 4.V1.2 My MVP V2
1.什么是MVP?
Google在2016年推出了官方的Android MVP架构Demo,本文主要分析一下官方的MVP Demo,并且借由自己的一些经验,提出一些学习过程中,遇到的问题和自己的改进、封装措施。
MVP架构已经推出很多年了,现在已经非常普及了,我在这里就不过多介绍,简单的说,它分为以下三个层次:
- Model:数据模型层,主要用来数据处理,获取数据;
- View:显示界面元素,和用户进行界面交互;
- Presenter: 是Model和View沟通的桥梁,不关心具体的View显示和Model的数据处理。View层中所有的逻辑操作都通过Presenter去通知Model层去完成,Model中获取的数据通过Presenter层去通知View层显示。 MVP架构最大的好处,就是把传统MVC架构中View层和Control层的复杂关系完全解耦,View层只关心界面显示相关的工作即可,Model层仅获取数据,处理逻辑运算即可,各司其职,而不用关心其他工作。而且大家发现没有,这样设计的话,很好写单元测试代码,针对于View、Presenter、Model层我们可以分别选用适合的单元测试框架,不用再像MVC/MVVM一样,由于代码混在或者分离在多处,到处无法“单一职责”的去完成每个类的单元测试代码编写。
在刚刚接触android的时候,或者说,现在依然有很大一部分的APP开发者,在开发过程中,总是习惯在一个Activity、Fragment中几乎完成了所有的功能。例如网络请求、数据加载、业务逻辑处理、界面加载、界面动画。 后来渐渐的我们接触到了MVC、MVP各种官方的框架,懂得了模块的分离、解耦(MVC),懂得了通过依赖于抽象去分离各个模块彻底解耦(MVP)。 但是官方的MVP框架的确已经帮我们做了很多,但是依然不够,接下来,我们基于官方给的MVP框架,结合六大基本原则,去封装更加适宜于项目的MVP框架。 整体设计模式Demo代码
2.Google官方的MVP
官方Demo怎么去做的?
我们为了方便去分析,我这里简化代码,我们逐步分析,BaseView 和 BasePresenter,BaseView谷歌是这么写的(其实就是view的接口,展示view),以下样例为了理解,我简化处理了部分代码 BaseView.java
package com.itbird.design.principle.mvp.google; | |
/** | |
* Google Demo | |
* Created by itbird on 2022/2/25 | |
*/ | |
public interface BaseView<T> { | |
//View中,设置presenter对象,使View可以持有Presenter对象引用 | |
void setPresenter(T presenter); | |
} |
BasePresenter
package com.itbird.design.principle.mvp.google; | |
/** | |
* Google Demo | |
* Created by itbird on 2022/2/25 | |
*/ | |
public interface BasePresenter { | |
} |
TaskDetailContract
package com.itbird.design.principle.mvp.google; | |
/** | |
* Google Demo | |
* Created by itbird on 2022/2/25 | |
*/ | |
public interface TaskDetailContract { | |
interface View extends BaseView<Presenter> { | |
//界面UI刷新方法 | |
void updateTextView(String s); | |
} | |
interface Presenter extends BasePresenter { | |
void loadDataFromModel(); | |
} | |
} |
TaskGoogleActivity
package com.itbird.design.principle.mvp.google; | |
import android.os.Bundle; | |
import android.util.Log; | |
import android.widget.TextView; | |
import androidx.appcompat.app.AppCompatActivity; | |
import com.itbird.design.R; | |
public class TaskGoogleActivity extends AppCompatActivity implements TaskDetailContract.View { | |
private static final String TAG = TaskGoogleActivity.class.getSimpleName(); | |
private TaskDetailContract.Presenter mPresenter; | |
private TextView mTextView; | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
findViewById(R.id.textview); | |
Log.e(TAG, TAG + " onCreate"); | |
new TaskGooglePresenter(this); | |
} | |
public void setPresenter(TaskDetailContract.Presenter presenter) { | |
mPresenter = presenter; | |
} | |
public void updateTextView(String s) { | |
mTextView.setText(s); | |
} | |
} |
TaskGooglePresenter
package com.itbird.design.principle.mvp.google; | |
public class TaskGooglePresenter implements TaskDetailContract.Presenter { | |
private static final String TAG = TaskGooglePresenter.class.getSimpleName(); | |
TaskDetailContract.View mView; | |
public TaskGooglePresenter(TaskDetailContract.View view) { | |
mView = view; | |
mView.setPresenter(this); | |
} | |
public void loadDataFromModel() { | |
//TODO :loaddata,此处可以用model、或者进行业务操作 | |
//调用界面方法,进行数据刷新 | |
mView.updateTextView("loaddata success!!!"); | |
} | |
} |
小结 我们单从设计来说, Google MVP Demo的确向我们展示了MVP的优点:
- 1)业务、数据、视图分离、解耦,从六大基本原则上来说,开闭、单一职责、里氏代换、依赖倒置、接口隔离、最少知道,基本都做到了。
- 2)由于完全分离,所以我们很方便针对于每层去选取对应的单元测试模式,例如针对于数据层可以使用(Junit+Mockito)、视图层可以使用(AndroidJunitRunner+Espresso)、业务层可以使用(Junit+Mockito)
- 3)通过Contract 契约类,完全将视图、业务相关的接口,封装放在了一个地方,简单明了,针对于开发者来说,不容易忘记和辅助养成习惯
但是,但是对于我们实际开发使用来说,依然有以下几点问题:
- 1)setPresenter接口完全没有必要存在,因为Presenter对象一定是在View类中new出来的,我既然都有它自己的对象了,干嘛还要在Presenter内部,去调用mView.setPresenter(this);,再返回View中,再去保存一个引用,代码看着很怪
- 2)我们知道MVP的精髓在与,P、V肯定要相互交互,他们互相要持有对方的引用,通过上面一点,我们知道Presenter对象一定是在View类中new出来的,所以View肯定有Presenter对象的引用,这个没问题了。但是Presenter要想拥有View的引用,只能通过Presenter构造方法、或者Presenter内部有一个setView方法,让开发者自己去调用setView或者实现带参构造方法,从设计角度来说,这明显是一个坑,因为一个框架的设计,是为了更加方便开发,而不是让开发人员设置这个、设置那个、必须调用某个方法。既然是必须调用的方法,我们应该通过框架去内部消化掉,而不是给开发者制造麻烦。
- 3)Presenter中拥有View的引用,如果activity、fragment销毁,但是presenter依然在执行某些任务,这样会导致activity、fragment无法GC回收,导致内存泄露,甚至与崩溃,所以这也是一个框架必须解决的问题。
3.V1.1 My MVP V1
基于上面提出的三点,我们去优化Google的MVP框架。 我们首先将第一点和第二点通过抽象来解决一下。 IPresenter
package com.itbird.design.principle.mvp.v1; | |
/** | |
* 自定义MVP框架,BasePresenter | |
* Created by itbird on 2022/2/25 | |
*/ | |
public interface IPresenter { | |
/** | |
* 与view班定 | |
* | |
* @param view | |
*/ | |
void onAttach(IView view); | |
/** | |
* 与view解绑 | |
*/ | |
void onDetach(); | |
/** | |
* 是否与view已经班定成功 | |
* | |
* @return | |
*/ | |
boolean isViewAttached(); | |
/** | |
* 获取view | |
* @return | |
*/ | |
IView getView(); | |
} |
IView
package com.itbird.design.principle.mvp.v1; | |
/** | |
* 自定义MVP框架,BaseView | |
* Created by itbird on 2022/2/25 | |
*/ | |
public interface IView { | |
} |
接下来是借助activity生命周期,对presenter的初始化进行封装 BaseActivity
package com.itbird.design.principle.mvp.v1; | |
import android.app.Activity; | |
import android.os.Bundle; | |
import androidx.annotation.Nullable; | |
/** | |
* Created by itbird on 2022/3/29 | |
*/ | |
public abstract class BaseActivity extends Activity implements IView { | |
IPresenter mPresenter; | |
protected void onCreate( { Bundle savedInstanceState) | |
super.onCreate(savedInstanceState); | |
mPresenter = createPresenter(); | |
if (mPresenter != null) { | |
mPresenter.onAttach(this); | |
} | |
} | |
protected void onDestroy() { | |
super.onDestroy(); | |
if (mPresenter != null) { | |
mPresenter.onDetach(); | |
mPresenter = null; | |
} | |
} | |
abstract IPresenter createPresenter(); | |
} |
BasePresenter
package com.itbird.design.principle.mvp.v1; | |
import java.lang.ref.WeakReference; | |
/** | |
* Created by itbird on 2022/3/29 | |
*/ | |
public class BasePresenter<V extends IView> implements IPresenter { | |
WeakReference<V> mIView; | |
public void onAttach(IView iView) { | |
mIView = new WeakReference<>((V) iView); | |
} | |
public void onDetach() { | |
mIView = null; | |
} | |
public V getView() { | |
if (mIView != null) { | |
mIView.get(); | |
} | |
return null; | |
} | |
public boolean isViewAttached() { | |
return mIView != null && mIView.get() != null; | |
} | |
} |
4.V1.2 My MVP V2
看上图类图,我们依然发现有一些不满足的点:
1)activity中,依然需要初始化mPresenter
IPresenter createPresenter() { | |
mTaskPresenter = new TaskMyPresenter(); | |
return mTaskPresenter; | |
} |
是否可以做到在activity中,自己像presenter中调用view一样,自己一句话getView就可以搞定
2)观察类图,其实IView、IPresenter没有必要存在,因为毕竟只是baseActivity、basePresenter的行为
所以改造如下: BasePresenter
package com.itbird.design.principle.mvp.v2; | |
import java.lang.ref.WeakReference; | |
/** | |
* Created by itbird on 2022/3/29 | |
*/ | |
public abstract class BasePresenter<V> { | |
WeakReference<V> mIView; | |
public void onAttach(V iView) { | |
mIView = new WeakReference<>(iView); | |
} | |
public void onDetach() { | |
mIView = null; | |
} | |
public V getView() { | |
if (mIView != null) { | |
mIView.get(); | |
} | |
return null; | |
} | |
public boolean isViewAttached() { | |
return mIView != null && mIView.get() != null; | |
} | |
} |
BaseActivity
package com.itbird.design.principle.mvp.v2; | |
import android.app.Activity; | |
import android.os.Bundle; | |
import androidx.annotation.Nullable; | |
/** | |
* Created by itbird on 2022/3/29 | |
*/ | |
public abstract class BaseActivity<V, T extends BasePresenter<V>> extends Activity { | |
T mPresenter; | |
protected void onCreate() { Bundle savedInstanceState | |
super.onCreate(savedInstanceState); | |
mPresenter = initPresenter(); | |
if (mPresenter != null) { | |
mPresenter.onAttach((V) this); | |
} | |
} | |
protected void onDestroy() { | |
super.onDestroy(); | |
if (mPresenter != null) { | |
mPresenter.onDetach(); | |
mPresenter = null; | |
} | |
} | |
public T getPresenter() { | |
return mPresenter; | |
} | |
abstract T initPresenter(); | |
} |
此时,契约类,不再需要依赖BasePresenter/BaseView相关接口,而且View中也可以自己获取到presenter的引用了。
package com.itbird.design.principle.mvp.v2; | |
/** | |
* my Demo | |
* Created by itbird on 2022/2/25 | |
*/ | |
public interface TaskMyContract { | |
interface View { | |
//界面UI刷新方法 | |
void updateTextView(String s); | |
} | |
interface Presenter { | |
void loadDataFromModel(); | |
} | |
} | |
package com.itbird.design.principle.mvp.v2; | |
import android.os.Bundle; | |
import android.util.Log; | |
import android.widget.TextView; | |
import com.itbird.design.R; | |
public class TaskMyActivity extends BaseActivity<TaskMyContract.View, TaskMyPresenter> implements TaskMyContract.View { | |
private static final String TAG = TaskMyActivity.class.getSimpleName(); | |
TextView mTextView; | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
mTextView = findViewById(R.id.textview); | |
Log.e(TAG, TAG + " onCreate"); | |
mPresenter.loadDataFromModel(); | |
} | |
TaskMyPresenter initPresenter() { | |
return new TaskMyPresenter(); | |
} | |
public void updateTextView(String s) { | |
mTextView.setText(s); | |
} | |
} |
此时的类图,是否更加明确,而且上面各个问题都已经得到了解决。