一、前言
最近公司需要对接webservice的服务器接口,从而给了时间研究webservice的内部协议。webservice中用到的是soap协议,简单来说,soap协议就是http协议+XML格式,它是RPC调用的一种。Android中的主流http请求框架用的是OKhttp,请求解耦框架是Retrofit。既然soap是基于http协议的,那么通过Retrofit框架处理是没有问题的。
soap即简单的面向对象协议,类比onc rpc是一个基于二进制的调用,这并不利于扩展,所以之后才有了面向对象的soap协议,在XML中参数的顺序和增加并不会对之前的代码有所影响。
在网上找了些Retrofit封装代码,比较好比较入门的https://github.com/xiewenfeng/RetorfitWebServiceSample,本文也是在此基础上分析完善。
二、SOAP协议
1)、WebService请求协议
POST http://www.webxml.com.cn/WebServices/WeatherWebService.asmx HTTP/1.1
Content-Type: text/xml;charset=UTF-8
Content-Length: 347
SOAPAction: http://WebXml.com.cn/getWeatherbyCityName
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Body xmlns="http://WebXml.com.cn/">
<getWeatherbyCityName>
<theCityName>合肥</theCityName>
</getWeatherbyCityName>
</soap:Body>
</soap:Envelope>
从webservice发送的协议来看,很容易看出,它是基于Http协议的,增加了header字段SOAPAction,以及将文本类型改成Content-Type: text/xml;charset=UTF-8。这样就能告诉服务器,参数类型是XML,以及调用哪个方法解析。
下面再来看看SOAP的语法结构:
<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
<soap:Header>
...
...
</soap:Header>
<soap:Body>
...
...
<soap:Fault>
...
...
</soap:Fault>
</soap:Body>
</soap:Envelope>
从SOAP结构中可以看到有四大块:
- Envelope必选,可把此 XML 文档标识为一条 SOAP 消息
- Header可选,头部信息附属功能
- Body必选,调用信息(传递的参数)或者响应信息(返回的结果)
- Fault可选,提供有关在处理此消息所发生错误的信息
Envelope
Envelope为根元素,没有该元素的一律不会是SOAP消息。
其中soap:Envelope中的soap是根据xmlns:soap="http://www.w3.org/2003/05/soap-envelope"而来的,它们必须一一匹配。
Body
Body元素在请求时放入传递给服务器的参数,返回时是服务器返回的数据。
2)、WebService返回协议
先看下上面的请求的返回例子:
200 OK http://www.webxml.com.cn/WebServices/WeatherWebService.asmx
Content-Length: 3229
Content-Type: application/soap+xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
Date: Sun, 05 May 2019 07:11:36 GMT
OkHttp-Sent-Millis: 1557040300871
OkHttp-Received-Millis: 1557040300923
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<getWeatherbyCityNameResponse xmlns="http://WebXml.com.cn/">
<getWeatherbyCityNameResult>
<string>安徽</string>
<string>合肥</string>
<string>58321</string>
<string>58321.jpg</string>
<string>2019/5/5 14:51:16</string>
<string>15℃/28℃</string>
<string>5月5日 多云</string>
<string>东风3-4级转东北风小于3级</string>
<string>1.gif</string>
<string>1.gif</string>
<string>今日天气实况:气温:27℃;风向/风力:东北风 3级;湿度:46%;紫外线强度:弱。空气质量:良。</string>
<string>紫外线指数:弱,辐射较弱,涂擦SPF12-15、PA+护肤品。
健臻·血糖指数:易波动,血糖易波动,注意监测。
穿衣指数:舒适,建议穿长袖衬衫单裤等服装。
洗车指数:较适宜,无雨且风力较小,易保持清洁度。
空气污染指数:良,气象条件有利于空气污染物扩散。</string>
<string>12℃/23℃</string>
<string>5月6日 多云</string>
<string>东风3-4级转小于3级</string>
<string>1.gif</string>
<string>1.gif</string>
<string>13℃/20℃</string>
<string>5月7日 多云转小雨</string>
<string>东风小于3级</string>
<string>1.gif</string>
<string>7.gif</string>
<string>合肥市,古称庐州,又名庐阳,位于安徽省中部,地处江淮之间、巢湖北岸,辖东市、西市、中市、郊区4区和长丰、肥东、肥西3县。总面积7266平方公里,人口425.9万。市内道路宽阔,绿树成荫,景色优美,既多现代建筑,又有名胜古迹,是一座古老而又年青的城市。合肥市位于江淮之间,处于中纬度地带,为亚热带湿润季风气候。年平均气温在15℃-16℃之间,极端最低气温-20.6℃,极端最高气温38℃以上。年平均降水量在900-1000毫米之间。全年气温变化的特点是季风明显、四季分明、气候温和、雨量适中、春温多变、秋高气爽、梅雨显著、夏雨集中,总之气候条件优越,气候资源丰富。合肥市素以“三国旧地、包拯故里”闻名于世,具有“淮右襟喉、江南唇齿”的战略地位,常为兵家必争之地。三国时魏将张辽大败孙权十万大军的逍遥津战役,即发生在这里。两千多年前,这里就已开始形成商业都会。秦、汉在此设郡县,明、清为庐州府治,民国时为安徽省省会,如今已是千樯鳞次、商贾辐凑的商业都会。合肥素有“绿色城市”、“花园城市”的美名,其环城公园便修建在合肥古城墙的基础之上,沿着起伏的岗丘地形,加之原有的绿林带及护城河,精筑而成。环城公园总长约达9公里,分为六个景区,其中较为著名的有茂林修竹,夏河朝露的银河景区;湖峦相映、水碧枫赤的西山景区;林木葱茏、芳草常青的环北景区。这样的环城公园无城墙之隔阂,面水而立,一派迷人旖旎的江南风光。</string>
</getWeatherbyCityNameResult>
</getWeatherbyCityNameResponse>
</soap:Body>
</soap:Envelope>
从返回的可以看出来它是包含了以上的Envelope和Body,解析只需要将XML格式化成对象格式即可。
小结
从以上的协议分析,使用Retrofit请求和解析没有任何问题。请求的是Post请求,现在将请求的消息按照SOAP协议封装成符合服务器要求的格式,再将其字符串注入到post请求的Body中即可。返回的消息是个XML格式的字符串,现在只需要按照后台给定的消息去解析即可,也可以通过XML解析框架处理,这会更加简单。
三、实践Retrofit解析
引用的第三方库:
- retrofit :网络请求框架
- logging-interceptor : OKhttp日志打印
- converter-simplexml : 生成及解析XML框架
1)、请求参数编写
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Body xmlns="http://WebXml.com.cn/">
<getWeatherbyCityName>
<theCityName>合肥</theCityName>
</getWeatherbyCityName>
</soap:Body>
</soap:Envelope>
需要我们将代码拼接成如上的格式,其中theCityName里面的内容是传入的。
为此使用了converter-simplexml框架将对象封装成XML,建立了三个类。
// Root代表外层
@Root(name = "soap:Envelope")
@NamespaceList({
@Namespace(reference = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi"),
@Namespace(reference = "http://www.w3.org/2001/XMLSchema", prefix = "xsd"),
@Namespace(reference = "http://www.w3.org/2003/05/soap-envelope", prefix = "soap")
})
public class RequestEnvelope {
@Element(name = "soap:Body")
private RequestBody requestBody;
public void setRequestBody(RequestBody requestBody) {
this.requestBody = requestBody;
}
}
@NamespaceList({
@Namespace(reference = "http://WebXml.com.cn/")
})
public class RequestBody {
@Element(name = "getWeatherbyCityName")
private RequestModel getWeatherbyCityName;
public void setGetWeatherbyCityName(RequestModel getWeatherbyCityName) {
this.getWeatherbyCityName = getWeatherbyCityName;
}
}
/**
* 描述:参数
* Created by PHJ on 2019/5/5.
*/
public class RequestModel {
@Element(name = "theCityName")
public String cityName; //城市名字
public void setCityName(String cityName) {
this.cityName = cityName;
}
}
以此转化,就是所需要的XML。并将XML放入到请求体即可。
2)、解析响应信息
200 OK http://www.webxml.com.cn/WebServices/WeatherWebService.asmx
Content-Length: 3229
Content-Type: application/soap+xml; charset=utf-8
Server: Microsoft-IIS/7.5
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
Date: Sun, 05 May 2019 07:11:36 GMT
OkHttp-Sent-Millis: 1557040300871
OkHttp-Received-Millis: 1557040300923
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<getWeatherbyCityNameResponse xmlns="http://WebXml.com.cn/">
<getWeatherbyCityNameResult>
<string>安徽</string>
<string>合肥</string>
<string>58321</string>
<string>58321.jpg</string>
<string>2019/5/5 14:51:16</string>
<string>15℃/28℃</string>
<string>5月5日 多云</string>
<string>东风3-4级转东北风小于3级</string>
<string>1.gif</string>
<string>1.gif</string>
<string>今日天气实况:气温:27℃;风向/风力:东北风 3级;湿度:46%;紫外线强度:弱。空气质量:良。</string>
<string>紫外线指数:弱,辐射较弱,涂擦SPF12-15、PA+护肤品。
健臻·血糖指数:易波动,血糖易波动,注意监测。
穿衣指数:舒适,建议穿长袖衬衫单裤等服装。
洗车指数:较适宜,无雨且风力较小,易保持清洁度。
空气污染指数:良,气象条件有利于空气污染物扩散。</string>
<string>12℃/23℃</string>
<string>5月6日 多云</string>
<string>东风3-4级转小于3级</string>
<string>1.gif</string>
<string>1.gif</string>
<string>13℃/20℃</string>
<string>5月7日 多云转小雨</string>
<string>东风小于3级</string>
<string>1.gif</string>
<string>7.gif</string>
<string>合肥市,古称庐州,又名庐阳,位于安徽省中部,地处江淮之间、巢湖北岸,辖东市、西市、中市、郊区4区和长丰、肥东、肥西3县。总面积7266平方公里,人口425.9万。市内道路宽阔,绿树成荫,景色优美,既多现代建筑,又有名胜古迹,是一座古老而又年青的城市。合肥市位于江淮之间,处于中纬度地带,为亚热带湿润季风气候。年平均气温在15℃-16℃之间,极端最低气温-20.6℃,极端最高气温38℃以上。年平均降水量在900-1000毫米之间。全年气温变化的特点是季风明显、四季分明、气候温和、雨量适中、春温多变、秋高气爽、梅雨显著、夏雨集中,总之气候条件优越,气候资源丰富。合肥市素以“三国旧地、包拯故里”闻名于世,具有“淮右襟喉、江南唇齿”的战略地位,常为兵家必争之地。三国时魏将张辽大败孙权十万大军的逍遥津战役,即发生在这里。两千多年前,这里就已开始形成商业都会。秦、汉在此设郡县,明、清为庐州府治,民国时为安徽省省会,如今已是千樯鳞次、商贾辐凑的商业都会。合肥素有“绿色城市”、“花园城市”的美名,其环城公园便修建在合肥古城墙的基础之上,沿着起伏的岗丘地形,加之原有的绿林带及护城河,精筑而成。环城公园总长约达9公里,分为六个景区,其中较为著名的有茂林修竹,夏河朝露的银河景区;湖峦相映、水碧枫赤的西山景区;林木葱茏、芳草常青的环北景区。这样的环城公园无城墙之隔阂,面水而立,一派迷人旖旎的江南风光。</string>
</getWeatherbyCityNameResult>
</getWeatherbyCityNameResponse>
</soap:Body>
</soap:Envelope>
解析XML就是去除外层的包装,转化成对象,并且保存。
在此之前需要配置转化格式:
private static Strategy strategy = new AnnotationStrategy();
private static Serializer serializer = new Persister(strategy);
// 设置电脑链接
instance.retrofit = builder.baseUrl(BASE_URL)
// 设置client
.client(client)
// 设置Json解析器
.addConverterFactory(SimpleXmlConverterFactory.create(serializer))
.build();
采用了SimpleXmlConverterFactory解析对应的XML,当然这部分也可以自己获取字符串后自己解析。
详细代码可以参考GitHub代码