APP下载

基于HTTP的安卓与服务器交互方法的实现

2015-04-13马昭征

无线互联科技 2015年3期
关键词:通信安卓

马昭征

摘 要:文章主要实现了基于HTTP的安卓与服务器交互的几种实用方法。简析安卓上传数据和下载数据的传输过程以及服务器接受收据并响应的过程。通过比较各参数,以简单直接的方法实现上述目的。文章也附上实际案例,由安卓端的上传和下载文字及图片,演示了客户端对于数据请求的发送、服务器对该请求的处理、服务器向安卓发送数据以及安卓处理服务器相应的整个流程。验证了文中所介绍方法的实用性。

关键词:安卓;HttpClient;通信

2014年7月,市场分析机构Strategy Analytics公布了2014年第二季度智能手机操作系统全球分布情况。报告显示,目前安卓操作系统的全球市场份额已达85%(有史以来最高比重),而苹果的iOS、微软的WindowsPhone等系统占比均有所下滑[1]。近年来,在谷歌努力的研发和推广下,安卓迅速兴起和发展,碎片化问题也随着Android 4.4的发布得到了改观。开发者们对安卓开发的热情也随之空前的高涨。然而除了诸如计算机、录音机、手电筒等完全本地化的应用,目前安卓手机上大部分应用几乎都要和网络打交道。作为一个开发者,掌握安卓的通讯机制是十分必要的。目前而言,安卓与服务器的通讯最为普遍。这种通讯经常采用超文本传送协议,即HTTP(Hypertext transfer protocol)。另外也有FTP(File Transfer Protocol)、Telnet、SMTP(Simple Mail Transfer Protocol)等几种不同的协议,但考虑到使用的广泛性,文章仅介绍最常用的基于HTTP的通交互方式。在这当中较为常用的访问网络方法有如下几种:直接使用统一资源定位符,即URL(Uniform Resource Locator)的HttpURLConnection、Apache的HttpClient、还有就是利用WebView等等。这几种方法各有利弊。WebView主要是用来显示网页的,而就数据的传输而言,前两者使用广泛。java.net包提供了通过HTTP访问资源的基本功能,即HttpURLConnection。它是URLConnection的一个子类,是一个轻量级的类,它的实例可以共享连接到HTTP服务器的基础网络。HttpURLConnection因为较为轻便,因此理论上传输速度较快,另外其可扩展性也较强。但是由于协议应用本身的复杂性,使得在大量实际项目单纯使用Java语言的软件开发工具包进行HTTP编程仍然相对比较困难。而且在Android2.2之前有一个未修复的错误。针对这种情况,开源软件组织Apach推出了HttpClient开源组件,并且提供稳定持续的升级版本。它提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP 协议最新的版本和建议。它封装了很多实用的方法共开发者调用,开发者无需再写大量代码。因此在实际项目中采用HttpClient组件进行HTTP协议编程是一种高效经济的解决方案[2]。因此,文章介绍的与服务器交互的方法是基于HttpClient的。

1 安卓端

1.1 安卓端的上传

首先是请求方式的选择。众所周知,HTTP的请求方法有GET、POST、HEAD、PUT、DELETE、TRACE和OPTIONS几种,其中用的较为广泛的有GET和POST两种。相对而言,我更加推荐使用POST方法。原因是GET方法的请求中只包含request-line部分,它将数据直接添加在URI中,这样的话一些重要且私密的数据就会暴露在地址栏里。而POST则可以通过添加request-body,将数据放进body里再进行传送,这样不会出现数据暴露的安全隐患。因此相较于GET方法,POST的传输数据量更大,安全性也更高。

要注意的是,使用POST方式看不到传送的数据不是因为POST方式自身的处理,而是因为浏览器做了相应的处理和限制,因此使数据不会被明显暴露在浏览器界面上。但是只要利用一些工具还是可以查找到数据的。所以从这个角度讲,GET和POST都是不安全的。但是相对而言,GET是把数据直接显示在地址栏里,而POST则要隐秘许多。因此POST相对GET确实比较安全。

在安卓客户端要使用HttpClient首先要创建一个它的实例。由于安卓自带有HttpClient,所以可以直接调用系统应用程序编程接口来创建。此后需要创建请求方式的实例。

HttpPost post=new HttpPost(URL);

注意,这里的URL是请求的地址,务必要填写,不然的话在执行POST方法时会报一个NullPointer的空指针错误。由于文章中的服务器采用的是Struts2+Hibernate框架,因此URL的基本格式是:

http://服务器ip地址/项目名/action名称.action

如果需要传送一些数据,在这里可以用刚才说到的向request-body添加数据的方式来传送。HttpClient的结构如图1所示。

由图1可以看到,我们关注的各种HTTP方法都被定义成一个个独立的类,而他们都继承自HttpRequestBase。其中比较特殊一点的是HttpPut和HttpPost,可以看出只有他们都是继承自HttpEntityEnclosingRequestBase这个抽象类。这是因为它们需要设置request-body,即请求实体。而HttpEntityEnclosingRequestBase里有HttpEntity的成员变量。HttpEntity是一个接口,程序员可以根据具体项目中需要传递的数据类型选择ByteArrayEntity、StringEntity、InputreamEntity、FileEntity等等类。他们均实现了HttpEntity这个接口。除此之外HttpClient还提供了UrlEncodedFormEntity类和MultipartEntity类来满足更多的需求。

在此,笔者介绍一种通过模拟超文本标记语言HTML(HyperText Mark-up Language)的表单来传送POST请求里参数的方法。而用到的工具则是UrlEncodedFormEntity这个类。

首先创建一个List的实例:

List params = new ArrayList ();

这里的NameValuePair是用于关联某一名称与某一值的专门类。

第二步就可以向实例params里添加需要传送的数据了。使用的是params的add方法:add(param1, String1)。

这里有两个参数。实际上就相当于Map里的键值对的概念。而这里的第二个参数必须是字符串格式。

再将添加完数据后的List实例params加入POST的body里。如果数据中有汉字,必须设置POST的编码格式:

post.setEntity(new UrlEncodedFormEntity(params,"UTF-8"));

之后可以执行请求并且读取Response:

HttpResponse response = client.execute(post);

最后可用releaseConnection释放连接。

如果需要上传图片、音频等文件,也可以使用这种模拟表单的方法。可以使用刚刚提到的在HttpClient程序包中另一个类MultipartEntity。它同样实现了HttpEntity接口。但由于该类使用起来并不是最方便,并且已经介绍过表单模拟的相关方法了。是这里介绍一种更为简单的方法,即使用FileEntity类来实现。只需要实例化FileEntity

FileEntity entity = new FileEntity(file, "binary/octet-stream");

再同样用setEntity方法后就可以执行了。

1.2 安卓端的接受

上一节讲到接受服务器的响应可以实例HttpResponse来实现:

HttpResponse response = client.execute(post);

然后可以用if语句来判断Response的情况:

if(re.getStatusLine().getStatusCode()= =HttpStatus.SC_OK)

这里的HttpStatus.SC_OK即200,代表整个传送顺利进行。

接下来对服务器传递过来的数据进行解析。

首先要获取承载数据的实体:

HttpEntity entity = response.getEntity();

判断entity是否为空,如果非空则从里面获取其实际的数据。

这里需要用到EntityUtils这个类。它是一个工具类,是为HttpEntity对象提供的静态帮助类。利用它可以快速获取服务器传递的数据:

String out = EntityUtils.toString(n, "UTF-8");

如果是图片、音频等转换而成的byte数组,则可以用如下方法直接得到byte数组:

byte[] by = EntityUtils.toByteArray(en);

另外,如果服务器的数据是通过JSON (JavaScript Object Notation)包装过的,则还需将字符串out转换为JSON对象:

JSONObject json = new JSONObject(out);

最后就可以从json实例里直接得到服务器反馈的数据了,如:

int result1 = jsonObject1.getInt("result1");

Sring result2=jsonObject1.getString("result2");

在此对JSON进行简单介绍。

JSON是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScript Programming Language,Standard ECMA-262 3rd Edition- December 1999的一个子集。JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C,C++,C#,Java,JavaScript,Perl,Python等)。这些特性使JSON成为理想的数据交换语言。由于JSON的数据格式非常简单,我们可以用JSON传输一个简单的String、Integer、Boolean、也可以传输一个数组或者一个复杂的Object对象[3]。

2 服务器端

2.1 服务器的接受

服务器用的是Struts2+Hibernate框架。其中Struts框架是整个系统应用框架的基础,它实现了各个模块的低耦合,使用Hibernate框架只考虑持久层应用。所谓的持久层就是由DAO(Data Access Object)组件构成,简单说就是屏蔽了与数据库打交道的细节,只需调用DAO接口中的方法就可以对后台数据操作[4]。用户通过表示层向服务器发送应用请求,Struts框架的主要功能就是拦截用户的操作请求,解析用户请求的对象,并把请求转发到相应的Action类处理,在Action类中,调用相应的持久层再把操作结果返回前端表示层显示。在持久层,Hibernate主要责任就是负责实体对象与数据库之间的交互映射,使得我们只需通过操作DAO层的实体对象就可以操作数据库,获得我们想要的数据,再经过业务逻辑层、表示层最终返回给表示层,展示给用户使用。因此它接受客户端传来的数据十分简单。按照Struts的规则,在Action类里面写对应的方法即可[5]。

这里需要注意的有4点。

(1)在struts.xml里配置action时,package里的extends一定要写成extends="json-default"。同时result里的type要写成type="json"。这样就可以通过JSON来传递数据了。

(2)而Action类需要实现ServletRequestAware和ServletResponseAware这两个接口,这样就可以得到Request和Response的实例了。

(3)所有的Action类都要抛出IOException。

(4)Action类的成员变量要有getter()和setter()方法[6]。

接下来只需要根据传递数据的格式来取数据:

request.getParameter("name");

request.getInputStream;

这里的"name"是NameValuePair里Value所对应的Name。而InputStream则适用于图片等文件的读取。

2.2 服务器的响应

与接收数据一样,也是在Action类里写对应的方法,再在struts.xml里配置。

要通过JSON传递数据先要创建JSONObject的 实例:

JSONObject jsonObject = new JSONObject();

如果是传递多组数据,在此可以使用在JSONArray里面添加JSONObject的方法:

JSONArray jsonArray = new JSONArray();

之后向jsonObject里添加需要反馈给客户端的数据:

jsonObject.put("result", result);

这里也要注意如果数据包含中文字符,则需要设置Response的编码格式: response.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8");

最后将jsonObject转换为String,利用Response的Writer把数据发送出去:

response.getWriter().write(jsonObject.toString());

对于发送图片,可以将服务器上待发送的文件通过文件输出流和字节数组流转换为字节数组。之后获取Response的OutputStream的实例,就可以由该实例的write方法将字节数组发送出去[7]。基本流程可参考一下代码片段:

FileInputStream fis=new FileInputStream(file);

ByteArrayOutputStream bao =

new ByteArrayOutputStream();

int data = -1;

while ((data = fis.read()) != -1) {

bao.write(data);

}

byte[] b = bao.toByteArray();

OutputStream os = response.getOutputStream();

os.write(b);

os.flush();

3 实际案例

3.1 开发环境

3.1.1 安卓端

操作系统:MOKEE - Android 4.4.4

手机型号:Sony L36H

开发环境:Eclipse Ver 4.2.0

3.1.2 PC

操作系统:Windows 7 旗舰版 64位

3.1.3 服务器:apache-tomcat-7.0.57

集成环境:MyEclipse Ver 4.3.0

3.1.4 工程名:SSHTest

3.2 具体实施

该案例是对文章所讲述的方法做一次实际的操作来进行验证。因此分别实现文字的上传与下载和图片的上传与下载四个功能。界面布局如图2所示。

需要上传的图片和文字打开应用时就直接显示在界面上。

3.2.1 安卓端的上传和服务器的接受

安卓端:

在进入正题之前,有两点需要注意。首先要在AndroidManifest.xml文件里添加INTERNET权限。不添加这个用户权限,该应用无法进行网络访问。但这往往是最容易忘记的一步。

第二点,在Android 4.0以上的版本,只能由主线程更改界面,而访问网络只能在子线程进行。所以这里可以用异步类AsyncTask和Handler机制。

接下来直接进入正题。

首先找到图片文件的路径,如果文件是储存在SD卡里的话,还需要在AndroidManifest.xml里添加WRITE_EXTERNAL_STORAGE权限。随后将文件添加到FileEntity里。之后创建HttpClient和HttpPost的实例并且执行。

代码:

//这里的 arg0[0]是AsyncTask传递进来的第一//个URL。对应服务器里相应的Action

HttpPost post = new HttpPost(arg0[0]);

HttpClient client = new DefaultHttpClient();

//添加图片

File file = new File(path);

FileEntity entity = new FileEntity(file, "binary/octet-stream");

entity.setContentEncoding("binary/octet-stream");

post.setEntity(entity);

//或者添加文字

String wordsToUpload = words.getText().toString().trim();

List params = new ArrayList ();

params.add(new BasicNameValuePair ("mystring", wordsToUpload));

post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));

//执行请求

HttpResponse re = client.execute(post);

服务器:

在相应的Action方法里只需要一步就可以获取安卓端发过来的数据了:

//获取图片流

InputStream is = request.getInputStream();

//获取模拟表单数据

String string=request.getParameter("mystring");

注意,这里的"mystring"和安卓端添加数据时

params.add(new BasicNameValuePair ("mystring", wordsToUpload));

这一句里的"mystring"是相对应的。该值由程序员自定义。

在接受完数据后,将图片储存在电脑的硬盘里,我选择的路径是D盘的FromAndroid文件夹。再将字符串打印到控制台显示出来。

如果程序顺利执行,安卓端收到HttpResponse的StatusCode将会等于200。此时执行一个Toast提示用户上传成功,如图3所示。反之,如果StatusCode不等于200,则说明执行过程存在错误。那么也执行Toast提醒上传失败,请重新上传!此后可以查看Eclipse的LogCat和MyEclipse的Console来确认哪一步出错。

3.2.2 安卓端的下载和服务器的响应

安卓端的请求:

由于下载过程中安卓端无需向服务器发送数据,因此只需要简单地请求服务器中响应的Action就可以了。不用再添加request-body。

服务器的响应:

当服务器接受到安卓发出的请求后,与URL相对应的Action就开始工作,如图4所示。

代码:

//发送图片。图片位于D盘内

File file = new File("D:\\FromServer.png");

FileInputStream fis= new FileInputStream(file);

ByteArrayOutputStream bos=new ByteArrayOutputStream();

int data = -1;

while ((data = fis.read()) != -1) {

bops.write(data);

}

byte[] b = bos.toByteArray(); //最终将图片转换为字节数组

//得到response的输出流并写出

OutputStream os = response.getOutputStream();

os.write(b);

os.flush();

fis.close();

bos.close();

//发送字符组

//创建JSON格式的列表用户添加多组数据

JSONArray jsonArray = new JSONArray();

JSONObject jsonObject1 = new JSONObject();

jsonObject1.put("id", 1);

jsonObject1.put("place", "阳明山");

//创建JSONObject对象,将数据一一填入

JSONObject jsonObject2 = new JSONObject();

jsonObject2.put("id", 2);

jsonObject2.put("place", "日月潭");

//向列表里添加各个JSONObject。它们将顺序//排列

jsonArray.add(0, jsonObject1);

jsonArray.add(1, jsonObject2);

//从这里开始发送。

//设置字符编码格式和数据/格式

response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=utf-8");

response.getWriter().write(jsonArray.toString());

安卓端的接受主要就是从HttpResponse里获取响应实体:

HttpEntity entity = re.getEntity();

第二部分里提到过用于接受服务器数据的帮助类EntityUtils。在这里就要使用它。

按照服务器发送的数据格式我们可以调用EntityUtils里不同方法。

对于字符串可以使用EntityUtils. toString()方法。对于字节数组可以使用EntityUtils.toByteArray()方法。

byte[] by = EntityUtils.toByteArray(entity);

String out = EntityUtils.toString(entity, "UTF-8");

随后只需把获取的数据装入Message中,并用Handler发送给主线程,就可以显示在界面上了,如图5所示。

4 结语

文章对安卓与服务器之间交互的具体方法做了相应的介绍。采用的通讯方式是HTTP。它采用了请求/响应模型。安卓端向服务器发送的请求包含了:请求的方法、URL、协议版本和客户信息等等。而服务器势必要有一个反馈。如果安卓端的请求内容中包括对服务器内客户资料、服务器文件或其他数据的请求,那么服务器在响应的同时还需要将这些数据一并传送到安卓端。因此文章就上传和下载这两个生活中最常见的动作为主,分别从安卓端和服务器端介绍了它们之间通讯的具体过程。

而在具体方法的选择中,文章是本着简单实用多样的原则进行介绍的。在传输方式上选择了更为安全和更具传输量的POST方法。传送字符串数据用的是模拟HTML表单的NameValuePair列表方式。类似键值对的输入过程一目了然。需要关心的只有字符编码的统一设置,规避乱码的问题。而对于图片的传送则用到了继承HttpEntity接口的FileEntity。它是专门用于传送文件的类,只需在创建其实例时将文件放入,再设置文件的格式就可以方便地传送该文件到服务器;而对于下载字符串数据,则用了轻量级的数据交换格式JSON。下载图片则是将文件转换为字节数组再进行传输。

最后的实例分析也验证了以上方法切实可行。希望能让读者对于安卓与服务器之间的通讯交互的细节有更多的实际认识。

猜你喜欢

通信安卓
MIPS安卓平台上ARM二进制翻译系统
文物表情包
安卓系统或成智能汽车标配
一种基于安卓系统的手机侧抓包分析方法
基于“一级调度、两级运维”的通信管理体系研究①
对数字微波通信技术的研究
安卓L未至安卓M来了!安卓首泄漏M系统