最近Retrofit+RxJava还是非常火的,搭配一下MVP那开发简直就一个字爽。
但是现在很多比较老一点的公司还在使用WebService+xml,真的很让人感到忧伤。不过真的想使用Retrofit替代ksoap也不是不行的,至于效率到底高不高,还是要亲测才知道。
首先说一下WebService其实也是基于http的,也是同样可以用http去请求的。由于每个公司的协议都不同,所以在请求的时候,肯定是难以写出一个统一适用的请求框架的,最开始我是根据我公司WebService请求格式来定义各种请求用的Xml-bean。
具体可参照http://blog.csdn.net/huanghunhou705/article/details/51133893
这篇博客描述了如何构建请求用的xml。
首先贴一下我公司的WebService请求格式:
POST /WebService/UserService.asmx HTTP/1.1
Host: android.goodjob.cn
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://android.goodjob.cn/UserLogin"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<AuthenticatedHeader xmlns="http://android.goodjob.cn/">
<AccountID>string</AccountID>
<Pin>string</Pin>
<EncryptedMacAddress>string</EncryptedMacAddress>
</AuthenticatedHeader>
</soap:Header>
<soap:Body>
<UserLogin xmlns="http://android.goodjob.cn/">
<userName>string</userName>
<password>string</password>
</UserLogin>
</soap:Body>
</soap:Envelope>
响应格式:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<UserLoginResponse xmlns="http://android.goodjob.cn/">
<UserLoginResult>boolean</UserLoginResult>
</UserLoginResponse>
</soap:Body>
</soap:Envelope>
看上去好复杂。接下来我会根据我公司这种格式,来写一下如何使用Retrofit请求服务器数据.
首先我们先实例化一个Retrofit对象
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
Strategy strategy = new AnnotationStrategy();
Serializer serializer = new Persister(strategy);
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(SimpleXmlConverterFactory.create(serializer))
.client(new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build())
.baseUrl("http://android.goodjob.cn/WebService/")
.build();
在Retrofit中.client()方法可以传一个OkHttpClient对象,可以自己定义一个,配置缓存等等东西。这里为了讲述原理,只简单新建了一个。
接下来要写API了,用过Retrofit的同学都知道,我们都是通过这个api来定义请求参数,和返回参数的格式的。那应用在WebService上又应该怎么去写呢。
public interface UsStatesApi {
@Headers({
"Content-Type: application/soap+xml; charset=utf-8 ",
"Accept-Charset: utf-8"
})
@POST("UserService.asmx")
Observable<LoginResponseEnvelope> UserLogin(@Body LoginRequestEnvelope body);
}
看上去似乎还可以,接下来就是定义xml-bean了,这一部分内容我只简单描述一下,根据xml的格式建立对应的bean,目的是成功生成所需的xml和正确解析收到xml,为了保证正确性,之前在OkHttp中添加了log拦截器,可以看到发送的xml是否符合要求,我们来看看请求的格式,包含了一个soapHeader和一个soapBody。
所以我新建了一个LoginResponseEnvelope,来包含这两个对象,
@Root(name = "soap12:Envelope")
@NamespaceList({
@Namespace(prefix = "xsi", reference = "http://www.w3.org/2001/XMLSchema-instance"),
@Namespace(prefix = "xsd", reference = "http://www.w3.org/2001/XMLSchema"),
@Namespace(prefix = "soap12", reference = "http://www.w3.org/2003/05/soap-envelope")
})
@Default
public class LoginRequestEnvelope {
@Element(name = "soap12:Header", required = false)
LoginRequestHeader body;
@Element(name = "soap12:Body", required = false)
LoginResquestBody header;
public LoginRequestHeader getBody() {
return body;
}
public void setBody(LoginRequestHeader body) {
this.body = body;
}
public LoginResquestBody getHeader() {
return header;
}
public void setHeader(LoginResquestBody header) {
this.header = header;
}
@Override
public String toString() {
return "LoginRequestEnvelope{" +
"body=" + body +
", header=" + header +
'}';
}
}
大概是这样,然后又接着定义Body-Bean和Header-Bean
还有根据响应的xml格式定义响应的xml-bean
最后弄下来大概也就10个左右文件
以上当然是反话,看到这一大堆的文件,第一反应就是一个请求竟然要做这么多复杂的操作,这完全违背了我们当初想要使用Retrofit的初衷嘛,Retrofit本来是为了简化网络操作的,结果弄出这么多类,完全已经把请求复杂化了好吗?第二反应大概就是,如果这些都能被复用就好了,通过泛型之类的?
反正博主尝试了非常多的方法,发现几乎都没有办法去复用这些,除了header以外,body总是要不停变化的,这意味着我们每次请求都要定义一堆body-bean。这太麻烦了。基本我在这个项目中实现了复用,换一种请求格式,又只能重头再来,两个字,心酸。
那到底有没有办法呢,其实还是有的,首先我们观察一下请求时的格式,其实仅仅只有参数不一样嘛,如果xml可以不使用框架去生成,而是我们自己去生成就好了,Retrofit为我们提供了自定义ConvertFactory的机会。我们可以自定义一个ConvertFactory。之前本来是想请求用String返回用框架解析对象的。但后来发现也存在很多问题,同样也是需要编写许多对应的bean文件,最后改成请求用String,响应也用String来接收,并自己实现xml的解析,当然也是用simple-xml,只是提前对响应结果做一些处理而已。
自定义ConvertFactory代码如下:
public class ToStringConverterFactory extends Converter.Factory {
private static final MediaType MEDIA_TYPE = MediaType.parse("text/plain");
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
if (String.class.equals(type)) {
return new Converter<String, RequestBody>() {
@Override public RequestBody convert(String value) throws IOException {
return RequestBody.create(MEDIA_TYPE, value);
}
};
}
return null;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (String.class.equals(type)) {
return new Converter<ResponseBody, String>() {
@Override public String convert(ResponseBody value) throws IOException {
return value.string();
}
};
}
return null;
}
}
接下来我们可以自己定义自己的工具类用于拼接请求参数,这个就不多说了,根据各自的情况拼接就好。
api文件只需修改一下参数类型;
@Headers({
"Content-Type: application/soap+xml; charset=utf-8 ",
"Accept-Charset: utf-8"
})
@POST("UserService.asmx")
Observable<String> UserLogin(@Body String body);
我实现了自己的拼接工具类之后,最后调用的方式为:
public static BaseEnvelope getInterview() {
BaseEnvelope envelope = new BaseEnvelope("GetInterviewingCollection");
String[] propertyName = {"myUserId", "pageIndex"};
String[] propertyValue = {"****", "1"};
envelope.setParamName(propertyName);
envelope.setParamValue(propertyValue);
return envelope;
}
基本上配置一下参数名称和参数值就行了,当然你也可以有更好的方式去实现这个参数的拼接。最后结合Rxjava使用,是这样的。
IPositionApi api = retrofit.create(IPositionApi.class);
BaseEnvelope envelope = PositionService.getInterview();
api.getPositionList(envelope.getEnvelope())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new ServerSubscriber<String>(iMain) {
@Override
public void onCompleted() {
iMain.showProgress(false);
}
@Override
public void onNext(String s) {
String result = XmlParseUtil.roundStart(s);
String ss = (String) XmlParseUtil.getXMLObject(result, String.class);
iMain.showData(ss);
}
});
我自己写了一个XmlParseUtil的工具类,用于解析服务器返回的数据,当然,这个工具类并不是通用的,需要根据响应的格式做对应修改,但已经比之前简单太多了,复用性也高了很多,基本上只需要设置参数名参数值,返回时解析只需要传递一个类型的值就行了。
到这的话基本实现了WebService使用Retrofit+RxJava的内容了。
而且实现了复用。
接下来我说一下我在使用过程中的一些总结吧。由于OkHttp是属于比较成熟的网络框架,对网络请求的连接时间,响应时间,解析时间都能够观测,只需要简单谢一个拦截器即可,配置缓存也非常方便。但也存在一些问题,比如访问服务器报错的时候,这里是指发生了异常,在http中我不知道是否存在,但在WebService中如果服务器发生异常,在客户端这边是可以接收到错误信息,知道服务器那段代码出错,这样可以方便服务器那边修改。但使用了Retrofit之后,错误信息直接被拦截,最后只会抛出服务器异常,并不会有详细的服务器错误信息。不过如果在这方面没有什么需求,还是可以考虑使用。
本文在最开始也提到Retrofit其实也只是一个网络框架,如果你自己的项目中已经有一套自己熟悉的,并且效率较高的网络框架,其实我们还是可以使用自己的网络框架结合RxJava来使用的。
下一篇博客再更这方面内容吧。
最后对本文内容有任何疑问欢迎加群讨论:283272067