MVP简介
如果知道MVC模式那么应该清楚,MVC不符合单一模式原则,其V层和C层几乎在一起。
MVP模式则是为了更好的实现解耦
MVP字母分别代表:
- M (Model) 数据相关
- V (View) 视图层,针对UI做操作
- P (Presenter) 纽带层,链接Model和View,实现具体事物
在android开发中:
- View层通常是Activity或Fragemnt,view层的作用就是操控ui界面组件,提供友好的界面与用户进行交互。
- Model层即数据层。负责对数据的存取操作,数据库的读写,网络数据的请求。
- Presenter层作为View层和Model层的桥梁对业务逻辑进行处理。
三者之间关系图示:
三者通过持有相关的引用关联起来。
MVP实现
MVP最简单实现
目录结构如图:
BaseView接口
/**
* @author Chenchangsheng
* @since 2019/7/28
*/
interface BaseView<T>{
/**
* 为view层绑定Presenter
*/
fun setPresenter(presenter:T)
}
BasePresenter接口
/**
* @author Chenchangsheng
* @since 2019/7/28
*
* basePresenter接口
*/
interface BasePresenter {
/**
* 初始化方法,可以将一些初始化操作放在此方法中
*/
fun start():Unit
}
MainContract接口
/**
* @author Chenchangsheng
* @since 2019/7/28
*
* 将Presenter层和View层的接口放在一起可以更清晰的看到它们中都有那些功能,方便后期维护
*/
interface MainContract {
/**
* view层的接口
*
* 声明一些ui界面交互的方法
*/
interface View :BaseView<Presenter>{
}
/**
* presenter层接口
*
* 声明一些业务逻辑方法
*/
interface Presenter : BasePresenter{
}
}
MainPresenter 类
MainPresenter实现了MainContract.Presenter接口
/**
* @author Chenchangsheng
* @since 2019/7/28
*
* Main页面的Presenter,业务逻辑的实现
*/
class MainPresenter(view:MainContract.View):MainContract.Presenter{
/**
* MainActivity的引用,可以通过view调用MainActivity重写的方法
*/
private var mView = view
init {
mView.setPresenter(this)
}
/**
* 实现BasePresenter的start方法,可以进行一些初始化操作
*/
override fun start() {
}
}
MainActivity
/**
* @author Chenchangsheng
* @since 2019/7/28
*
* view层
*/
class MainActivity : AppCompatActivity(),MainContract.View{
private lateinit var presenter:MainContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/**
* 这里或许会有疑问,为什么不:
* var presenter:MainPresenter= MainPresenter(this)
* 而是直接
* MainPresenter(this)
*
* 仔细看就会发现
* MainPresenter(this)————调用————>MainPresenter:init{}块的 mView.setPresenter(this)————也就是说会调用————>MainActivity:重写
* 过的setPresenter(presenter: MainContract.Presenter)方法 (就在下面⬇️)
*
* 这样一来 MainActivity就持有了MainPresenter的引用 而MainPresenter也持有了 MainActivity的引用
*
* 因此Presenter层和View层就关联了起来
*/
MainPresenter(this)
}
override fun setPresenter(presenter: MainContract.Presenter) {
this.presenter=presenter
}
}
梳理
以上代码就是一个很简单的MVP模式结构了,每个部分的作用我也加上注释了。
- BaseView、BasePresenter 所有View层和Presenter层都需要继承这两个接口,接口中声明了必要的方法
- MainContract 是一个契约类,该类中有两个内部接口,View、Presenter分别继承了BaseView和BasePresenter。View和Presenter所有的具体方法,都定义在这两个接口中,这样做好处就是view和presenter的方法都集中在一起,一目了然。
- MainActivity 则是View接口的实现类,View接口所有方法的实现均在此类中
- MainPresenter 是Presenter接口实现类,实现了Presenter接口相关的方法
引用过程
在MainActivity中通过MainPresenter(this)新建一个MainPresenter实例,并传入当前View对象,使该MainPresenter实例持有View层的引用,因此Presenter可以调用View层的方法。而在MainPresenter的构造函数中(我用的kotlin 则为init块),将会调用view的setPresenter并传入当前的MainPresenter实例,这样一来就想View和Presenter两个层关联起来。
MVP实际上就是将各功能代码分开,通过持有对方引用调用代码而已。
MVP小例子
在上面的MVP简单实现的基础上,做一个小demo,实现一个登陆功能,相当简陋的登陆功能!
效果如下:
布局文件
为MainContract添加方法
interface View :BaseView<Presenter>{
/**
* 登陆成功
*/
fun onSuccess()
/**
* 登陆失败
*/
fun onFail()
}
/**
* presenter层接口
*
* 声明一些业务逻辑方法
*/
interface Presenter : BasePresenter{
/**
* 登陆
*/
fun login(user:String)
}
实现接口新增方法
MainPresenter类中:
private var mView = view
private lateinit var mModel:MainModel
init {
mView.setPresenter(this)
mModel = MainModel()
}
/**
* 实现登陆功能
* 登陆失败 调用view的onFail反之调用onSuccess
*/
override fun login(user: String) {
if (mModel.selectUser(user)){
mView.onSuccess()
}else{
mView.onFail()
}
}
MainActivity类中:
override fun setPresenter(presenter: MainContract.Presenter) {
this.presenter=presenter
}
override fun onSuccess() {
Toast.makeText(this,"登陆成功",Toast.LENGTH_SHORT).show()
}
override fun onFail() {
Toast.makeText(this,"登陆失败",Toast.LENGTH_SHORT).show()
}
fun setListener():Unit{
btMain.setOnClickListener(this)
}
override fun onClick(p0: View?) {
if (p0 != null) {
if (p0.id==btMain.id){
presenter.login(etMain.text.toString())
}
}
}
MainModel类中
此类主要进行数据操作
/**
* @author Chenchangsheng
* @since 2019/7/28
*
* 数据层,主要是针对数据操作,比如获取网络数据,数据库的增删查改等
*/
class MainModel {
/**
* 查找指定用户
*
* 应该是数据库操作,但是为了方便我写简单一点
*/
fun selectUser(user:String):Boolean{
val admin = "admin"
return admin.equals(user)
}
}
MVP包结构两种方式
第一种
第一种结构就是分别建立三个4个包:
- contract
- model
- presenter
- view
如图所示:
第二种
第二种则是像谷歌官方实例一样
按照每个功能页面建立包
比如:一个login页面建立一个包,里面包括contract、presenter、view
MVP优缺点
优点
- 降低耦合度
- 模块职责划分明显
- 利于测试驱动开发
- 代码复用
- 隐藏数据
- 代码灵活性
缺点
MVP缺点也十分明显
- 类太多了 主要表现在每个view都有presenter ,类相对比较多
- 代码复杂度和学习成本高,在某些场景下presenter的复用会产生接口冗余。
- Presenter和View相互持有引用,解除不及时的话容易出现内存泄漏。
- Model进行一步操作的时候,获取结果通过Presenter会传到View的时候,出现View引用的空指针异常。比如说执行网络耗时操作,如果view层已经结束,model才传回数据并且通过presenter操作view,则显然会出现空指针异常。
优化方案
这篇文章我们一步到胃,也说说优化方案。
解决内存泄漏问题
解决方法很简单就是为pasenter添加一个detach()方法,当activity结束时调用此方法解除引用
// 在MainContrct中声明方法
interface Presenter : BasePresenter{
/**
* 解绑view
*/
fun detach()
}
// 在MainPresenter中实现方法
override fun detach() {
this.mView=null
}
// MainActivity中调用
override fun onDestroy() {
super.onDestroy()
presenter.detach()
}
注意
1.使用此方法虽然解决了内存泄漏问题,但是可能会引起空指针问题,比如,耗时操作的例子,因此可以考虑增加结束耗时任务的方法,在解除引用的时候调用。
2.如果每个presenter都增加绑定、解除方法那么就十分麻烦,并且增加无用的代码量,因此可以考虑将它们写入BasePresenter中,这样一来每个Presenter实现类都拥有这两个方法
interface BasePresenter {
/**
* 初始化方法,可以将一些初始化操作放在此方法中
*/
fun start():Unit
/**
* 绑定view
* @param view
*/
fun attach(view: MainContract.View)
/**
* 解绑view
*/
fun detach()
}
未完不想写了 待续