Spring for Android 支持在Android环境下使用Spring框架,其意在简化Android本地开发。这包括能够在你的Android应用程序的Rest客户端使用RestTemplate。除了本次主要讲解的网络请求部分,Spring for Android还提供了基于OAuth支持将Spring Social的功能集成到Android应用程序, 授权客户机和实现流行的社交网站,如Twitter和Facebook。
RestTemplate Module
Spring的RestTemplate是一个健壮的、流行的基于java的REST client。Spring Android的RestTemplate模块提供一个能够在Android环境下工作的RestTemplate版本。
RestTemplate类是SFA RestTemplate组件包的核心类。他在概念上类似Spring框架其他同级项目中提供的模板类。RestTemlpate的行为表现取决于他提供的那些回调方法,同时通过配置合适的HttpMessageConverter 类,用户可以高效的将对象转换成HTTP请求中的信息或从响应消息中将信息转换回对应的对象。
每当创建一个新的RestTemplate实例,构造器就会生成配套的辅助对象用于支持RestTemplate的功能。以下是RestTemplate组件中一些提供支持的模块介绍。
HTTP Client
RestTemplate 提供了抽象的RESTful HTTP 请求的模块,内部实现中,RestTemplate 封装了原生的Android HTTP 客户端连接包。这包括标准的J2SE连接组件(用SimpleClientHttpRequestFactory封装)和HttpComponents HttpClient组件(用HttpComponentsClientHttpRequestFactory封装)。默认情况下具体使用哪种ClientHttpRequestFactory 取决于Android的版本。
@SuppressWarnings("deprecation") protected HttpAccessor() { if (httpClient43Present) { this.requestFactory = new HttpComponentsClientHttpRequestFactory(); } else if (okHttpPresent) { this.requestFactory = new OkHttpClientHttpRequestFactory(); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { this.requestFactory = new SimpleClientHttpRequestFactory(); } else { this.requestFactory = new org.springframework.http.client.HttpComponentsAndroidClientHttpRequestFactory(); } }
(2.3之后建议使用标准的J2SE连接组件)
Gzip压缩器
HTTP规范中允许通过在请求头中加入Accept-Encoding参数指定传输字节流的压缩方式。目前为止,RestTemplate 可以通过Gzip压缩组件支持在发送和接受时通过gzip(仅支持gzip)格式压缩过的数据。
对象-Json转换
如果需要支持这种转换,SFA的RestTemplate需要第三方Json数据映射包的支持。目前SFA提供三种包支持:Jackson JSON Processor, Jackson 2.x, 和 Google Gson。虽然Jackson 系列是有名的JSON解析包,但Gson的包更小,更适合生成小型的Android程序安装包。
SFA 将支持代码放在包 org.springframework.http.converter.json中。
RSS和Atom 摘要文件支持
RSS和Atom的支持也需要第三方包, Android ROME Feed Reader包就提供了相应的功能。
SFA 将支持代码放在包org.springframework.http.converter.feed 中。
所需要的包文件
spring-android-rest-template-{version}.jar
spring-android-core-{version}.jar
RestTemplate 构造器
下面列出四种RestTemplate 的构造方法。默认的构造方法不包含消息的转换器,因此必须自己添加消息转换器。
如上文所讲,如果需要一个特别ClientHttpRequestFactory ,也可以向构造器中传入一个ClientHttpRequestFactory 的实例参数。
RestTemplate();
RestTemplate(boolean includeDefaultConverters);
RestTemplate(ClientHttpRequestFactory requestFactory);
RestTemplate(boolean includeDefaultConverters, ClientHttpRequestFactory requestFactory);
RestTemplate API简介
RestTemplate 对应主要的六种Http 访问方法封装了6类高层调用方法,通过他们可以非常轻松且高效的完成对RESTful 服务的服务请求。
RestTemplate 的API方法命名遵循同样的规则: method+return
比如getForObject() 将调用HTTP GET方法,同时将HTTP响应数据转换成你指定的对象类型并返回。而postForLocation() 将调用HTTP POST方法,将给定的对象转换成HTTP请求内容发送,返回的信息中将标识新的对象资源所在的URL。
当HTTP请求发生异常时,异常信息将被封装在RestClientException 中并被抛出,可以通过在RestTemplate中实现ResponseErrorHandler来捕获并处理。
restTemplate.setErrorHandler(errorHandler);
/*捕获http请求发生异常RestClientException*/ restTemplate.setErrorHandler(new ResponseErrorHandler() { @Override public boolean hasError(ClientHttpResponse response) throws IOException { //获得请求响应,通过判断执行不同的操作。。。 return false;//返回true表示有错误,false表示没有错误 } @Override public void handleError(ClientHttpResponse response) throws IOException { //处理相应的错误操作。。。 //该方法只有在hasError方法中返回true时才会执行 } });
网络请求方法(其他请求方式类似,可参考官方在线的API文档,链接在参考资料中):
public <T> T getForObject(String url, Class<T> responseType, Object… urlVariables) throws RestClientException;
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> urlVariables) throws RestClientException;
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException;
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object… urlVariables);
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> urlVariables);
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException;
public URI postForLocation(String url, Object request, Object… urlVariables) throws RestClientException;
public URI postForLocation(String url, Object request, Map<String, ?> urlVariables);
public URI postForLocation(URI url, Object request) throws RestClientException;
public <T> T postForObject(String url, Object request, Class<T> responseType, Object… uriVariables);
public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables);
public <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException;
public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object… uriVariables);
public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException;
HTTP 消息转换器
对象通过getForObject(), getForEntity(), postForLocation(), postForEntity(), postForObject() and put() 发送或者返回消息时都是通过HttpMessageConverter 的具体实现,从而转换成Http请求或者从Http应答中转换。从下面的HttpMessageConverter API我们能对他的功能有个更深的认识。
public interface HttpMessageConverter<T> { //判断给定的类型是否可以被转换器读取 //clazz 用于测试是否能够读取 //mediaType 读取的网络媒介类型,如果不指定,可以为null //如果返回true表示可读,false表示其他情况 boolean canRead(Class<?> clazz, MediaType mediaType); // 判断给定的类型是否可以被转换器写入 //clazz 用于测试是否能够写入 //mediaType 写入的网络媒介类型,如果不指定,可以为null //如果返回true表示可写,false表示其他情况 boolean canWrite(Class<?> clazz, MediaType mediaType);
// 返回可被该转换器支持的header头消息类型 List<MediaType> getSupportedMediaTypes(); // 将给定的inputMessage消息转换成相应的类型对象并返回 // clazz 返回的类型,这个类型必须预先通过canRead方法,返回true T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; // 将给定的对象转换后写入outputMessage // t 写入outputMessage的对象,这个对象的类型必须预先通过canWrite方法,并返回true // contentType 写入时的媒介类型,如果为null的话,则必须使用默认的内容类型转换器,否则,媒介类型必须预先通过canWrite方法且返回值为true // outputMessage 要写入的消息 void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
其中,SFA框架已经提供了现成的MediaType(即mime类型)。
部分类型:
{ ".323", "text/h323" },
{ ".3gp", "video/3gpp" },
{ ".aab", "application/x-authoware-bin" },
{ ".aam", "application/x-authoware-map" },
{ ".aas", "application/x-authoware-seg" },
{ ".acx", "application/internet-property-stream" },
{ ".ai", "application/postscript" },
{ ".aif", "audio/x-aiff" },
{ ".aifc", "audio/x-aiff" },
{ ".aiff", "audio/x-aiff" },
{ ".als", "audio/X-Alpha5" },
{ ".amc", "application/x-mpeg" },
{ ".ani", "application/octet-stream" },
{ ".apk", "application/vnd.android.package-archive" },
{ ".asc", "text/plain" }
默认的消息转换器
基于性能的选择考虑,默认的RestTemplate无参构造方法不包含任何消息转换器。但是,如果调用了布尔版本的构造方法并传递true,则构造器会为一些主要的mime类型增加转换器。当然自己也可以编写自定义的转换器,并通过messageConverters属性添加。
RestTemplate生成的转换器包括:ByteArrayHttpMessageConverter, StringHttpMessageConverter, 和ResourceHttpMessageConverter。如果Android的版本在2.2或以上,那么XmlAwareFormHttpMessageConverter 和SourceHttpMessageConverter也会被添加。否则,若在2,.1版本,因为缺少对XML的必要支持,这两个转换器会被FormHttpMessageConverter 代替。我们来看看代码中是如何初始化的。
如果不设置,则使用该默认的消息转换器:
/** * Identifies and initializes default {@link HttpMessageConverter} implementations. */ private static class DefaultMessageConverters { private static final boolean javaxXmlTransformPresent = ClassUtils.isPresent("javax.xml.transform.Source", RestTemplate.class.getClassLoader()); private static final boolean simpleXmlPresent = ClassUtils.isPresent("org.simpleframework.xml.Serializer", RestTemplate.class.getClassLoader()); private static final boolean jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", RestTemplate.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", RestTemplate.class.getClassLoader()); private static final boolean gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", RestTemplate.class.getClassLoader()); public static void init(List<HttpMessageConverter<?>> messageConverters) { messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(new StringHttpMessageConverter()); messageConverters.add(new ResourceHttpMessageConverter()); // if javax.xml.transform is not available, fall back to standard Form message converter if (javaxXmlTransformPresent) { messageConverters.add(new SourceHttpMessageConverter<Source>()); messageConverters.add(new AllEncompassingFormHttpMessageConverter()); } else { messageConverters.add(new FormHttpMessageConverter()); } if (simpleXmlPresent) { messageConverters.add(new SimpleXmlHttpMessageConverter()); } if (jackson2Present) { messageConverters.add(new MappingJackson2HttpMessageConverter()); } else if (gsonPresent) { messageConverters.add(new GsonHttpMessageConverter()); } } }
ByteArrayHttpMessageConverter
该转换器可以从HTTP请求或应答中读写字节数组,默认情况下,该转换器支持所有的media types (*/*),同时写数据会以Content-Type为application/octet-stream(任意二进制数据)的形式。
FormHttpMessageConverter
能处理格式化的数据。支持用application/x-www-form-urlencoded(格式化编码数据)和multipart/form-data(多组件格式数据)的读写转换,格式化的数据通常用一个MultiValueMap<String, String>数据结构来读写。
XmlAwareFormHttpMessageConverter
是FormHttpMessageConverter的扩展,通过SourceHttpMessageConverter增加了对XML转换的支持。
ResourceHttpMessageConverter
能处理资源。支持对所有类型的读操作,写资源支持application/octet-stream。
SourceHttpMessageConverter
可以将请求或应答数据转换成javax.xml.transform.Source类型,只支持DOMSource, SAXSource, 和StreamSource三种类型。默认情况下支持text/xml和 application/xml两种XML可扩展标记语言MIME类型。
StringHttpMessageConverter
可以将请求或应答数据转换成String类型,支持所有的text media类型(text/*),支持Content-Type为text/plain(原文数据)写数据。
SimpleXmlHttpMessageConverter
支持对XML格式的读写处理,需要第三方包Simple XML serializer。XML的映射工作可以通过该包提供的注解方式进行配置。如果有需要提供附加的控制功能,可以通过切面的形式通过一个可定制的Serializer进行功能的加强。默认情况下,支持读写application/xml, text/xml, 和application/*+xml这几种MIME类型。
需要注意的是,此框架和Spring OXM并不兼容。他是用在SFA中的独立的第三方XML串行化实现。基于maven的依赖如下
<dependency> <groupId>org.simpleframework</groupId> <artifactId>simple-xml</artifactId> <version>${simple-version}</version> </dependency>
MappingJackson2HttpMessageConverter
基于Jackson框架提供对请求与应答数据的Json-Obejct转换。对于Json的映射配置可以Jackson提供的注解支持。当串行化/反串行化需要时也支持切面式的控制管理,默认支持application/json类型的读写。
请注意该转换器和GsonHttpMessageConverter 转换器都支持application/json类型的数据转换,因此最好只添加一款转换器,因为RestTemplate 会优先选择他最早匹配到的转换器进行数据转换,所以两款转换器都添加会导致不可预料的结果。
MappingJackson2HttpMessageConverter如果不是通过maven而是手工添加,还需要在lib包中引入jackson-annotations和jackson-core jars。
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson-version}</version> </dependency>
也可以通过MappingJacksonHttpMessageConverter支持 Jackson JSON Processor包。同样,手工导入需要引入jackson-core-asl jar。
<dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>${jackson-version}</version> </dependency>
GsonHttpMessageConverter
基本上和MappingJackson2HttpMessageConverter的功能一直,也支持注解和切面式。
<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>${gson-version}</version> </dependency>
(以上也可以使用gradle插件进行配置)
使用gzip压缩
// 添加 Accept-Encoding 消息头属性,设置为gzip HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.setAcceptEncoding(ContentCodingType.GZIP); HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders); RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add(new StringHttpMessageConverter()); // 注意调用的方法 ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class); Gzip压缩能有效的减少通讯传输的信息量。Gzip格式传输必须在服务器端被支持。通过将内容的请求头Accept-Encoding设置为gzip,可以向服务器端请求使用gzip压缩传输。如果服务器端支持,将返回压缩后的数据。RestTemplate 将检查Content-Encoding属性判断应答返回数据是否是压缩过的,是则用GZIPInputStream 去解压缩。目前Content-Encoding仅支持gzip格式。 需要注意,如果在2.3或以后的版本使用基于J2SE的标准连接SimpleClientHttpRequestFactory,Android框架会自动在Accept-Encoding中设置gzip,如果希望是gzip压缩失效,必须在header信息中增加其他标志值:
HttpHeaders requestHeaders = new HttpHeaders(); //增加如下标识 requestHeaders.setAcceptEncoding(ContentCodingType.IDENTITY); HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders); RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add(new StringHttpMessageConverter ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
通过GET方式获取JSON数据
首先需要定义一个与返回的JSON格式对应的POJO类。
public class Event { private Long id; private String title; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTitle() { return title; } public String setTitle(String title) { this.title = title; } }
使用Rest请求:
RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); Event[] events = restTemplate.getForObject(url, Event[].class); 也可以通过设置请求头Accept信息的方式
// 显式设置 Accept header HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.setAccept(Collections.singletonList(new MediaType("application","json"))); HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders); RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); ResponseEntity<Event[]> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, Event[].class); Event[] events = responseEntity.getBody();
其中MappingJackson2HttpMessageConverter可以换成GsonHttpMessageConverter,作为另一种实现JSON解析的选择。
通过GET方式获取 XML 数据
我们使用刚刚用过的Event对象来演示XML的使用方法,注意Event的类及属性都有注解。
@Root public class Event { @Element private Long id; @Element private String title; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTitle() { return title; } public String setTitle(String title) { this.title = title; } }
如果需要解析一组Event的数据,我们还需要定义基于List的包装类。
@Root(name="events") public class EventList { @ElementList(inline=true) private List<Event> events; public List<Event> getEvents() { return events; } public void setEvents(List<Event> events) { this.events = events; } }
编写请求代码如下:
RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add(new SimpleXmlHttpMessageConverter()); EventList eventList = restTemplate.getForObject(url, EventList.class);
和刚才一样,也可以通过设置请求头Accept信息的方式:
HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.setAccept(Collections.singletonList(new MediaType("application","xml"))); HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders); RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add(new SimpleXmlHttpMessageConverter()); ResponseEntity<EventList> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, EventList.class); EventList eventList = responseEntity.getBody();
通过POST的方式发送 JSON 数据
我们定义了如下的POJO用于演示:
public class Message { private long id; private String subject; private String text; public void setId(long id) { this.id = id; } public long getId() { return id; } public void setSubject(String subject) { this.subject = subject; } public String getSubject() { return subject; } public void setText(String text) { this.text = text; } public String getText() { return text; } }
接着是编辑数据和发送请求的代码
// 编辑数据
Message message = new Message(); message.setId(555); message.setSubject("test subject"); message.setText("test text"); RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); restTemplate.getMessageConverters().add(new StringHttpMessageConverter()); String response = restTemplate.postForObject(url, message, String.class);
同样,通过设置请求头Content-Type信息也是可以的
// 编辑数据 Message message = new Message(); message.setId(555); message.setSubject("test subject"); message.setText("test text"); // 设置 Content-Type HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.setContentType(new MediaType("application","json")); HttpEntity<Message> requestEntity = new HttpEntity<Message>(message, requestHeaders); RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); restTemplate.getMessageConverters().add(new StringHttpMessageConverter()); ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); String result = responseEntity.getBody();
(注意:如果想用spring for android的json解析功能,服务器需要返回的type类型应该为application/json而不是通常的text/html)
其他: Spring for Android还提供了三方授权,本身提供facebook和twtter的三方授,需要第三方包的支持。提供了Spring Social组件用于创建自己的连接工厂,并提供加密和保存信息的功能。
Spring for Android的核心部分比较强调服务器和客户端之间的数据交流,服务器需要确实返回指定的MIME类型,客户端才能进行更加简便的数据解析。
参考资料:
http://projects.spring.io/spring-android/
http://docs.spring.io/spring-android/docs/1.0.x/reference/htmlsingle/
http://tool.oschina.net/apidocs/apidoc?api=springforandroid
http://blog.csdn.net/yoara/article/details/37963497