需求:
阅读页功能多,代码逻辑复杂,使用mvp改造解耦,利于后期维护
任务拆分:
- 删除冗余代码,封装方法,可读性优化
- 将阅读页UI与业务逻辑分层,进行解耦
- 结合RxJava,优化消息分发逻辑
二、业务时序图/流程图
阅读页核心业务流程图

阅读页核心业务时序图
三、MVP一期类结构设计
ReaderPagePresenter类结构:
四、MVP一期操作步骤与注意点
1.操作步骤
遵循V->P->M的顺序逐步剥离代码
- 修改View层继承的接口,改为内部实现,方便转移到P层
- 剥离打开书籍业务到P层
- 剥离各类handler消息到P层
- 剥离各类控制业务到P层
- 剥离P层的数据请求到M层
2.注意点
1. 广播多,放在V层还是放在P层?
有多个业务使用广播,方法很长,查看都是业务相关,故放在P层
2. Handler怎么处理?
查看剥离后的P层代码,很多成员变量都会牵扯到Handler,这个怎么处理呢?
网上大部分的代码均使用回调的方式处理数据。即V-P-M-P-V
而我们的代码,拿到Handler以后,发送msg到V层处理,即V-P-M-V.
我们的Handler较多,处理起来难度较大,仿照书架P的处理方式,在P层获取到V的handler。
3. 空安全,内存泄漏?
MVP并非无缺点,空安全和内存泄漏需要注意。及时销毁PM,断开引用
五、MVP二期类结构设计
ReaderPagePresenter类结构:
六、MVP二期工作
一期可以先将V中的逻辑转移到P层中,二期可以对大P层进行拆分
1. 拆分多个PM层
借鉴网上开源项目思路,复杂的业务需求可拆分为多个PM,即一个V持有N个P,一个P持有多个M.进一步解耦庞大的P层代码。
目前耦合有三类,解耦思路为单例设计模式
1> 书籍信息类信息:即CurBook,autoBookmark。
1.当前书籍类型类(文本书,精排书),CurBook,
此变量维护当前书籍类型,并转型为各类书籍调用各自的方法,做成单例类,减少单个变量传递,这样就可解耦到M层获取
2.当前书籍类,autoBookmark

以前是一个字段autoBookmark来回修改传递,拆分多个M可以在用到这个mark的时候,用一个单例类解耦
2> 服务器书籍信息类:即OnlineChapterHandle,LocalChapterHandle。这两个在MVP其实充当的是M
1.OnlineChapterHandle负责在线书的书籍信息,章节解析等功能
这个类是比较复杂的一个类,充当为M类。作用有4,获取书籍类型(下载时使用),获取书籍信息和章节信息(保存到OnlineBookOperator中的OnlineBook对象中,包含整本书的信息+章节信息),获取书籍购买类型(查询书籍信息是前置条件,从内部的ob对象中直接拿即可),检测章节一致性
备注:获取书籍信息和章节信息前,还获取了批量购买协议内容,都保存到了OB对象中,此处可以使用rxjava的merge合并两次请求
2.LocalChapterHandle负责精排试读书的书籍信息等

内部维护了一个handler的注册表,初始化的时候注册到这个地方。查询完成后,通过handler把查询结果发送出去
一共有两个方法,获取书籍类型(在下载时使用),获取书籍信息(查询精排限免期)
3> 书籍下载类:即OnlineProvider,这个在MVP中也是M,负责下载章节

此类维护了两个对象,书籍下载的章节,和正在预取的章节。作用有2,获取章节(下载章节),维护下载章节队列
困难点:上述三个类考虑到复用问题(新增类还是复用,复用的话handler+听书如何复用),仍有待商榷
4>非耦合类ReaderPageModel
这个类,用来负责阅读页其他网络请求,比如广告,付费等
2. rxjava引入与handler
目前数据的处理散布在N个类内,比如OnlineProvider,OnlineChapterHandle,LocalChapterHandle等,这类数据相关的类,可以考虑移动到M层。
对V的所有操作,都是P层在控制,PM之间通过回调进行。可以逐步去掉handler的使用。
rxjava的引入。rxjava流式处理较好用,可以无缝切换线程。在项目中的体现就是:子线程拿数据(M)--> 切换--> 主线程展示UI(V).我们可以把rxjava运用到P层,来处理P层中涉及到这个操作的逻辑。
七、一期改造效果
仓库地址:http://xxxx/book_cooperate_client/cooperate-baseline.git (feature_xxxx_20181122_readerpage_mvp)
删减ReaderPageActivity类 初始行数13102,抽取结束后4853,代码减少63%,包含View交互。
新增ReaderPagePresenter类 行数6170,包含书籍加载+书籍控制+付费
新增ReaderPageModel类 行数231,包含数据请求
八、三期类结构设计
二期设计因为单例设计模式,在多个阅读页同时存在的情况下,会有实例问题。比如当前在读书籍Mark, 打开新阅读页的时候,会把老阅读页的单例Mark更新,导致老阅读页的书籍信息被修改。
所以对二期的设计进行了下修改,解决多阅读页问题。同时引入代理模式和观察者模式,解决多P协作,多P数据统一等问题。
ReaderPagePresenter类结构如下:

以下:ReaderPageActivity简称V,ReadpagePresenterImpl简称代理P,CommonPresenter等简称业务P,ReaderPageModel等简称M
代理模式
V持有代理P的引用,通过调用代理P的方法,完成P层逻辑。
引入代理P,一共有两个作用:
1.隐藏业务P的业务实现,可以进行后续业务扩展,不用更改V层代码
2.解决多P协作问题,如遇到多P才能完成的业务,可以写在代理P中,不用在V中进行周转
观察者模式
代理P是被观察者,业务P是观察者。代理P中持有一个观察者队列,ArrayList
当业务P中,某些需要同步的对象(如Mark)更新时,可以通知被观察者,更新观察者队列,做到了多P数据统一。
备注:因为业务涉及到多阅读页形式,无法使用EventBus,RxBus之类类库添加全局观察者,所以就实现了一个有局限性的观察者模式。如果以后无此类多阅读页需求,可以修改为三方类库实现,业务逻辑更加清晰一些。
疑问
通过拆分成不同的Iview,和Ipresenter.可以合理的规划业务边界,编写业务代码。但是也会引入两个新的问题
1.如果业务边界不清晰,怎么划分。即如何避免代理P增大的问题
2.如果业务变动频繁,怎么快速完成,改动量最小。