本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57bfef673c1174283d60bac0

Dev Club 是一个交流移动开发技术,结交朋友,扩展人脉的社群,成员都是经过审核的移动开发工程师。每周都会举行嘉宾分享,话题讨论等活动。

本期,我们邀请了腾讯IEG Android 开发工程师——戴俊,为大家分享《基于RxJava的一种MVP实现》。

分享内容简介:

RxJava是一个实现Java响应式编程的库,让异步事件以序列的形式组织。MVP则通常用来将View业务层与Model层分离开来,两者结合起来可轻松实现业务解耦、线程控制、单元测试等等强大功能

内容大体框架:

  1. Android开发框架的选择
  2. 如何一步步搭建MVP分层框架
  3. 使用RxJava来进行线程控制
  4. 结语

下面是本期分享内容整理


Hello,大家好,我是戴俊。目前在IEG腾讯动漫主要负责Android端的开发工作。

第一次进行这种微信群的分享,如果有任何疑问,欢迎大家在分享结束后提问。下面开始我们今天的分享。

1. Android开发框架的选择

我们知道原生Android开发已经是一个基础的MVC框架,所以在项目刚开始开发的时候并没有遇到太多问题。

对一个经典的Android MVC框架来讲,它的结构大概是下面这样(图片来自参考文献)

这样的结构下,Activity层既承担了View层的一部分工作(因为XML作为View层的一部分功能实在太弱了),又承担Controller层的工作,因此当业务变化时,Activity层会极剧膨胀。

拿我们项目早期的例子,一个Activity曾经最多达到了2000到3000行,重构的时候极其痛苦。

要解决这个问题,主要的办法有两种:

  • 第一种是分层
  • 第二种是模块化。

两个方法最终要实现的都是解耦。分层讲的是纵向层面上的解耦,模块化则是横向上的解耦。

我们今天要讨论的MVP就是一种通过分层来进行解耦的框架。

2. 如何一步步搭建MVP分层框架

如果你是个习惯了读文档的老司机,可以直接参考下面几篇文章

  1. Android Application Architecture
  2. Android Architecture Blueprints - Github
  3. Google官方MVP示例之TODO-MVP - 简书
  4. todo-mvp - github
  5. dev-todo-mvp-rxjava - github

当然如果觉得看官方的示例太麻烦,那么下面我们就来讲解一下如何实现一个简单的MVP构架。

这是一个比较典型的MVP结构图(图片来自参考文献),相比于第一张图,多了两个层,一个是Presenter和DataManager层。

多出这两个层到底有什么作用,下面我们来用代码说明。

首先我们假设有一个从服务端获取字符串并显示的手机上的简单功能。下面是主界面的代码

Activity里面包含了几个文件,一个是View层的对外接口MainView,一个是P层的Presenter。

首先看View层的对外接口文件

因为这个功能比较简单,只需要在设备上显示一个字符串,所以只有一个接口方法onShowString(),再看P层代码

从上面三个文件可以看到,View层通过注册Listener将自己的接口MainView交给了Presenter, 而Presenter层持有Model层的也只是一个接口。通过Presenter层将业务层与展现层隔离了开来,这样的好处是什么?

我们知道接口的一个作用通常是用来抽象行为,对外部屏蔽实现细节。所以对于View层来说,业务细节被屏蔽了,对业务层来说,展示细节被屏蔽了。而对于处于中间的Presenter层来说,它就像一个接口拼装器,把View层发出的请求传递给业务层,把业务层返回的数据又送还给View层展示,至于前后两端怎么实现的,它才不用关心。

接口的第二个作用是可以用来切换实现。我们先看下面的代码。

从上面三个文件可以看到,业务层对外的只有一个接口,实现却有两个(DataSourceImpl和DataSourceTestImpl)。从名字大家就能看出来有什么作用了,一个是正常环境的业务层实现,一个是测试环境的业务层实现。

这里我们设想一个场景:

开发同学接到一个新的需求,设计稿也输出完成了,然而后台的接口却迟迟没到,怎么办? 现在通过MVP,我们把业务层实现切换到DataSourceTestImpl,是不是可以先自己假写数据,调好一切前端和交互,然后泡一杯咖啡等后台同学把接口写完联调?或者有时候为了重现一个bug,要在线上写一条脏数据,测试完再删除?

类似的应用场景其实有非常非常多,这里我们就看到了使用接口解耦的一个好处了,不仅把业务层和展示层解耦开来,还把Android开发同其它的一切的外部数据依赖都解耦开来。

这里我想提到之前讨论过的单元测试问题,很多同学反馈项目开发过程中没有做过,或者没有时间精力去做单元测试,或者因为业务变化太大导致无法做单元测试。其实在我们项目中也遇到过样的问题,但其实通过这样分层之后,才发现单元测试其实是完全可以推进的,也完全不用再担心测试的时候会把脏数据写到线上的问题了。

到现在为止一个基于MVP简单框架就搭建完成了,但其实还遗留了一个比较大的问题。

很多同学可能已经发现了,Presenter层在调用业务层的时候是直接调用的,而Android规定,主线程是无法直接进行网络请求,会抛出NetworkOnMainThreadException异常。

所以在presenter层,我们需要进行一项线程切换的工作,这样才能保证“所有的IO操作都应当在线程中完成,主线程只负责页面渲染的工作”这一优化准则。

当然,Android本身提供一些方案,比如下面这种:

通过新建子线程进行IO读写获取数据,然后通过主线程的Looper将结果通过传回主线程进行展示,这种方案是勉强也行得通的。

但问题也有,一是线程需要额外管理,不可能每次发请求都要开启一个线程;二是适应性差,假如数据请求有先后依赖,有并行的情况,这样的写法变得脏乱无比。

好在有了RxJava ,可以比较方便的解决这个问题。

3. 使用RxJava来进行线程控制

RxJava是一个天生用来做异步的工具,相比AsyncTask,Handler等,它的优点就是简洁,无比的简洁。在Android中使用RxJava需要加入下面两个依赖。

 compile 'io.reactivex:rxjava:1.0.14'
compile 'io.reactivex:rxandroid:1.0.1'

这里我们直接介绍如何使用RxJava解决这个问题,在presenter中修改方法getData()。

简单解释一下,dataAction是我们的数据业务逻辑,viewAction是界面的显示逻辑,通过RxJava的传递和变换,dataAction会在由RxJava管理的IO线程—Schedulers.io() 中执行,而viewAction则会在UI线程—AndroidSchedulers.mainThread()中执行。

RxJava当然不止这么简单,还有别的玩法,比方说进入一个界面的时候,需要先加载缓存的数据,然后再从网络获取更新的数据进行刷新。有的时候,可能还需要处理IO过程中的异常情况,加入RxJava的异常处理参数。

RxJava的使用场景远不止这些,线程变换、数据变换、接口顺序依赖、接口并发请求这些要求对它来说都是小菜一碟。当然,有些同学可能觉得RxJava入手有些困难,代码也会变得不那么直观,但相信只要大家慢慢熟悉它之后,它就会变得无比讨人喜欢。

下面列出了一些常见的RxJava的常用场景,其实还有更多的其它功能等待着大家去挖掘。

  1. 取数据先检查缓存的场景
  2. 需要等到多个接口并发取完数据,再更新
  3. 一个接口的请求依赖另一个API请求返回的数据
  4. 界面按钮需要防止连续点击的情况
  5. 响应式的界面
  6. 复杂的数据变换

上面这些功能都可以通过RxJava来轻松完成。具体的使用就不再多讲了,大家可以参考下面的文章:(Google文章名就可以了)

1.给 Android 开发者的 RxJava 详解
2.RxJava 与 Retrofit 结合的最佳实践
3.RxJava使用场景小结
4.How To Use RxJava

结语

至此为止,通过MVP+RxJava的组合,我们已经构建出一个比MVC更灵活的Android项目开发框架,好处大概有以下几点:

  1. 每层各自独立,通过接口通信
  2. 实现与接口分离实现,不同场景(正式,测试)挂载不同的实现,方便测试写假数据
  3. 所有的业务逻辑都在非UI线程中进行,最大限度减少IO操作对UI的影响
  4. 使用RxJava可以将复杂的调用进行链式组合,解决多重回调嵌套问题

以上就是我今天的分享,内容上可能还有不足甚至不够好的地方,欢迎大家指出,一起讨论学习。

这里也顺便打个广告,欢迎大家下载腾讯动漫App,这里有最新最热的国漫日漫,支持正版,你我共享。

问答环节

Q1:对于这样的一个MVP项目,它里面的包结构怎样规划比较清晰合理呢?

包结构的通常分法有两种:一种是按功能模块分,把某一个功能的presenter, activity,view层接口放到一起;一种是按类型分,P层M层和V层分成三个包。实际项目应用,我个人倾向于第一种,这种无论是开发过程,还是排查问题都会方便很多。当然,不同的项目还是有不同的分法的,不一而论。

Q2:耗时操作可能引起的内存泄露问题,请问是如何处理的。
Q3:用mvp时,请问你们在哪里释放一些引用,防止内存泄露的
Q4:p持有v的引用,请问怎么解决Activity的内存泄露问题?
Q5:网特别慢的时候,应用退出,但网络请求还没结束,p层回调持有上下文造成内存泄露,一般怎么解决啊。

这几个问题其实比较类似,我们在实际项目中,presenter会随着activity的生命周期进行销毁,比如在onDestroy方法中对presenter进行置空和引用解绑, 当然我们可以给所有的Presenter写一个共有父类BasePresenter,专门来处理这个问题。

Q6:需求包含列表页的时候,列表项也是按照mvp的思想来分层,还是封装成模块比较合适

目前我们的做法是直接封装成模块,简单的问题不宜过度设计

Q7:想问一下腾讯动漫这个app目前用的就是您讲的这个架构吗,在实际用的过程中有遇到什么问题吗

是的,我们已经使用了这个架构。实际使用过程中,经常会纠结的问题是业务逻辑层要不要再次独立分层。

Q8:项目中做测试是好事,但我觉得建议去掉TestImpl测试文件。如果项目打包时,打到包里,会导致包变大,这种测试建议用node写个简单的服务,不知道嘉宾你咋看?

是的。正式项目中,可以通过注解,或者proguard或者gradle的配置将这些测试文件不打到包里。Node写服务的话是不是又要搭环境,这里的做法就是不使用任何外部环境依赖。

Q9:mvp一般都是activity和Fragment加入presenter层,那么列表adapter里的逻辑是否也要加上presenter层呢

Adapter其实跟View更接近的一个东西,它是用来处理重复显示问题。一般来说,我们传给adapter的数据完好能直接显示的,建议在业务逻辑层将数据拼装好再传进去。
答:Adapter其实跟View更接近的一个东西,它是用来处理重复显示问题。一般来说,我们传给adapter的数据完好能直接显示的,建议在业务逻辑层将数据拼装好再传进去。

Q10:我们项目中采用了MVP但是没有用RxJava,m与p层采用回调方式,这样m通过回调间接引用p,p层有v的引用。如果在网络情况不好频繁打开关闭页面在网络请求结束前是否会有内存泄漏问题。rx是否能解决这个问题。还有当网络结束回调时v对应activity destory了怎么办。每次都去判断activity状态吗?

Rx不能解决内存泄漏的问题,前面2.3.7问题都提到了,通常的做法是在activity层销毁的时候进行解绑。回调时activity destory的话,我们现在的做法是对view层接口进行一次空值判定。如果有更好的办法,也欢迎大家提出来讨论

Q11:有时候例如自定义view依赖于服务器返回的model,里面也有很多根据model属性去绘制的过程,这种情况怎么处理?在P层抛出一个model的get方法吗?

自定义的View跟Activity一样,我们统称为View层。上面的例子中View层只有一个接口MainView,实际项目中,View层可能会实现好几个接口。对一个经常会被利用的自定义View,会额外给它新建一个接口。

Q12:你的例子中p层实现中getDate()方法对数据进行了处理,是否m层只是单纯的获取原始数据,对于数据上的业务也放入到p层中处理,有没有好的方式能够复用有关数据业务的这块逻辑

嗯,这个问题我们确实也遇到了。在项目实际操作过程中,如果有比较复杂业务流程,我会单独再分离出一层业务层,业务层再去调用dataSource取数据。如果只是单纯的取数据展示,现在这样就够了,尽量避免过度设计。

Q13:为了更好的解偶每一层,你们用MVP时 是否每层都有自己的数据结构,如果有的话,层与层之间的数据结构转换开销大不大?

目前来讲,大部分的业务都是一个数据结构穿透使用的,偶尔会有数据结构重新封装, 影响不大。我个人判断的话,相比IO处理,数据结构的转换开销还是小的,而且,如果有很多复杂转换的话,保证不要在UI线程中做,也不会太大问题。

Q14:activity与p层用接口的方式衔接的价值在哪?另外如何界定展现方法在哪调用?比如页面需要显示一个标题,内容是从之前页面传过来的,那是在activity接收后就直接显示?还是先传递到p层再回调activity的显示方法?感谢

价值在于,把presenter 与activity解耦之后,我可以在别的activity使用这个presenter层逻辑,也可以在这个activity 里调用其它页面的presenter方法。如果是前页传过来的,直接显示就好,不做过度设计。

Q15:rxJava使用lamaba的语法格式的话貌似会将代码缩减很多,请问嘉宾有试过这种方式吗?这个对项目的性能会有什么影响吗?因为我试用过几次后一直出现oom的问题

lambda表达式会让语法看起来更简洁,非常推荐使用。但我们的项目目前只能使用jdk 7,悲伤。如果后面我们有机会切换的话,可以再一起分享一下。

Q16:rxjava怎么实现队列像handler message那样,就是队列执行,不是并发执行?

rxJava中的just方法和from方法都是以队列形式发出事件。我猜你想问的问题可能是:一个接口的请求依赖另一个API请求返回的数据,这就是嵌套回调问题。可以找下大头鬼Bruce的一篇文章,《RxJava使用场景小结》,里面有介绍的,这里不详细讨论了。

更多精彩内容欢迎关注bugly的微信公众账号:

腾讯 Bugly是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的情况以及解决方案。智能合并功能帮助开发同学把每天上报的数千条 Crash 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码行,实时上报可以在发布后快速的了解应用的质量情况,适配最新的 iOS, Android 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧!

【腾讯Bugly干货分享】基于RxJava的一种MVP实现的更多相关文章

  1. 【腾讯Bugly干货分享】基于 Webpack & Vue & Vue-Router 的 SPA 初体验

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57d13a57132ff21c38110186 导语 最近这几年的前端圈子,由于 ...

  2. 【腾讯Bugly干货分享】彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/qOMO0LIdA47j3RjhbCWUEQ 作者:李 ...

  3. 【腾讯Bugly干货分享】深入源码探索 ReactNative 通信机制

    Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 本文从源码角度剖析 RNA 中 J ...

  4. 【腾讯Bugly干货分享】微信iOS SQLite源码优化实践

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57b58022433221be01499480 作者:张三华 前言 随着微信iO ...

  5. 【腾讯Bugly干货分享】微信热补丁Tinker的实践演进之路

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57ad7a70eaed47bb2699e68e Dev Club 是一个交流移动 ...

  6. 【腾讯Bugly干货分享】H5 视频直播那些事

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57a42ee6503dfcb22007ede8 Dev Club 是一个交流移动 ...

  7. 【腾讯Bugly干货分享】JSPatch 成长之路

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/579efa7083355a9a57a1ac5b Dev Club 是一个交流移动 ...

  8. 【腾讯Bugly干货分享】一步一步实现Android的MVP框架

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/5799d7844bef22a823b3ad44 内容大纲: Android 开发 ...

  9. 【腾讯Bugly干货分享】React Native项目实战总结

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/577e16a7640ad7b4682c64a7 “8小时内拼工作,8小时外拼成长 ...

随机推荐

  1. 01.SQLServer性能优化之----强大的文件组----分盘存储

    汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 文章内容皆自己的理解,如有不足之处欢迎指正~谢谢 前天有学弟问逆天:“逆天,有没有一种方 ...

  2. 关于解决python线上问题的几种有效技术

    工作后好久没上博客园了,虽然不是很忙,但也没学生时代闲了.今天上博客园,发现好多的文章都是年终总结,想想是不是自己也应该总结下,不过现在还没想好,等想好了再写吧.今天写写自己在工作后用到的技术干货,争 ...

  3. 使用Visual Studio 2015 开发ASP.NET MVC 5 项目部署到Mono/Jexus

    最新的Mono 4.4已经支持运行asp.net mvc5项目,有的同学听了这句话就兴高采烈的拿起Visual Studio 2015创建了一个mvc 5的项目,然后部署到Mono上,浏览下发现一堆错 ...

  4. 【Big Data】HADOOP集群的配置(一)

    Hadoop集群的配置(一) 摘要: hadoop集群配置系列文档,是笔者在实验室真机环境实验后整理而得.以便随后工作所需,做以知识整理,另则与博客园朋友分享实验成果,因为笔者在学习初期,也遇到不少问 ...

  5. Oracle SQL Developer 连接 MySQL

    1. 在ORACLE官网下载Oracle SQL Developer第三方数据库驱动 下载页面:http://www.oracle.com/technetwork/developer-tools/sq ...

  6. SQL中字符串拼接

    1. 概述 在SQL语句中经常需要进行字符串拼接,以sqlserver,oracle,mysql三种数据库为例,因为这三种数据库具有代表性. sqlserver: select '123'+'456' ...

  7. 深入理解Spring MVC

    如何让一个普通类成为Controller? 方案一:实现接口Controller 解析:handleRequest(request,response) 方案二:继承AbstractController ...

  8. Lesson 18 He often does this!

    Text After I had had lunch at a village pub, I looked for my bag. I had left it on a chair beside th ...

  9. C# 委托Delegate(一) 基础介绍&用法

    本文是根据书本&网络 前人总结的. 1. 前言 定义&介绍: 委托Delegate是一个类,定义了方法的类型, 使得可以将方法当做另一个方法的参数来进行传递,这种将方法动态地赋给参数的 ...

  10. MySQL有趣的查询方式

    背景介绍 美国大选开始了,国防部要求我对两个总统候选人的票数进行统计.我首先简单的进行一次无条件查询,了解了一下表格的结构及所有数据长什么样子. select * from foo 查询到的结果令我很 ...