用Jersey为Android客户端开发Restful Web Service
2016-04-15 19:53
621 查看
平时在做Android客户端的时候经常要与服务器之间通信,客户端通过服务端提供的接口获取数据,然后再展示在客户端的界面上,作为Android开发者,我们平时更多的是关注客户端的开发,而对服务端开发的关注相对较少,但是如果我们要自己一个人设计并开发一套完整的系统,我们就必须同时具备客户端和服务端的开发知识,而且我认为同时掌握客户端和服务端的开发技术还是很有用处的,不仅有助于提升我们的架构知识和能力,而且还……你懂得!身边一些做WEB开发的朋友很多都说为客户端开发接口和单纯地做WEB项目并没有很大的区别,不管后台语言用的是JAVA,PHP,还是C#等,用这些语言为客户端开发接口的时候有很多相似之处,比如说服务端请求路径的分发以及请求处理方式等。这几天出于好奇,想为自己的客户端开发Web Service接口,所以就在网上搜集了一些资料,发现有很多WEB框架可以很轻松地为移动端开发服务器接口,比如说Spring MVC,CXF,AXIS,RESTEasy等等,但是我最终选用了Jersey来完成服务端的开发。
首先,我们来大致说一下Jersey是什么。按照Jersey官网(https://jersey.java.net/)的介绍,Jersey是在Java开发中,为了简化RESTful Web Service以及客户端开发而设计的一套开源的,标准的,很优秀的,使用起来非常方便的JAX-RS API(即Java API for RESTful Web Services),Jersey支持JAX-RS API,并且是JAX-RS (JSR 311 & JSR 339) 的参考实现,值得注意的是,Jersey不仅仅是JAX-RS的参考实现,同时,它在JAX-RS工具包的基础上做了大量的扩展,增加了许多额外的特性和工具,并且都提供了丰富的说明文档以便开发者进一步对Jessey进行扩展以满足自己的实际开发需求。
为了方便大家更好地理解JAX-RS和RESTful,在这里我对这两个名词稍加说明。
JAX-RS按照百度百科上面的说明,即Java API for RESTful Web Services,是一个Java 编程语言的应用程序接口,支持按照表述性状态转移(REST)架构风格创建Web服务。JAX-RS使用了Java SE5引入的Java标注来简化Web服务的客户端和服务端的开发和部署。JAX-RS提供了一些标注将一个资源类,一个POJO Java类,封装为Web资源。JAX-RS包含了一系列的标注,包括:
@Path,标注资源类或者方法的相对路径
@GET,@PUT,@POST,@DELETE,标注方法是HTTP请求的类型。
@Produces,标注返回的MIME媒体类型
@Consumes,标注可接受请求的MIME媒体类型
@PathParam,@QueryParam,@HeaderParam,@CookieParam,@MatrixParam,@FormParam,分别标注方法的参数来自于HTTP请求的不同位置,例如@PathParam来自于URL的路径,@QueryParam来自于URL的查询参数,@HeaderParam来自于HTTP请求的头信息,@CookieParam来自于HTTP请求的Cookie。
RESTful是一种软件架构风格,而不是软件设计标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。关于RESTful的详细信息请参考:http://baike.baidu.com/link?url=z4989VJ3yaALLF9Xs7yc-j7h9QjKjDLGMz12F-ORdWpNjawQE6oOVLVXdnCh4OW1Pla7DJBpy6NlhwuzI1LUAK。
好了,废话不多说,接下来马上开始我们的实战阶段,我将通过写一个服务端项目和一个Android客户端项目来详细说明是如何使用Jersey实现为移动端开发Web Service的。
第一步,简单介绍一下项目的需求,我们将开发一个APP登录模块,登录之后进入首页,首页显示欢迎语和登录用户的名字,首页中有一个Button,通过点击Button获取到服务器中保存的所有用户的信息并通过TextView展示,需求就是这么简单,逻辑也不会很复杂。我希望就是通过这么一个简单的模型让对服务端接口开发还不是很熟悉的朋友了解到通过Jersey如何实现服务端的开发,以及客户端和服务端是如何交互。
第二步,介绍一下使用到的开发工具,服务端我用的是MyEclipse 2015,客户端使用的是Android Studio。
第三步,说明一下客户端和服务端通信数据传输格式为JSON,客户端在解析服务端返回的json时使用了谷歌自家的工具Gson。对于简单的JSON,我们使用普通的JsonArray和JsonObject可以很方便的解析,没必要使用Gson等解析工具,但是在解析一些非常复杂的json的时候,Gson等解析工具的优势就会体现出来了,Gosn能够很方便的将json转换为实体Bean,也能很方便地将实体Bean转换为json,这对于做WEB API开发非常有帮助,省去了不少解析的麻烦,因此我个人向大家推荐学习一下Gson的使用。此外,客户端采用HttpURLConnection向服务端发送请求,请求方式为POST,请求内容的类型(Content-Type)为application/x-www-form-urlencoded,并设置请求连接服务器的超时时间为5秒。
第四步,服务端项目编写:
服务端需要使用到很多jar包,包括Jersey官方提供的众多jar包,以及注册json转化器需要使用到的JackSon项目jar包,JackSon的作用基本和Gson类似,用于java中java bean和json或者xml之间的转换。Jersey官方的jar包的下载地址:https://jersey.java.net/download.html。我在项目中使用的是Jesey2.x版本,大家在使用的时候下载最新版的就可以,当然也可以直接拷贝项目中的所有jar包到自己的项目中。JackSon的官网貌似现在不能下载jar包了,但是应该有其它一些渠道可以下载,大家在使用的时候可以直接拷贝我丢在项目中的jar包。
首先,我们需要创建一个动态Web工程,即Dynamic Web Project,具体创建步骤如下:
File-New-Other
选择Dynamic Web Project,点击next
给项目取名,其它默认,点击finish。
工程创建好之后,我们看一下项目完成之后的整体结构,如图:
其中,核心代码在RestService.java中,所有的接口都写在这里面,我直接把RestService.java的代码贴出来,如下:
我们可以看到,类和接口方法均有注解,这些注解是必须写上去的,因为这些注解指定了一些很关键的信息,客户端请求的时候必须依赖这些注解才能最终找到请求的接口,然后通过接口获取数据并返回。RestService.java上面的注解@Path("/restService")指定了Web Service接口的转发路径,每一个接口方法上面有三个注解,其中,@POST和@GET等表示网络请求方式,请求方式除了@POST和@GET之外,常见的还有put、delete等;@Path("/getUserText")、@Path("/getUserXml")、@Path("/getUserJson")表示的是请求的接口的路径,与类的@Path注解和项目目录一起拼接组成完整的请求路径,@Path注解后面的名字可以按照命名规范自己取;@Produces指定了请求的数据格式,一般为JSON和XML,当然还有TEXT文档等类型。这些注解都指定好之后,那么客户端就可以通过完整的请求路径来访问服务器了,完整的路径格式为:http://[IP地址]:[端口号]/[服务端工程名]/[路径映射]/[接口类路径]/[接口方法路径],例如,客户端要访问我们项目中的login接口,那么客户端请求的完整路径为:http://192.168.254.26:8080/JerseyServer/rest/restService/login,192.168.254.26是我电脑的局域网IP地址,8080为访问apache的端口号,rest是在web.xml中配置的路径映射名。web.xml的配置内容如下:
web.xml中需要注意的项已经标注在了注释里面,其中有一个资源加载类RestApplication.java用于加载各种资源和所需工具,代码如下:
这里,主要指定了服务类的包路径,并用Jackson作为Json的解析转换工具,用glassfish的LoggingFilter打印日志。
此外,项目中的User实体类代码如下:
注意,需要用@XmlRootElement进行注释才能正常传递实体数据。
ResponseDTO.java用于包装返回给客户端的JSON数据,包括返回状态码,返回状态值以及实体数据,代码如下:
好了,到此服务端就搞定了,接下来让我们简单看看android端的实现。客户端的项目结构如下:
客户端与服务器连接的部分以及各个接口都在WebService.java中,实现代码如下:
其中,使用HttpURLConnection向服务器发送请求数据,用GSON来解析JSON数据,在解析JSON的时候,为了解析的方便,我对GSON进行了进一步封装,以便在接口中对所有的实体和JSON进行统一的转换,其实现代码如下:
同服务端一样,客户端也用一个Response类封装实体对象的信息。
Response.java的代码如下:
User.java的代码如下:
NetworkUtil.java用于网络状况的判断,实现代码如下:
好了,剩下的就是登陆界面和主界面了,Activity的布局和逻辑都非常简单,我直接把代码贴上。
LoginActivty:
LoginActivty的布局文件:
MainActivity:
MainActivty的布局文件:
最后,别忘了配置清单文件,如下所示:
好了,到此客户端和服务端均已大功告成,整个项目其实就是用最简单的模型来说明怎样利用Jersey这个框架来为我们的移动端搭建后台接口,其中,难免有诸多纰漏之处,请大家多多批评指正,谢谢!
最后,附上项目源码的下载链接:
客户端:①http://download.csdn.net/detail/owoxiangxin12/9492575 ②https://github.com/monkey1992/JerseyClient
服务端:①http://download.csdn.net/detail/owoxiangxin12/9492571 ②https://github.com/monkey1992/JerseyServer
首先,我们来大致说一下Jersey是什么。按照Jersey官网(https://jersey.java.net/)的介绍,Jersey是在Java开发中,为了简化RESTful Web Service以及客户端开发而设计的一套开源的,标准的,很优秀的,使用起来非常方便的JAX-RS API(即Java API for RESTful Web Services),Jersey支持JAX-RS API,并且是JAX-RS (JSR 311 & JSR 339) 的参考实现,值得注意的是,Jersey不仅仅是JAX-RS的参考实现,同时,它在JAX-RS工具包的基础上做了大量的扩展,增加了许多额外的特性和工具,并且都提供了丰富的说明文档以便开发者进一步对Jessey进行扩展以满足自己的实际开发需求。
为了方便大家更好地理解JAX-RS和RESTful,在这里我对这两个名词稍加说明。
JAX-RS按照百度百科上面的说明,即Java API for RESTful Web Services,是一个Java 编程语言的应用程序接口,支持按照表述性状态转移(REST)架构风格创建Web服务。JAX-RS使用了Java SE5引入的Java标注来简化Web服务的客户端和服务端的开发和部署。JAX-RS提供了一些标注将一个资源类,一个POJO Java类,封装为Web资源。JAX-RS包含了一系列的标注,包括:
@Path,标注资源类或者方法的相对路径
@GET,@PUT,@POST,@DELETE,标注方法是HTTP请求的类型。
@Produces,标注返回的MIME媒体类型
@Consumes,标注可接受请求的MIME媒体类型
@PathParam,@QueryParam,@HeaderParam,@CookieParam,@MatrixParam,@FormParam,分别标注方法的参数来自于HTTP请求的不同位置,例如@PathParam来自于URL的路径,@QueryParam来自于URL的查询参数,@HeaderParam来自于HTTP请求的头信息,@CookieParam来自于HTTP请求的Cookie。
RESTful是一种软件架构风格,而不是软件设计标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。关于RESTful的详细信息请参考:http://baike.baidu.com/link?url=z4989VJ3yaALLF9Xs7yc-j7h9QjKjDLGMz12F-ORdWpNjawQE6oOVLVXdnCh4OW1Pla7DJBpy6NlhwuzI1LUAK。
好了,废话不多说,接下来马上开始我们的实战阶段,我将通过写一个服务端项目和一个Android客户端项目来详细说明是如何使用Jersey实现为移动端开发Web Service的。
第一步,简单介绍一下项目的需求,我们将开发一个APP登录模块,登录之后进入首页,首页显示欢迎语和登录用户的名字,首页中有一个Button,通过点击Button获取到服务器中保存的所有用户的信息并通过TextView展示,需求就是这么简单,逻辑也不会很复杂。我希望就是通过这么一个简单的模型让对服务端接口开发还不是很熟悉的朋友了解到通过Jersey如何实现服务端的开发,以及客户端和服务端是如何交互。
第二步,介绍一下使用到的开发工具,服务端我用的是MyEclipse 2015,客户端使用的是Android Studio。
第三步,说明一下客户端和服务端通信数据传输格式为JSON,客户端在解析服务端返回的json时使用了谷歌自家的工具Gson。对于简单的JSON,我们使用普通的JsonArray和JsonObject可以很方便的解析,没必要使用Gson等解析工具,但是在解析一些非常复杂的json的时候,Gson等解析工具的优势就会体现出来了,Gosn能够很方便的将json转换为实体Bean,也能很方便地将实体Bean转换为json,这对于做WEB API开发非常有帮助,省去了不少解析的麻烦,因此我个人向大家推荐学习一下Gson的使用。此外,客户端采用HttpURLConnection向服务端发送请求,请求方式为POST,请求内容的类型(Content-Type)为application/x-www-form-urlencoded,并设置请求连接服务器的超时时间为5秒。
第四步,服务端项目编写:
服务端需要使用到很多jar包,包括Jersey官方提供的众多jar包,以及注册json转化器需要使用到的JackSon项目jar包,JackSon的作用基本和Gson类似,用于java中java bean和json或者xml之间的转换。Jersey官方的jar包的下载地址:https://jersey.java.net/download.html。我在项目中使用的是Jesey2.x版本,大家在使用的时候下载最新版的就可以,当然也可以直接拷贝项目中的所有jar包到自己的项目中。JackSon的官网貌似现在不能下载jar包了,但是应该有其它一些渠道可以下载,大家在使用的时候可以直接拷贝我丢在项目中的jar包。
首先,我们需要创建一个动态Web工程,即Dynamic Web Project,具体创建步骤如下:
File-New-Other
选择Dynamic Web Project,点击next
给项目取名,其它默认,点击finish。
工程创建好之后,我们看一下项目完成之后的整体结构,如图:
其中,核心代码在RestService.java中,所有的接口都写在这里面,我直接把RestService.java的代码贴出来,如下:
package com.jerseyserver.service; import java.util.ArrayList; import java.util.HashMap; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.jerseyserver.entity.ResponseDTO; import com.jerseyserver.entity.User; import com.jerseyserver.util.TextUtil; @Path("/restService") public class RestService { @POST @Path("/getUserText") @Produces(MediaType.TEXT_PLAIN) /** * 测试返回text文本媒体类型数据的方式 * @return "Hello,World!" */ public String getUserText() { return "Hello,World!"; } @GET @Path("/getUserXml") @Produces(MediaType.APPLICATION_XML) /** * 测试返回xml媒体类型数据的方式 * @return User object */ public User getUserXml() { User user = new User(); user.setName("snail"); user.setAge("22"); user.setSex("male"); return user; } @GET @Path("/getUserJson") @Produces(MediaType.APPLICATION_JSON) /** * 测试返回json媒体类型数据的方式 * @return User object */ public User getUserJson() { User user = new User(); user.setName("snail"); user.setAge("22"); user.setSex("male"); return user; } @POST @Path("/getUserInfo") @Produces(MediaType.APPLICATION_JSON) /** * 测试带请求参数时返回json媒体类型数据的方式 * @param username * @return */ public User getUserInfo(@FormParam("username") String username) { if (username == null || "".equals(username)) { return null; } return getUserByName(username); } /** * 通过用户名获取用户 * @param username * @return User object */ private User getUserByName(String username) { HashMap<String, User> map = initAllUsers(); return map.get(username); } /** * 获取所有用户的map * @return 所有用户的map */ private HashMap<String, User> initAllUsers() { HashMap<String, User> map = new HashMap<>(); User user1 = new User(); user1.setName("Jack"); user1.setPasswd("Jack"); user1.setAge(18 + ""); user1.setSex("男"); map.put(user1.getName(), user1); User user2 = new User(); user2.setName("Alice"); user2.setPasswd("Alice"); user2.setAge(18 + ""); user2.setSex("女"); map.put(user2.getName(), user2); User user3 = new User(); user3.setName("Allen"); user3.setPasswd("Allen"); user3.setAge(20 + ""); user3.setSex("女"); map.put(user3.getName(), user3); return map; } @POST @Path("/login") @Produces(MediaType.APPLICATION_JSON) /** * 用户登录 * @param username 用户名 * @param password 密码 * @return Response object */ public Response login(@FormParam("username") String username, @FormParam("password") String password) { if (TextUtil.isEmpty(username) || TextUtil.isEmpty(password)) { return null; } User user = checkUser(username, password); if (user == null) { return null; } ObjectMapper mapper = new ObjectMapper(); GenericEntity<String> payloadEntity; try { payloadEntity = new GenericEntity<String>( mapper.writeValueAsString(new ResponseDTO(200, "ok", user))) { }; return Response.ok(payloadEntity).build(); } catch (JsonProcessingException e) { e.printStackTrace(); return Response .ok(" {\"status\": 404,\n\"message\": \"error\",\n\"response\": \"\"}") .build(); } } /** * 验证用户是否存在 * @param username * @param password * @return User object */ private User checkUser(String username, String password) { HashMap<String, User> map = initAllUsers(); User user = map.get(username); if (user != null) { String passwd = user.getPasswd(); if (password.equals(passwd)) { return user; } } return null; } @POST @Path("/getAllUsers") @Produces(MediaType.APPLICATION_JSON) /** * 获取所有用户的集合 * @return Response object */ public Response getAllUsers() { ArrayList<User> list = new ArrayList<User>(); User user1 = new User(); user1.setName("Jack"); user1.setPasswd("Jack"); user1.setAge(18 + ""); user1.setSex("男"); list.add(user1); User user2 = new User(); user2.setName("Alice"); user2.setPasswd("Alice"); user2.setAge(18 + ""); user2.setSex("女"); list.add(user2); User user3 = new User(); user3.setName("Allen"); user3.setPasswd("Allen"); user3.setAge(20 + ""); user3.setSex("女"); list.add(user3); ObjectMapper mapper = new ObjectMapper(); GenericEntity<String> payloadEntity; try { payloadEntity = new GenericEntity<String>( mapper.writeValueAsString(new ResponseDTO(200, "ok", list))) { }; return Response.ok(payloadEntity).build(); } catch (JsonProcessingException e) { e.printStackTrace(); return Response .ok(" {\"status\": 404,\n\"message\": \"error\",\n\"response\": \"\"}") .build(); } } }
我们可以看到,类和接口方法均有注解,这些注解是必须写上去的,因为这些注解指定了一些很关键的信息,客户端请求的时候必须依赖这些注解才能最终找到请求的接口,然后通过接口获取数据并返回。RestService.java上面的注解@Path("/restService")指定了Web Service接口的转发路径,每一个接口方法上面有三个注解,其中,@POST和@GET等表示网络请求方式,请求方式除了@POST和@GET之外,常见的还有put、delete等;@Path("/getUserText")、@Path("/getUserXml")、@Path("/getUserJson")表示的是请求的接口的路径,与类的@Path注解和项目目录一起拼接组成完整的请求路径,@Path注解后面的名字可以按照命名规范自己取;@Produces指定了请求的数据格式,一般为JSON和XML,当然还有TEXT文档等类型。这些注解都指定好之后,那么客户端就可以通过完整的请求路径来访问服务器了,完整的路径格式为:http://[IP地址]:[端口号]/[服务端工程名]/[路径映射]/[接口类路径]/[接口方法路径],例如,客户端要访问我们项目中的login接口,那么客户端请求的完整路径为:http://192.168.254.26:8080/JerseyServer/rest/restService/login,192.168.254.26是我电脑的局域网IP地址,8080为访问apache的端口号,rest是在web.xml中配置的路径映射名。web.xml的配置内容如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app id="WebApp_ID"> <!-- 同项目名 --> <display-name>JerseyServer</display-name> <servlet> <!-- 为servlet取名 --> <servlet-name>mobile</servlet-name> <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> <init-param> <param-name>javax.ws.rs.Application</param-name> <!-- 配置自己的资源加载类,用于加载资源 --> <param-value>com.jerseyserver.RestApplication</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <!-- 保持和servlet名一致 --> <servlet-name>mobile</servlet-name> <!-- 分发根路径,用来做路径映射--> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> </web-app>
web.xml中需要注意的项已经标注在了注释里面,其中有一个资源加载类RestApplication.java用于加载各种资源和所需工具,代码如下:
package com.jerseyserver; import org.glassfish.jersey.filter.LoggingFilter; import org.glassfish.jersey.server.ResourceConfig; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; public class RestApplication extends ResourceConfig { public RestApplication() { // 服务类所在的包路径 packages("com.jerseyserver.service"); // 注册JSON转换器 register(JacksonJsonProvider.class); // 打印访问日志,便于跟踪调试,正式发布可清除 register(LoggingFilter.class); } }
这里,主要指定了服务类的包路径,并用Jackson作为Json的解析转换工具,用glassfish的LoggingFilter打印日志。
此外,项目中的User实体类代码如下:
package com.jerseyserver.entity; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class User { private String name; private String passwd; private String age; private String sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
注意,需要用@XmlRootElement进行注释才能正常传递实体数据。
ResponseDTO.java用于包装返回给客户端的JSON数据,包括返回状态码,返回状态值以及实体数据,代码如下:
package com.jerseyserver.entity; public class ResponseDTO { private int status; private String message; private Object response; public ResponseDTO(int status, String message, Object response) { super(); this.status = status; this.message = message; this.response = response; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Object getResponse() { return response; } public void setResponse(Object response) { this.response = response; } }
好了,到此服务端就搞定了,接下来让我们简单看看android端的实现。客户端的项目结构如下:
客户端与服务器连接的部分以及各个接口都在WebService.java中,实现代码如下:
package com.jy.jerseyclient.service; import android.text.TextUtils; import android.util.Log; import com.jy.jerseyclient.entity.Response; import com.jy.jerseyclient.utils.JsonUtil; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; /** * Created by xy on 2016/4/10. */ public class WebService { private final static String TAG = "WebService"; /**服务器接口根路径*/ private static final String WEB_ROOT = "http://192.168.254.26:8080/JerseyServer/rest/restService/"; /**登录*/ private static final String LOGIN = "login"; /**获取所有用户信息*/ private static final String GET_ALL_USERS = "getAllUsers"; /** * 登录 * @param username 用户名 * @param password 用户密码 * @return 包含用户信息的Response对象 */ public static Response login(String username, String password) { String path = WEB_ROOT + LOGIN; Map<String, String> map = new HashMap<>(); map.put("username", username); map.put("password", password); InputStream is = connection(path, map); if (is != null) { String content = getStringFromIS(is); if (content != null) { return parseResponse(content); } else { Log.e(TAG, "contentS == null"); } } else { Log.e(TAG, "is == null"); } return null; } /** * 获取所有用户信息 * @return 包含所有用户信息的Response对象 */ public static Response getAllUsers() { String path = WEB_ROOT + GET_ALL_USERS; InputStream is = connection(path, null); if (is != null) { String content = getStringFromIS(is); if (content != null) { return parseResponse(content); } else { Log.e(TAG, "contentS == null"); } } else { Log.e(TAG, "is == null"); } return null; } /** * 解析服务器返回的JSON数据 * @param content JSON数据 * @return Response对象 */ private static Response parseResponse(String content) { Log.e(TAG, "state======" + content); if (TextUtils.isEmpty(content)) { return null; } return JsonUtil.getEntity(content, Response.class); } /** * 得到服务器返回的输入流数据 * @param path 请求路径 * @param map 包含密文的map集合 * @return 服务器返回的数据 */ private static InputStream connection(String path, Map<String, String> map) { try { String pathUrl = path; URL url = new URL(pathUrl); HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); StringBuffer sb = new StringBuffer(); if (map != null) { if (!map.isEmpty()) { for (Map.Entry<String, String> entry : map.entrySet()) { sb.append(entry.getKey()).append('=').append(URLEncoder.encode(entry.getValue(), "UTF-8")).append('&'); } sb.deleteCharAt(sb.length() - 1); } } byte[] entityData = sb.toString().getBytes(); httpConn.setDoOutput(true); httpConn.setDoInput(true); httpConn.setUseCaches(false); httpConn.setRequestMethod("POST"); //设置请求服务器连接的超时时间 httpConn.setConnectTimeout(5 * 1000); //设置服务器返回数据的超时时间 //httpConn.setReadTimeout(30 * 1000); httpConn.setRequestProperty("Content-length", "" + entityData.length); httpConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); OutputStream outStream = httpConn.getOutputStream(); outStream.write(entityData); outStream.flush(); outStream.close(); int responseCode = httpConn.getResponseCode(); if (HttpURLConnection.HTTP_OK == responseCode) { InputStream is = httpConn.getInputStream(); return is; } } catch (Exception ex) { ex.printStackTrace(); return null; } return null; } /** * 将服务器返回的输入流转换为字符串 * @param is 服务器返回的输入流 * @return 输入流转换之后的字符串 */ public static String getStringFromIS(InputStream is) { byte[] buffer = new byte[1024]; ByteArrayOutputStream os = new ByteArrayOutputStream(); try { int len = -1; while ((len = is.read(buffer)) != -1) { os.write(buffer, 0, len); } os.close(); is.close(); } catch (Exception e) { e.printStackTrace(); } String reString = new String(os.toByteArray()); Log.e(TAG, "geStringFromIS reString======" + reString); return reString; } }
其中,使用HttpURLConnection向服务器发送请求数据,用GSON来解析JSON数据,在解析JSON的时候,为了解析的方便,我对GSON进行了进一步封装,以便在接口中对所有的实体和JSON进行统一的转换,其实现代码如下:
package com.jy.jerseyclient.utils; import android.util.Log; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import java.util.ArrayList; import java.util.List; /** * Created by xy on 2015/12/29. */ public class JsonUtil { private final static String TAG = "JsonUtil"; /** * 将对象转换为json字符串 * @param obj 对象,可以是实体对象,List对象,Map对象等 * @return json字符串 */ public static String toJson(Object obj) { if (obj == null) { throw new IllegalArgumentException("illegal argument"); } return new Gson().toJson(obj); } /** * 将json字符串转换为实体对象 * @param jsonString json字符串 * @param cls 实体类 * @param <T> 泛型参数 * @return 实体对象 */ public static <T> T getEntity(String jsonString, final Class<T> cls) { T t; try { Gson gson = new Gson(); t = gson.fromJson(jsonString, cls); } catch (Exception e) { e.printStackTrace(); Log.e(TAG, jsonString + " 无法转换为 " + cls.getSimpleName() + " 对象"); return null; } return t; } /** * 将json字符串转换为List对象 * @param jsonString json字符串 * @param cls 实体类 * @param <T> 泛型参数 * @return 实体List对象 */ public static <T> List<T> getEntityList(String jsonString, final Class<T> cls) { List<T> list = new ArrayList<T>(); JsonArray array = new JsonParser().parse(jsonString).getAsJsonArray(); for (final JsonElement elem : array) { list.add(new Gson().fromJson(elem, cls)); } return list; } }
同服务端一样,客户端也用一个Response类封装实体对象的信息。
Response.java的代码如下:
package com.jy.jerseyclient.entity; import java.io.Serializable; /** * Description: * Author: xy * Date: 2016/4/12 14:49 */ public class Response implements Serializable { private static final long serialVersionUID = 8980216391057926016L; public int status; public String message; public Object response; public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Object getResponse() { return response; } public void setResponse(Object response) { this.response = response; } }
User.java的代码如下:
package com.jy.jerseyclient.entity; import java.io.Serializable; /** * Created by 123 on 2016/4/10. */ public class User implements Serializable { private static final long serialVersionUID = -1044671445753823751L; private String name; private String passwd; private String sex; private String age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", passwd='" + passwd + '\'' + ", sex='" + sex + '\'' + ", age='" + age + '\'' + '}'; } }
NetworkUtil.java用于网络状况的判断,实现代码如下:
package com.jy.jerseyclient.utils; import android.content.Context; import android.net.ConnectivityManager; /** * 网络相关工具类 * @author xy * */ public class NetworkUtil { /** * 判断是否有可用网络 * @param context * @return */ public static boolean isNetWorkOpened(Context context) { ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (connManager.getActiveNetworkInfo() != null) { return connManager.getActiveNetworkInfo().isAvailable(); } return false; } }
好了,剩下的就是登陆界面和主界面了,Activity的布局和逻辑都非常简单,我直接把代码贴上。
LoginActivty:
package com.jy.jerseyclient; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import com.jy.jerseyclient.entity.Response; import com.jy.jerseyclient.entity.User; import com.jy.jerseyclient.service.WebService; import com.jy.jerseyclient.utils.JsonUtil; import com.jy.jerseyclient.utils.NetworkUtil; public class LoginActivity extends AppCompatActivity { private final static String TAG = "LoginActivity"; private EditText edt_username; private EditText edt_password; private Button btn_login; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); initView(); initEvent(); } private void initView() { edt_username = (EditText) findViewById(R.id.edt_username); edt_password = (EditText) findViewById(R.id.edt_password); btn_login = (Button) findViewById(R.id.btn_login); } private void initEvent() { btn_login.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final String username = edt_username.getText().toString(); if (TextUtils.isEmpty(username)) { Toast.makeText(LoginActivity.this, "user name must not be empty", Toast.LENGTH_SHORT).show(); return; } final String password = edt_password.getText().toString(); if (TextUtils.isEmpty(password)) { Toast.makeText(LoginActivity.this, "password must not be empty", Toast.LENGTH_SHORT).show(); return; } if (NetworkUtil.isNetWorkOpened(LoginActivity.this)) { new AsyncTask<String, Integer, Response>() { @Override protected Response doInBackground(String... params) { Response response = WebService.login(username, password); return response; } @Override protected void onPostExecute(Response response) { if (response == null) { Toast.makeText(LoginActivity.this, "login failed,response is null", Toast.LENGTH_SHORT).show(); } else if (200 == response.getStatus()) { Log.e(TAG, "user======" + response.toString()); Object obj = response.getResponse(); if (obj == null) { Toast.makeText(LoginActivity.this, "login failed,the response field is null", Toast.LENGTH_SHORT).show(); } else { User user = JsonUtil.getEntity(obj.toString(), User.class); if (user == null) { Toast.makeText(LoginActivity.this, "login failed,illegal json", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(LoginActivity.this, "login succeed", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(LoginActivity.this, MainActivity.class); intent.putExtra("user", user); startActivity(intent); finish(); } } } else { Toast.makeText(LoginActivity.this, "login failed," + response.getMessage(), Toast.LENGTH_SHORT).show(); } super.onPostExecute(response); } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { Toast.makeText(LoginActivity.this, "network is not available", Toast.LENGTH_SHORT).show(); } } }); } }
LoginActivty的布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:orientation="vertical" android:background="#FFFFFF" android:gravity="center" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/tv_username" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="用户名" android:textSize="18sp" android:layout_alignParentLeft="true" /> <EditText android:id="@+id/edt_username" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_toRightOf="@id/tv_username" android:textSize="18sp" /> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" > <TextView android:id="@+id/tv_password" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="密 码" android:textSize="18sp" android:layout_alignParentLeft="true" /> <EditText android:id="@+id/edt_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_toRightOf="@id/tv_password" android:inputType="textPassword" android:textSize="18sp" /> </RelativeLayout> <Button android:id="@+id/btn_login" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="登录" android:textSize="18sp" android:gravity="center" android:layout_marginTop="50dp" /> </LinearLayout>
MainActivity:
package com.jy.jerseyclient; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.jy.jerseyclient.entity.Response; import com.jy.jerseyclient.entity.User; import com.jy.jerseyclient.service.WebService; import com.jy.jerseyclient.utils.JsonUtil; import com.jy.jerseyclient.utils.NetworkUtil; import java.util.ArrayList; public class MainActivity extends AppCompatActivity { private final static String TAG = "MainActivity"; private TextView tv_welcome; private Button btn_show_all_users; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initEvent(); initData(); } private void initView() { tv_welcome = (TextView) findViewById(R.id.tv_welcome); btn_show_all_users = (Button) findViewById(R.id.btn_show_all_users); } private void initEvent() { btn_show_all_users.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (NetworkUtil.isNetWorkOpened(MainActivity.this)) { new AsyncTask<String, Integer, Response>() { @Override protected Response doInBackground(String... params) { Response response = WebService.getAllUsers(); return response; } @Override protected void onPostExecute(Response response) { if (response == null) { Toast.makeText(MainActivity.this, "request failed,response is null", Toast.LENGTH_SHORT).show(); } else if (200 == response.getStatus()) { Log.e(TAG, "user======" + response.toString()); Object obj = response.getResponse(); if (obj == null) { Toast.makeText(MainActivity.this, "request failed,the response field is null", Toast.LENGTH_SHORT).show(); } else { ArrayList<User> users = (ArrayList<User>) JsonUtil.getEntityList(obj.toString(), User.class); if (users == null) { Toast.makeText(MainActivity.this, "request failed,illegal json", Toast.LENGTH_SHORT).show(); } else { StringBuilder allUserInfo = new StringBuilder(); for (User u : users) { allUserInfo.append(u.getName() + ":" + u.getSex() + "," + u.getAge() + "\n"); } tv_welcome.setText(allUserInfo); } } } else { Toast.makeText(MainActivity.this, "request failed," + response.getMessage(), Toast.LENGTH_SHORT).show(); } super.onPostExecute(response); } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { Toast.makeText(MainActivity.this, "network is not available", Toast.LENGTH_SHORT).show(); } } }); } private void initData() { Intent intent = getIntent(); if (intent != null) { User user = (User) intent.getExtras().getSerializable("user"); if (user != null) { tv_welcome.setText("Welcome," + user.getName()); } } } }
MainActivty的布局文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context=".MainActivity"> <TextView android:id="@+id/tv_welcome" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="欢迎您" android:textSize="18sp" /> <Button android:id="@+id/btn_show_all_users" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="显示所有用户" android:textSize="18sp" android:layout_below="@id/tv_welcome" android:layout_marginTop="20dp" /> </RelativeLayout>
最后,别忘了配置清单文件,如下所示:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.jy.jerseyclient" > <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" > <activity android:name=".LoginActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".MainActivity" /> </application> </manifest>
好了,到此客户端和服务端均已大功告成,整个项目其实就是用最简单的模型来说明怎样利用Jersey这个框架来为我们的移动端搭建后台接口,其中,难免有诸多纰漏之处,请大家多多批评指正,谢谢!
最后,附上项目源码的下载链接:
客户端:①http://download.csdn.net/detail/owoxiangxin12/9492575 ②https://github.com/monkey1992/JerseyClient
服务端:①http://download.csdn.net/detail/owoxiangxin12/9492571 ②https://github.com/monkey1992/JerseyServer
相关文章推荐
- 初学android开发--day07
- android使用matrix控制图片的旋转,缩放
- Android设计模式之单例模式的七种写法
- android手机锁屏
- Android Path Time ScrollBar(Path 时间轴)
- RxAndroid + Retrofit + Databinding
- Android的.so文件、ABI和CPU的关系
- android一些常用事件
- Android Launcher 启动简述 <2>
- android实现自定义SwichButton
- Android课程---添加黑名单的练习(课堂讲解)
- Android瀑布流照片墙、滑动切换图片
- android学习之unbindservice一
- coco游戏android.mk
- Android中Path类的使用
- Android中Canvas、Path 和 Paint 的方法说明
- android点击事件不灵敏
- Android Studio 配置使用注解框架AndroidAnnotations
- Android ImageView的scaleType属性与adjustViewBounds属性
- 承香墨影 Android--Matrix图片变换处理