如何构建一个REST客户端

什么是REST?

含状态传输(英文:Representational State Transfer,简称REST)是一种软件架构风格。需要注意的是,REST是设计风格而不是标准。REST通常基于使用HTTP,URI,和XML以及HTML这些现有的广泛流行的协议和标准。

  • 资源是由URI来指定。
  • 对资源的操作包括获取、创建、修改和删除资源,这些操作正好对应HTTP协议提供的GET、POST、PUT和DELETE方法。
  • 通过操作资源的表现形式来操作资源。
  • 资源的表现形式则是XML或者HTML,取决于读者是机器还是人,是消费web服务的客户软件还是web浏览器。当然也可以是任何其他的格式

REST绝不是只和HTTP相联系.你可以使用任意一种传输协议,但HTTP在REST风格架构中无疑是使用最广泛的。如果我要连接的服务已经有一个适配手机端的web网站,为何我还要开发一个应用呢?换句话说,为什么我们不使用浏览器,一步到位?

为什么使用REST?

好吧,除非浏览器技术发展起来了。我要列举5大原因。

  • 第一个原因是android应用是集成在android platform中,可以使用intents, content provider等等,浏览器做不到这些。
  • 正是android平台为android应用提供了一些新功能,你的应用可以为其他应用提供intents.
  • 你的应用可以在后台运行。这意味着如果要从服务器更新数据,当从服务器取得数据后,应用可以通知用户,让他们知道有数据可用。
  • 应用和网络的连接稳定性有限。当网络较差时,请求可能失败。而android应用可以在后台进行多次重试,让用户在使用浏览器多次按刷按钮的痛苦中解放出来。
  • 应用可以将从网络中取得资源,比如JSON, 或者二进制,或者XML格式的内容解析并存储在数据库中。因此,当你想取得新数据时,你就可以选择获取比数据库中数据更新的或者更老旧的数据,而不是和数据库中缓存相同的数据。这样你就不用获得所有html,就不用花长时间去下载javascript.
  • 最后,你的应用可以和android操作系统在使用上保持一致性。

新手应该避免的

你是一个Java程序员。翻看了一下Android SDK,你对自己说,“我知道什么是activity,这有什么难的?我将创建一个activity, 知道REST方法执行耗时,因为会连接服务器,所以我会开启一个线程来执行。你决定这个线程是Activity里的内部类,并将取得的数据存储在内存中。

当你把app跑起来了,它运行得很快,没有崩溃,那么这为什么正确的方法?你得理解Android操作系统是如何工作的,Android操作系统是设计运行在有限资源的设备上的,当系统要启动一个新应用,并且内存不够时,它将决定强制关闭一个已经存在的应用。那么它如何作出这个决定?答案是,你要帮助操作系统做出决定。如果你的应用没有前台运行的activity,不向用户展示,没有执行一些操作,那么就会安全关闭。

上图有什么问题呢?比如有这样一个场景:
启动线程执行REST方法,这时来了一个电话,当前activity状态为paused,这时操作系统说,这个进程没有前台的activity,所以我要杀死它,让它强制关闭。你猜会发生什么,提交的POST, PUT, DELETE方法也许就在服务器上执行,但是你的app永远也不会了解到执行结果。你执行了GET方法,获取到所有的数据,解析,you’re happy.操作系统却关闭了它。浪费带宽。你的方法存在的另一个问题是将数据存放在内存中。等等,这样运行的会快。至于为什么这么做不好,是因为你得不停从服务器获取数据,因为用户重启了设备,或者某个时间点进程被强制关闭了,浪费了CPU,电池电量,网络带宽。这不是写android应用的好方式。至于某些人说,从内存取数据要快得多,我要说,使用Content Provider,你可以将数据缓存到内存中。所以这都不是理由。

三种正确的设计模式

google工程师在2011年的IO大会讲了三种设计模式,会带来最好的用户体验及最高的性能。一种是基于Service API,一种是基于Content Provider API,第三种使用Sync Adapter,是第二种的变种。这里只讨论第一种:

由底至上

REST METHOD

什么是REST METHOD?很简单,REST METHOD是具备这种能力的实体,准备HTTP URL,在某些情况下准备HTTP body,和服务器进行HTTP传输,处理响应数据。

Processor

Processor的角色是将服务器资源映射到本地数据库中。我们需要往数据库中插入一条记录,该记录至少有两个字段,status column和result column. status列指示资源的状态。因为当执行REST方法时每种资源都具有传输状态。当资源正在传输过程中,资源没有和服务器成功同步,所以需要跟踪资源的状态。两方面原因:用户界面可能想要显示资源的某种状态,在同步的过程中,用户可能会离开当前activity。而你可以提醒用户说:“我正在同步资源,请继续使用app,当任务完成时我会通知你”。当该界面状态消失时,说明资源已经同步完毕。

因此status会保存一些标志。“我正在执行POST METHOD”, “我正在执行UPDATING”, “DELETING”.于是在任何时候,你只需要检查数据库就可以知道资源的状态。result列保存最后一次REST方法执行后的HTTP响应数据。

Processor在REST METHOD执行的前后都执行一次。当和服务器交互完毕,更新状态标记。

顺便说一句,这些标记可以用作其他用途。当你需要重试之前失败的操作怎么办?而知道将要进行的操作是不是很棒?posting标记告诉你需要执行POST METHOD.查看传输状态标记,是否有一个HTTP传输正在POST,如果有将不会开始一个新的请求。否则的话我可以选择重试。

还要提一句,用于不要在主线程中执行数据库操作或者Content Provider操作,可能会带来ANR.

Service 和 ServiceHelper

Service的角色是在activity消失后仍能在后台执行耗时操作。activity来去自由,它仅仅是应用程序的用户界面。用户可以在他执行耗时操作时选择点击主页按钮,点击关闭按钮。而耗时的操作仍在执行,最好的方式是让数据库去存储资源和状态。当你回来的时候,不管发生了什么,你都可以了解到发生了什么。执行了吗?仍在执行中?

Service内部维护一个任务队列 ,所有的请求通过ServiceHelper加入或者从队列中移除。当Service取出一个任务开始执行时,将任务交给Processor执行,执行完毕后交由Processor执行回调进行数据处理。

最重要的,当所有的操作完成,关闭service.对其它应用友好,当Service工作完毕,关闭它。

Activity

总是记得activities有它们的生命周期,paused, resumed或者destroyed.它们是由用户控制的界面。在onResume方法中我们要在service中增加回调,告诉它我回来了。在onPause移除回调。否则应用迟早会崩溃。

总是要考虑到一下三种场景:

activity发出了请求,当activity在前台的时候,一切正常。如果activity发出请求后,activity 暂停,回来,恢复,这时回调执行,你仍然能够执行回调,一切正常。在这种情况下你需要存储请求id,这样就可以询问Service Helper该请求是否正在执行。当执行onResume时回答是“是的,正在执行”。我们只需等待回调执行。

第三种最复杂,activity paused, request 完成,然后activity resumed.在onResume方法中询问请求是否正在执行,这次的回答是no.那么现在你明白了为什么我选择将响应的结果数据缓存到数据库中了吧。

Content Provider

Content Provider支持ContentObservers. 当数据库表中的记录变化时,ContentObservers很方便从Content Provider接收到通知.你可以监控到某一个记录,表中的所有记录或者数据库中得所有资源的状态。

WIKI - REST 介绍

YouTuBe - Google IO 大会视频