Android 当中的 MVP 模式(二)封装

摘要:在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

其中 setMethodsetRequestUrl 方法直接在 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 来增加新的模块。

共82.3k字
0%
.gt-container a{border-bottom: none;}