包含结目的sootodo list所有页面,现在以wwWsootodo listcom无法在显示了

Google官方MVP模式示例项目解析 todo-mvp - tnnowu - 博客园
博客停更,新的博客迁移到简书。http://www.jianshu.com/u/c219ab07dca0
转载请注明出处:&
引言:在Google没有给出一套权威的架构实现之前,很多App项目在架构方面都有或多或少的问题。第一种常见问题是没有架构,需求中的一个页面对应项目中的一个activity或一个fragment,所有的界面响应代码、业务逻辑代码、数据请求代码等等都集中在其中。第二种常见的问题是架构实现的不断变化,不断在各种架构间摇摆,一直找不到一个适合自己的架构。
Google官方示例项目地址&
Google提供这个示例项目有两个目的:
Provide a basic Model-View-Presenter (MVP) architecture without using any architectural frameworks.
Act as a reference point for comparing and contrasting the other samples in this project.
中文解释:
提供了一个基础的MVP架构,而不是用其他的架构。
用这个项目和其他类似的做一个参考对比。
当然Google也明确表示了这些示例只是用来做参考,而并不是要为了当做标准
下面我们从源码的角度来分析todo-mvp(mvp基础架构示例)的实现。我们先从项目的整体组织方式开始,再看项目究竟使用了哪些组件,最后当然是最重要的具体mvp的实现方式。
先看一下项目代码组织方式:
项目含一个app src目录,4个测试目录,分别是androidTest(UI层测试)、androidTestMock(UI层测试mock数据支持)、test(业务层单元测试)、mock(业务层单元测试mock数据支持)。
src目录的代码组织方式完全是按照功能来组织的,功能内部分为xActivity、xContract、xFragment、xPresenter四个类文件(x代表业务名称)。
由于项目是基于gradle进行编译的,所以我们可以从build.gradle文件看到项目依赖的全貌。
dependencies {
// App's dependencies, including test
compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
compile "com.android.support:cardview-v7:$rootProject.supportLibraryVersion"
compile "com.android.support:design:$rootProject.supportLibraryVersion"
compile "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion"
compile "com.android.support:support-v4:$rootProject.supportLibraryVersion"
compile "com.android.support.test.espresso:espresso-idling-resource:$rootProject.espressoVersion"
compile "com.google.guava:guava:$rootProject.guavaVersion"
// Dependencies for local unit tests
testCompile "junit:junit:$rootProject.ext.junitVersion"
testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testCompile "org.hamcrest:hamcrest-all:$rootProject.ext.hamcrestVersion"
// Android Testing Support Library's runner and rules
androidTestCompile "com.android.support.test:runner:$rootProject.ext.runnerVersion"
androidTestCompile "com.android.support.test:rules:$rootProject.ext.runnerVersion"
// Dependencies for Android unit tests
androidTestCompile "junit:junit:$rootProject.ext.junitVersion"
androidTestCompile "org.mockito:mockito-core:$rootProject.ext.mockitoVersion"
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
// Espresso UI Testing
androidTestCompile "com.android.support.test.espresso:espresso-core:$rootProject.espressoVersion"
androidTestCompile "com.android.support.test.espresso:espresso-contrib:$rootProject.espressoVersion"
androidTestCompile "com.android.support.test.espresso:espresso-intents:$rootProject.espressoVersion"
// Resolve conflicts between main and test APK:
androidTestCompile "com.android.support:support-annotations:$rootProject.supportLibraryVersion"
androidTestCompile "com.android.support:support-v4:$rootProject.supportLibraryVersion"
androidTestCompile "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion"
androidTestCompile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
androidTestCompile "com.android.support:design:$rootProject.supportLibraryVersion"
项目中使用到了Guava库,官网地址&
该库是Google在基于java的项目中都会引用到得一个库,库中包含大约14k的方法数,是个很大的库,其中包含了集合、缓存、并发、基本注解、字符串处理、io处理等等。项目中使用Guava库主要是处理null这种不安全的情况,因为一般我们在使用有可能为null的对象时,一般会增加一次判断。比如项目中的出现的:
public boolean isEmpty() {
return Strings.isNullOrEmpty(mTitle) &&
Strings.isNullOrEmpty(mDescription);
这样面对空的时候,就不用再多写很多代码了,确实是方便了很多。但是不建议为了null安全直接引入如此大的一个库,因为我们都知道android apk的65k方法数限制,如果要用的话可以把源码中涉及到得部分直接拿出来用。当然Guava中还有很多重要的功能,其他功能读者可以自行研究,关于Guava就先到这里了。
测试相关组件
示例项目在可测试方面做的非常好,由于对视图逻辑(view层)和业务逻辑(presenter层)进行了拆分,所以我们就可以对UI、业务代码分别进行测试。为了进行UI测试引入了Espresso,为了对业务层进行单元测试引入了junit,为了生成测试mock对象引入了mockito,为了支撑mockito又引入了dexmaker,hamcrest的引入使得测试代码的匹配更接近自然语言,可读性更高,更加灵活。
重头戏:项目MVP实现方式
两个Base接口 BasePresenter 和 BaseView,这两个类分别是 presenter 和 view 的基类。
public interface BasePresenter {
void start();
BasePresenter 中含有方法 start(),该方法的作用是 presenter 开始获取数据并调用 view 中方法改变界面显示,其调用时机是在 Fragment 类的 onResume 方法中。
项目中调用 start() 的地方:
public interface BaseView&T& {
void setPresenter(T presenter);
BaseView 中含方法 setPresenter(),该方法作用是在将 presenter 实例传入 view 中,其调用时机是 presenter 实现类的构造函数中。
项目中调用 setPresenter() 的地方:
与之前见到的所有mvp实现都不同,Google官方的实现中加入了契约类来统一管理view与presenter的所有的接口,这种方式使得view与presenter中有哪些功能,一目了然,维护起来也方便,实例如下:
public interface TasksContract {
interface View extends BaseView&Presenter& {
void setLoadingIndicator(boolean active);
void showTasks(List&Task& tasks);
void showAddTask();
interface Presenter extends BasePresenter {
void result(int requestCode, int resultCode);
void loadTasks(boolean forceUpdate);
void addNewTask();
3.Activity在MVP中的作用
Activity 在项目中是一个全局的控制者,负责创建 view 以及 presenter 实例,并将二者联系起来,下面是 Activity 中创建 view 及 presenter 的代码:
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
// Create the presenter
mTasksPresenter = new TasksPresenter(
Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
我们可以从上面看到整个创建过程,而且要注意的是创建后的 Fragment 实例作为 presenter 的构造函数参数被传入,这样就可以在 presenter 中调用 view 中的方法了。
4.MVP的实现与组织
实例中将 Fragment 作为 view 层的实现类,为什么是 Fragment 呢?
有两个原因:
我们把 Activity 作为一个全局控制类来创建对象,把 Fragment 作为 view,这样两者就能各司其职。
因为 Fragment 比较灵活,能够方便的处理界面适配的问题。
我们先看 view 的实现,我们只挑一部分重要的方法来看
public class TasksFragment extends Fragment implements TasksContract.View {
public void onResume() {
super.onResume();
mPresenter.start();
public void setPresenter(@NonNull TasksContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
上面可以看到 setPresenter() 方法,该方法继承于父类,通过该方法,view 获得了 presenter 得实例,从而可以调用 presenter 代码来处理业务逻辑。我们看到在 onResume 中还调用了 presenter 得 start() 方法。
下面我们再看presenter的实现
public class TasksPresenter implements TasksContract.Presenter {
public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");
mTasksView.setPresenter(this);
public void start() {
loadTasks(false);
presenter 构造函数中调用了 view 的 setPresenter() 方法将自身实例传入,start() 方法中处理了数据加载与展示。如果需要界面做对应的变化,直接调用 view 层的方法即可,这样 view 层与 presenter 层就能够很好的被划分。
最后还剩下 model 层实现,项目中 model 层最大的特点是被赋予了数据获取的职责,与我们平常 model 层只定义实体对象截然不同,实例中,数据的获取、存储、数据状态变化都是 model 层的任务,presenter 会根据需要调用该层的数据处理逻辑并在需要时将回调传入。这样 model、presenter、view 都只处理各自的任务,此种实现确实是单一职责最好的诠释。
我们再来整体看下官方的实现方式有哪些特性。首先是复杂度,我们可以从上面的分析看出整体的复杂度还是较低的,易学的;然后是可测试性,由于将UI代码与业务代码进行了拆分,整体的可测试性非常的好,UI层和业务层可以分别进行单元测试;最后是可维护性和可扩展性,由于架构的引入,虽然代码量有了一定的上升,但是由于界限非常清晰,各个类职责都非常明确且单一,后期的扩展,维护都会更加容易。
关注,获取更多Android开发资讯!关注,领略科技、创新、教育以及最大化人类智慧与想象力!最近在进行前端面试方面的一些准备,看了网上许多相关的文章,发现有一个问题始终绕不开: 在浏览器中输入URL到整个页面显示在用户面前时这个过程中到底发生了什么。仔细思考这个问题,发现确实很深,这个过程涉及到的东西很多。这个问题的回答真的能够很好的考验一个web工程师的水平,于是我自问自答一番。
总体来说分为以下几个过程:
发送HTTP请求
服务器处理请求并返回HTTP报文
浏览器解析渲染页面
DNS解析的过程就是寻找哪台机器上有你需要资源的过程。当你在浏览器中输入一个地址时,例如www.baidu.com,其实不是百度网站真正意义上的地址。互联网上每一台计算机的唯一标识是它的IP地址,但是IP地址并不方便记忆。用户更喜欢用方便记忆的网址去寻找互联网上的其它计算机,也就是上面提到的百度的网址。所以互联网设计者需要在用户的方便性与可用性方面做一个权衡,这个权衡就是一个网址到IP地址的转换,这个过程就是DNS解析。它实际上充当了一个翻译的角色,实现了网址到IP地址的转换。网址到IP地址转换的过程是如何进行的?
DNS解析是一个递归查询的过程。
上述图片是查找www.google.com的IP地址过程。首先在本地域名服务器中查询IP地址,如果没有找到的情况下,本地域名服务器会向根域名服务器发送一个请求,如果根域名服务器也不存在该域名时,本地域名会向com顶级域名服务器发送一个请求,依次类推下去。直到最后本地域名服务器得到google的IP地址并把它缓存到本地,供下次查询使用。从上述过程中,可以看出网址的解析是一个从右向左的过程: com -& google.com -& www.google.com。但是你是否发现少了点什么,根域名服务器的解析过程呢?事实上,真正的网址是www.google.com.,并不是我多打了一个.,这个.对应的就是根域名服务器,默认情况下所有的网址的最后一位都是.,既然是默认情况下,为了方便用户,通常都会省略,浏览器在请求DNS的时候会自动加上,所有网址真正的解析过程为: . -& .com -& google.com. -& www.google.com.。
了解了DNS的过程,可以为我们带来哪些?上文中请求到google的IP地址时,经历了8个步骤,这个过程中存在多个请求(同时存在UDP和TCP请求,为什么有两种请求方式,请自行查找)。如果每次都经过这么多步骤,是否太耗时间?如何减少该过程的步骤呢?那就是DNS缓存。
DNS存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。
在你的chrome浏览器中输入:chrome://dns/,你可以看到chrome浏览器的DNS缓存。
系统缓存主要存在/etc/hosts(Linux系统)中:
DNS负载均衡
不知道大家有没有思考过一个问题: DNS返回的IP地址是否每次都一样?如果每次都一样是否说明你请求的资源都位于同一台机器上面,那么这台机器需要多高的性能和储存才能满足亿万请求呢?其实真实的互联网世界背后存在成千上百台服务器,大型的网站甚至更多。但是在用户的眼中,它需要的只是处理他的请求,哪台机器处理请求并不重要。DNS可以返回一个合适的机器的IP给用户,例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等,这种过程就是DNS负载均衡,又叫做DNS重定向。大家耳熟能详的CDN(Content Delivery Network)就是利用DNS的重定向技术,DNS服务器会返回一个跟用户最接近的点的IP地址给用户,CDN节点的服务器负责响应用户的请求,提供所需的内容。在这里打个免费的广告,我平时使用的比较多的是七牛云的CDN(免费)储存图片,作为我个人博客的图床使用。
HTTP协议是使用TCP作为其传输层协议的,当TCP出现瓶颈时,HTTP也会受到影响。但由于TCP优化这一块我平常接触的并不是很多,再加上大学时的计算机网络的基础基本上忘完,所以这一部分我也就不在这里分析了。
我不知道把HTTPS放在这个部分是否合适,但是放在这里好像又说的过去。HTTP报文是包裹在TCP报文中发送的,服务器端收到TCP报文时会解包提取出HTTP报文。但是这个过程中存在一定的风险,HTTP报文是明文,如果中间被截取的话会存在一些信息泄露的风险。那么在进入TCP报文之前对HTTP做一次加密就可以解决这个问题了。HTTPS协议的本质就是HTTP + SSL(or TLS)。在HTTP报文进入TCP报文之前,先使用SSL对HTTP报文进行加密。从网络的层级结构看它位于HTTP协议与TCP协议之间。
HTTPS在传输数据之前需要客户端与服务器进行一个握手(TLS/SSL握手),在握手过程中将确立双方加密传输数据的密码信息。TLS/SSL使用了非对称加密,对称加密以及hash等。具体过程请参考经典的阮一峰先生的博客。HTTPS相比于HTTP,虽然提供了安全保证,但是势必会带来一些时间上的损耗,如握手和加密等过程,是否使用HTTPS需要根据具体情况在安全和性能方面做出权衡。
其实这部分又可以称为前端工程师眼中的HTTP,它主要发生在客户端。发送HTTP请求的过程就是构建HTTP请求报文并通过TCP协议中发送到服务器指定端口(HTTP协议80/8080, HTTPS协议443)。HTTP请求报文是由三部分组成:&请求行,&请求报头和请求正文。
格式如下:Method Request-URL HTTP-Version CRLF
eg: GET index.html HTTP/1.1
常用的方法有: GET, POST, PUT, DELETE, OPTIONS, HEAD。
GET和POST有什么区别?
请求报头允许客户端向服务器传递请求的附加信息和客户端自身的信息。PS: 客户端不一定特指浏览器,有时候也可使用Linux下的CURL命令以及HTTP客户端测试工具等。常见的请求报头有: Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等。
上图是使用Chrome开发者工具截取的对百度的HTTP请求以及响应报文,从图中可以看出,请求报头中使用了Accept, Accept-Encoding, Accept-Language, Cache-Control, Connection, Cookie等字段。Accept用于指定客户端用于接受哪些类型的信息,Accept-Encoding与Accept类似,它用于指定接受的编码方式。Connection设置为Keep-alive用于告诉客户端本次HTTP请求结束之后并不需要关闭TCP连接,这样可以使下次HTTP请求使用相同的TCP通道,节省TCP连接建立的时间。
当使用POST, PUT等方法时,通常需要客户端向服务器传递数据。这些数据就储存在请求正文中。在请求包头中有一些与请求正文相关的信息,例如: 现在的Web应用通常采用Rest架构,请求的数据格式一般为json。这时就需要设置Content-Type: application/json。
自然而然这部分对应的就是后端工程师眼中的HTTP。后端从在固定的端口接收到TCP报文开始,这一部分对应于编程语言中的socket。它会对TCP连接进行处理,对HTTP协议进行解析,并按照报文格式进一步封装成HTTP Request对象,供上层使用。这一部分工作一般是由Web服务器去进行,我使用过的Web服务器有Tomcat, Jetty和Netty等等。
HTTP响应报文也是由三部分组成:&状态码,&响应报头和响应报文。
状态码是由3位数组成,第一个数字定义了响应的类别,且有五种可能取值:
1xx:指示信息&表示请求已接收,继续处理。
2xx:成功&表示请求已被成功接收、理解、接受。
3xx:重定向&要完成请求必须进行更进一步的操作。
4xx:客户端错误&请求有语法错误或请求无法实现。
5xx:服务器端错误&服务器未能实现合法的请求。平时遇到比较常见的状态码有:200, 204, 301, 302, 304, 400, 401, 403, 404, 422, 500(分别表示什么请自行查找)。
301和302有什么区别?
该图是本公司对状态码的一个总结,绘制而成的status code map,请大家参考。
常见的响应报头字段有: Server, Connection...。
服务器返回给浏览器的文本信息,通常HTML, CSS, JS, 图片等文件就放在这一部分。
浏览器在收到HTML,CSS,JS文件后,它是如何把页面呈现到屏幕上的?下图对应的就是WebKit渲染的过程。
浏览器是一个边解析边渲染的过程。首先浏览器解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。这个过程比较复杂,涉及到两个概念: reflow(回流)和repain(重绘)。DOM节点中的各个元素都是以盒模型的形式存在,这些都需要浏览器去计算其位置和大小等,这个过程称为当盒模型的位置,大小以及其他属性,如颜色,字体,等确定下来之后,浏览器便开始绘制内容,这个过程称为repain。页面在首次加载时必然会经历reflow和repain。reflow和repain过程是非常消耗性能的,尤其是在移动设备上,它会破坏用户体验,有时会造成页面卡顿。所以我们应该尽可能少的减少reflow和repain。
JS的解析是由浏览器中的JS解析引擎完成的。JS是单线程运行,也就是说,在同一个时间内只能做一件事,所有的任务都需要排队,前一个任务结束,后一个任务才能开始。但是又存在某些任务比较耗时,如IO读写等,所以需要一种机制可以先执行排在后面的任务,这就是:同步任务(synchronous)和异步任务(asynchronous)。JS的执行机制就可以看做是一个主线程加上一个任务队列(task queue)。同步任务就是放在主线程上执行的任务,异步任务是放在任务队列中的任务。所有的同步任务在主线程上执行,形成一个执行栈;异步任务有了运行结果就会在任务队列中放置一个事件;脚本运行时先依次运行执行栈,然后会从任务队列里提取事件,运行任务队列中的任务,这个过程是不断重复的,所以又叫做事件循环(Event loop)。
浏览器在解析过程中,如果遇到请求外部资源时,如图像,iconfont,JS等。浏览器将重复1-6过程下载该资源。请求过程是异步的,并不会影响HTML文档进行加载,但是当文档加载过程中遇到JS文件,HTML文档会挂起渲染过程,不仅要等到文档中JS文件加载完毕还要等待解析执行完毕,才会继续HTML的渲染过程。原因是因为JS有可能修改DOM结构,这就意味着JS执行完成前,后续所有资源的下载是没有必要的,这就是JS阻塞后续资源下载的根本原因。CSS文件的加载不影响JS文件的加载,但是却影响JS文件的执行。JS代码执行前浏览器必须保证CSS文件已经下载并加载完毕。
上面部分主要介绍了一次完整的请求对应的过程,了解该过程的目的无非就是为了Web优化。在谈到Web优化之前,我们回到一个更原始的问题,Web前端的本质是什么。我的理解是: 将信息快速并友好的展示给用户并能够与用户进行交互。快速的意思就是在尽可能短的时间内完成页面的加载,试想一下当你在淘宝购买东西的时候,淘宝页面加载了10几秒才显示出物品,这个时候你还有心情去购买吗?怎么快速的完成页面的加载呢?优雅的学院派雅虎给出了常用的一些手段,也就是我们熟悉的。这34军规实际上就是围绕请求过程进行的一些优化方式。
如何尽快的加载资源?答案就是能不从网络中加载的资源就不从网络中加载,当我们合理使用缓存,将资源放在浏览器端,这是最快的方式。如果资源必须从网络中加载,则要考虑缩短连接时间,即DNS优化部分;减少响应内容大小,即对内容进行压缩。另一方面,如果加载的资源数比较少的话,也可以快速的响应用户。当资源到达浏览器之后,浏览器开始进行解析渲染,浏览器中最耗时的部分就是reflow,所以围绕这一部分就是考虑如何减少reflow的次数。
写这篇文章真的非常纠结,前前后后断断续续写了两个星期,因为涉及到的东西比较多,再加上有些东西记忆的没有那么清晰了,所以不好下笔。所涉及到的大部分内容,也基本上是一笔带过,只是给读者一个浅显的认知,当遇到相关的问题时,知道如何去查询。大家可以当成一篇Web开发的科普类文章去阅读。
另外在这里为公司的产品打个广告,在Chrome store中搜索,这是一款超级好用的Web客户端工具,囊括了很多的功能: 报文分析,API测试等等,可谓说是WEB工程师必备工具。
转载自https://segmentfault.com/a/9700
阅读(...) 评论() &Android Architecture Blueprints 学习之 TODO-MVP(一) | JudyMaxiee's blog
TODO-MVP 是
中讲解的第一种架构,是一个基本的 MVP 架构。
TasksActivity
APP 的入口是 TasksActivity。
它主要包含有两个成员:
TasksFragment:待做清单的主界面(V)
TasksPresenter:对应的 Presenter(P)
TasksRepository: 数据源(M)
MVP 是如何建立起关系来的?
首先,M 只在 P 中使用,与 V 无关,因此 M 只要传入 P 中即可。
P 与 V 之间的关系是这样的:V 和 P 互相保存对方的实例。V 在需要进行数据操作逻辑的时候不自己做,而是交给 P 来做,P 完成之后调用 V 中的方法实现界面更新。
来看对应的代码如下:
首先创建 TasksFragment,并将 Fragment 添加到 Activity 中:
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
这里用到一个工具类 ActivityUtils 的函数 addFragmentToActivity,实现为:
public static void addFragmentToActivity (@NonNull FragmentManager fragmentManager,
@NonNull Fragment fragment, int frameId) {
checkNotNull(fragmentManager);
checkNotNull(fragment);
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(frameId, fragment);
transaction.commit();
这里插一句与主题无关的话,一个创建 Fragment,Google 写得如此严谨,还有向 checkNotNull 这种第一次见到函数,我还是得学习一个啊。
创建 M 和创建 P 是在一句话里完成的:
// Create the presenter
mTasksPresenter = new TasksPresenter(
Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
Injection.provideTasksRepository(getApplicationContext())
这一句就创建了 M 数据源,这里用到了依赖注入,这里的依赖不是靠框架完成的,手动指定的。这里就不展开了,总之这句返回一个 TasksRepository 实例。
P 和 V 相互保存对方的实例发生在 TasksPresenter 的构造函数当中:
public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");
mTasksView.setPresenter(this);
第一句是将 M 存进 P,第二句是将 V 存进 P,第三句是将 P 存进 V。
TasksActivity V 与 P 的功能划分
在不使用 MVP 的传统结构当中,Activity / Fragment 除了界面的显示逻辑之外,往往还需要实现判断逻辑、数据加载逻辑,就导致结构非常复杂,不好维护。
在 MVP 结构当中,V 中只保留与界面显示有关的逻辑,将判断逻辑和数据加载等都移到 P 中去,这样就实现了一个拆分,使得结构变清晰,因此可维护性就提高了。
对于 TasksActivity 这个 TODO list 应用的主界面而言,V 和 P 具体应该如何划分呢?
下图是 TasksActivity 的界面:
从界面中,我们可以看出 TasksActivity 具备以下功能:
点击添加 FAB 进入 AddEditTaskActivity 添加页面
点击 TODO item 进入详情页
点击 TODO item 的勾设为已完成
过滤菜单:所有、活动状态、已完成
菜单:清除已完成、刷新
添加完成弹出 SnackBar 提示成功
生命周期:onResume、onActivityResult
对于界面上的这些功能,需要进一步拆分,将数据有关的放入 P,将显示有关的留在 V,还有一点要强调的是,P 需要绑定生命周期,即在 P 中创建同名方法,在 V 的生命周期中调用,这样保证 P 中的数据源响应生命周期,能及时更新状态。
V、P 的接口
上一节中总结了 TasksActivity 的功能,并思考如何将功能分别拆放到 V、P 中。
TasksContract 接口中就存放了拆分的结果,它包含两个接口 View 和 Presenter,其内容如下:
public interface TasksContract {
interface View extends BaseView&Presenter& {
void setLoadingIndicator(boolean active);
void showTasks(List&Task& tasks);
void showAddTask();
void showTaskDetailsUi(String taskId);
void showTaskMarkedComplete();
void showTaskMarkedActive();
void showCompletedTasksCleared();
void showLoadingTasksError();
void showNoTasks();
void showActiveFilterLabel();
void showCompletedFilterLabel();
void showAllFilterLabel();
void showNoActiveTasks();
void showNoCompletedTasks();
void showSuccessfullySavedMessage();
boolean isActive();
void showFilteringPopUpMenu();
interface Presenter extends BasePresenter {
void result(int requestCode, int resultCode);
void loadTasks(boolean forceUpdate);
void addNewTask();
void openTaskDetails(@NonNull Task requestedTask);
void completeTask(@NonNull Task completedTask);
void activateTask(@NonNull Task activeTask);
void clearCompletedTasks();
void setFiltering(TasksFilterType requestType);
TasksFilterType getFiltering();
这个接口是 MVP 概念中很重要的一个部分。它相当于对一个页面显示、加载数据的一个大纲。在实际的开发过程中,设计好一个界面的原型后,就应该来构思这个接口,思考这个界面都要现实哪些东西,要加载哪些数据,如何进行拆分,先把这个接口写出来。
有了这个接口之后,TasksFragment 实现TasksContract.View,TasksPresenter 实现 TasksContract.Presenter。有一点要注意的是,V 和 P 是紧密耦合在一起的,两者之间会经常相互调用。
至此,本文从 TasksActivity 的代码入手,主要分析了 MVP 中 V 与 P 的关系。在后续的文章中,我会继续对 TODO-MVP 这个官方的 MVP 示范项目进行分析。}

我要回帖

更多关于 todomvc 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信