摘要:在Android当中的MVP模式(一)基本概念中,用了一个简单的的登录Demo展示了一下 MVP
模式的基本姿势,虽然项目结构是更加清晰了,但是代码量明显增多了,原来的网络请求操作只用 1
个类可以搞定,现在需要 4
个类,并且每当有不同作用的 model
出现时,我们就需要相应的为他们添加 presenter
层的对象,但是细细查看,这些model
的作用都大体相似,与获取数据相关,类似于网络请求或者是数据库 DAO
的操作,所以此处可以考虑将他们的共性抽取出来,封装成基累,然后子类去继承即可。
一个简单的网络请求
一个简单的需求:通过
url
获取数据,然后用Gson
解析成JavaBean
,然后展示到 ListView上。这里使用知乎日报的获取最新消息的API
接口 https://news-at.zhihu.com/api/4/news/latest
那么按照普通 MVP
的思路,首先 view
层:
为了突出重点,当前View层只做一件事情:就是展示获取的数据
ILatestVIew
此接口需要一个 String
类型的列表数据,主要是用于给 Adapter
展示用。
LatestViewActivity
很简答,就是实现接口。
IRequestLatestModel
请求服务器端数据的接口
RequestLatestNewsModel
使用 okhttp
请求数据,然后将返回的json类型数据传递给 Presenter
层。
ILatestNewsPresenter
一个接口用于处理 Json
数据,一个接口用于通知 model
层向服务器发起请求 。
LatestNewsPresenter
实现接口定义的方法
其中 HttpUtils
方法如下:
此处
OKhttp
也可以进行封装, 后面再写一篇文章, 专门记录,先暂时简单的使用。
运行之后,点击 button
, 即可发起网络请求,运行效果如下:
弊端:
假设我们现在又有另外的一个需求, 请求知乎日报过往的消息, 对应的 API
接口为URL: https://news-at.zhihu.com/api/4/news/before/20131119
,那么我就需要按照上述的方式,又写一套MVP的代码,最少又得留个类,如此一来,随着需求的增多,代码量会极具增大,但是多余增加的每层代码所做的事情又大多数相同,只是具体细节不一样,那么我们可不可以把每一层要做的事情给抽取出来,封装成基类,然后让子类去继承,去实现,这样就可以大量减少代码量? 抱着这个问题,我就来分析一下 MVP
每一层所做的事情。
以简单网络请求为例,分析 MVP 各层的职责
以上面请求知乎日报的最新消息为例,分析每一层的职责。
Model
层
Model
角色主要是提供数据的存取功,并且将数据或者是错误信息回调给 Presenter
层。更直白的说,Model
就是封装了数据库 DAO
或者网络获取数据的角色,或者两种数据获取方式的集合。所以它主要的功能是:
1. 向数据源发起请求
2. 取消发起的请求
3. 通知 Presenter 处理结果
Presenter
层
一般是通知 Model
向服务器发起请求,然后接收 Model
层的请求结果,包括成功的数据和错误的信息,同时也负责将处理之后的数据或者是错误信息通知 View
层,由 View
层作展示。所以他的主要功能是:
1. 通知 Model 层向服务器发起数据请求
2. 通知 Model 层取消这次请求
3. 接收 Model 层返回的数据
4. 接收 Model 层返回的错误信息
5. 通知 View 层接收处理之后的结果或者是错误信息
View
层
此处 View 层的作用就比较专一化,只用于处理 UI
相关的事情,不再负责业务逻辑。主要职责如下:
1. Loading 状态的展示隐藏
2. 接收 Presenter 层处理后的数据
3. 接收 Presenter 层处理后的错误信息
4. 接收 Presenter 层处理后的服务器拒绝信息
嗯,差不多就是这么多吧
既然将每一层的主要职责总结了出来, 很明显就可以将这些职责「在代码中就是对应的方法」抽象成方法,然后让子类去个性化的实现。
抽取共性封装网络请求
Model
层
IBaseModel
其中 setMethod
和 setRequestUrl
方法直接在 Presenter
的构造方法中调用,设置好请求的方式和请求的 Url
地址,这样方便 model
层在请求服务器数据时,使用对应的参数,使用对应的请求方式。
此处没有用到
method
是因为知乎日报的最新新闻 API 接口是 Get 方式,不需要参数,所以此处没有根据请求方式来调用不同的请求方法
Presenter
层
IBasePresenter
Presenter
层是逻辑控制层,是 Model
层和 View
层的桥梁,对这一层抽取共性进行封装的时候,不能像 Model
层一样,把全部的功能装好好,原因如下:
1.如果将其全部封装起来,是没办法复用同一个功能模块的,并且会导致部分业务逻辑需要在 view 层中做处理,这样和 MVP 的思想相悖。
2.Presenter 层需要处理和 View 层的交互逻辑以及 Model 层返回的数据。
但是 Model
层是可以的,我是认为,Model
层就是从数据源中拿数据,并且将数据传递给 Presenter
层,所有的 Model
层做的都是这个操作,只是访问数据源的参数不同,数据源类型不同,访问数据源的方法不同而已,所以很明显可以全部抽取出来放基类中,然后各个子类去各自实现。
1. requestServer 在View层调用的接口,用于通知Model层想服务器发起请求,参数可为空,比如,有些Get方式的请求就不需要参数
2. requestSuccess 在Model层调用,通过此方法将服务器返回的数据传递给给Presenter层处理
3. cancelRequest 在View层调用,用于通知Model层取消请求
4. okHttpError 在Model层调用,当网络请求产生错误的时候
5. getModel 在子类中调用,用于拿到Model对象
6. getParams 在Model层中调用,此方法用于获取Presenter层处理好的参数
BasePresenter
-
public abstract class BasePresenter<Params, Data> implements IBasePresenter<Params>
这是一个泛型的抽象类,其中泛型Params
是用于model
层向服务器发起请求的请求参数,Data
是服务器返回的Json
类对应的JavaBean
类。 -
BasePresenter
处理了View
层和model
层中大多数的逻辑,我们要做的就是在子类中实现public abstract void serverResponse(Data data);
这个抽象方法就好了。 -
public abstract void serverResponse(Data data);
这个方法是在用于处理model
层返回的结果,然后进行处理之后回调给view
层。 - 可以看到
46、47、50、51、52
行的代码给注释掉了,其实一般情况下这里是不需要注释的,这里是用于判断返回数据的errorNum errorType errorDesc
信息的,这么操作,是为了实现如下功能:若返回的信息有误,则BasePresenter直接回调给View
层,如果正确,才会传递给子类。上述最后一条,需要对泛型
Data
在进行一次封装,并且使用上Gson
的@SerializedName(value = "...",alternate = {"...","...","..."})
这个注解,并且这里涉及到泛型擦除的问题,这一块我还没有很好的解决办法,所以此处没有进行封装。
View
层
还是按照上面分析的 View
层职责来写:
IBaseView
到此为止,对 MVP
模式的每一层都写出了对应的基类,有了这件基类作为基础之后,在进行同样的网络请求。
使用上述封装好的类进行相同的网络请求
LatestNewsModel
LatestNewsPresenter
其中Param
泛型参数填的是nullable
是因为这个请求是get
方式,没有涉及到参数。LatestNews
作为Data
的泛型,主要是用于BasePresenter
解析并映射。
ILatestNewsView
IlatestNewsVIew
接口是继承IBaseView
接口的,是因为它需要在IBaseView
接口所定义的功能之上,还需要实现将数据展示到列表中这么一个操作,所以添加上了一个showLatestViewTitle
方法。
LatestNewsTitleActivity
这个类写起来就简单了,跟着接口来, 把之前每一个接口提到的功能给实现以下就可以了。
顺便贴个 XML
文件:
搞定,实现的效果和上面是一样的。
回过头一看,MMP,这代码量似乎也没有少很多啊,-。- ,没事没事,需求多了就少了~
小结
先看看上一篇中提到的一张图
此处将MVP模式封装后,MVP的流程图如下:
后面的文章将使用上面封装的框架,通过扩展 BasePresenter
来增加新的模块。