Retrofit2+Okhttp3+Rxjava通过SOAP协议

作者: Mersens | 来源:发表于2016-10-20 16:25 被阅读6721次

    前言

    刚入职新公司,负责其Android App的开发,他们的接口访问是通过SOAP协议实现的(基于xml数据格式的数据交换规范),由于之前也没有接触过这种东西,对此也是一脸懵逼,好在公司之前通过AnsysTask封装了SOAP的工具类,那就先做个一不明真相的吃瓜群众,直接拿来使用吧。

    先用着再说

    作为一个有上进心的程序员,你会甘心停止不前么?管你会不会,反正我是不会哈哈哈.....在使用的过程中发现AnsysTask封装的SOAP还是存在一定的 缺陷(具体是什么就不阐述了QAQ),由于项目的功能还在扩充,需求也在不断的改,我就想对 这种请求进行优化,可是那个封装代码写得那叫一个乱,关键还没注释(哎呀。。。脑袋瓜子疼,你知道这是有多么痛苦!),受尽折磨,果断放弃不干。前段时间公司要重新开发一个App,我打算放弃之前的架构,重新开始,包括网络请求框架,可是选择什么比较好呢?打开技术论坛你就会发现什么Retrofit啊,okhttp,Rxjava,MVP的架构模式之间的各种整合的Demo,发展可谓是风生水起,这么好的东西我也不会错过嘿嘿,当然每个工具都有它流行的意义,经过一段时间对它们的了解,最终决定使用etrofit2+Okhttp3+Rxjava封装SOAP来请求WebService,当然这其中也有过坎坷,毕竟网上对于这种问题的提问和回答少之又少。

    辗转很久,最终谷歌给我了答案,所以我决定把我的经验进行分享,希望对有需求的小伙伴有所帮助。接下来我会对具体实现进行详细分析。

    分析

    什么是SOAP?

    它是基于xml规范的数据交换协议,WebServices就是基于 XML 和 HTTP 的,简单点讲使用soap对WebServices进行访问请求就是普通的HTTP,只不过参数类型是以XML的格式进行传递和交流,明白了这点,我们就可以对我们的请求参数进行封装,封装成为WebServices能够识别的数据格式进行普通的Http请求就可以,管它后台服务器是java写的还是.NET写的,这时就与我们无关了,但是每个公司定义的数据类型各不相同,在拼接封装时候需要对数据进行分析和调试。

    首先我们需要使用HTTP请求的调试工具,这类工具很多,我这里使用REST Client,它是一个谷歌浏览器的插件,安装到谷歌浏览器里很方便使用,在调试之前还是先看一下请求和返回的数据格式规范(这是我们公司的一个接口数据规范)。

    这个是数据请求的格式

    每次请求服务器都会对你的请求进行验证,里面有“UserName”和“PassWord”(可能是为了安全性考虑需要验证吧),然后就是请求参数,该参数是一个String类型的字符串,如果是多个参数的话需要拼接成一个字符串进行处理。该字符串也是有严格的规范,一不小心就会掉入坑中,别问我是怎么知道的O.o(曾经爬了一天才脱坑)。

    这个是返回数据的格式

    真正的返回有效数据就是里面的String。


    知道了数据的请求和返回的数据格式,接着我们就可以进行调试了

    http模拟请求

    参数类型的拼接需要遵循这样的规范,这里需要对“<”和“>”进行 转义字符处理,否则的话就直接当作xml的节点了,这样的话是不符合服务器的参数规法就会返回错误数据,可以测试一下,效果如下:

    参数没有进行处理

    这个时候就会返回失败:

    参数错误,返回失败 请求参数规范,需要转义字符 请求成功返回的json数据

    由以上的调试我们可以发现,在进行soap请求只是参数和头部信息不同而已,事实就是这样,我们把它当作普通http请求,在参数上动点手脚就可以了。从数据的请求格式可以看出每一个请求都不许要添加Content-Type: text/xml; charset=utf-8,这是用来说明我们上传的参数类型是xml格式的,SOAPAction: "http://tempuri.org/ADInquiry"这里调用的每个接口都不一样,其实不一样的就是“ADInquiry”这个参数会变化。

    返回的数据类型也是xml形式的,我们可以把返回数据看作字符串来进行处理。对数据进行适当截取,里面的json才是我们真正想要的。

    分析到这里已经差不多了,废话不多说,直接撸代码!


    具体实现

    开始之前先放出锁需要的依赖:

    Rxjava依赖:

    compile'io.reactivex:rxandroid:1.2.1'

    compile'io.reactivex:rxjava:1.1.6'

    Rxjava GitHub地址:传送门

    Rxjava详细介绍:传送门


    OkHttp依赖:

    compile'com.squareup.okhttp3:okhttp:3.4.1'

    compile'com.squareup.okhttp3:logging-interceptor:3.4.1'

    compile'com.squareup.okhttp3:okhttp-urlconnection:3.4.1'

    OkHttp地址:传送门


    Retrofit依赖:

    compile'com.squareup.retrofit2:retrofit:2.0.1'

    compile'com.squareup.retrofit2:converter-scalars:2.1.0'

    compile'com.squareup.retrofit2:converter-gson:2.1.0'

    compile('com.squareup.retrofit2:converter-simplexml:2.1.0') {

    excludegroup:'xpp3',module:'xpp3'

    excludegroup:'stax',module:'stax-api'

    excludegroup:'stax',module:'stax'

    }

    Retrofit地址:传送门


    新建Interface请求接口:

    需要通过注解方法添加头部信息

    需要对请求的参数进行拼接处理,这里我们选择字符串的拼接,可能有的人会问,simplexml支持实体类的拼接啊,通过注解就可以轻松实现xml数据的转化,为什么不用而选择字符串呢?答案就是封装!封装!封装!(重要话说三遍),因为我们的请求数据格式字段比较多,每个接口请求的话大约需要建立五个实体并且进行赋值,记得,是每个接口,这是多么繁重的工作,果断抛弃,但是呢,我还是打算在文章最后对这种使用方法进行简单说明一下,谁让Retrofit如此强大呢!

    对请求参数进行拼接处理

    封装RequestManager网络请求工具类,采用单例模式:

    public final static intCONNECT_TIMEOUT=10;

    public final static intREAD_TIMEOUT=20;

    public final static intWRITE_TIMEOUT=10;

    public RetrofitmRetrofit;

    protected Map  params;

    private staticRequestManager manager;//管理者实例

    publicOkHttpClient mClient;//OkHttpClient实例

    在构造方法中进行数据初始化:

    1:对OkHttp进行缓存,请求超时,重连机制进行设置;

    2:对Retrofit进行设置并与OkHttp进行关联;

    private RequestManager() {

    Strategy strategy =newAnnotationStrategy();

    Serializer serializer =newPersister(strategy);

    HttpLoggingInterceptor interceptor =newHttpLoggingInterceptor();

    interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

    File httpCacheDirectory =newFile(App.getInstance().getCacheDir(),"retrofit");

    intcacheSize =32*1024*1024;

    Cache cache =newCache(httpCacheDirectory, cacheSize);

    OkHttpClient.Builder builder =newOkHttpClient.Builder();

    builder.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS);

    builder.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS);

    builder.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS);

    builder.retryOnConnectionFailure(true);

    builder.addInterceptor(interceptor);

    builder.addNetworkInterceptor(getNetWorkInterceptor());

    builder.addInterceptor(getInterceptor());

    builder.cache(cache);

    mClient= builder.build();

    mRetrofit=newRetrofit.Builder()

    .baseUrl(Constans.WEBSERVICE_URL)

    .addConverterFactory(ScalarsConverterFactory.create())

    .addConverterFactory(GsonConverterFactory.create())

    .addConverterFactory(SimpleXmlConverterFactory.create(serializer))

    .client(mClient)

    .build();

    }

    新建execute()方法,主要请求和处理网络数据,服务器返回的 数据是xml形式,我们需要对数据进行解析,把结果看作String类型的字符串,然后进行分割就可以得到json数据:

    execute

    铛铛.....Rxjava上场了,doRequest方法内部就是通过Rxjava,把异步的结果返回到主线程,轻松实现主线程与非主线程之间的相互切换(是不是突然也感觉到Rxjava真是太好用了),否则的话,你第一时间想到的可能就是Handler。

    doRequest

    对OkHttp拦截器进行设置,有网络时候请求数据,没有网络从缓存读取 数据,比较遗憾的是还没有找到更好的方法去设置SOAP的缓存(貌似SOAP不支持缓存),普通的请求是完全支持的:

    拦截器设置

    定义一个RequestCallBack接口,用于把请求结果进行回调处理:

    public interfaceRequestCallBack {

    voidonSueecss(String msg);

    voidonError(String msg);

    voidonStart();

    voidonFinish();

    }

    调用方式,由于项目是基于MVP模式来写的,接口层次比较多,这里就简单的说明一下调用方式:

    Map map=newHashMap<>();

    map.put("DoctorMobile",name);

    map.put("Password",psd);

    map.put("PhoneType","0");

    map.put("ClientID","");

    map.put("DeviceToken","");

    String result= Node.getResult("MSDoctorLogin",map);

    finalServiceStore service=manager.create(ServiceStore.class);

    Call call=service.login(result);

    Map集合里面存放的就是参数的值,然后通过Node的getResult()方法进行数据拼接,返回拼接后带参数的字符串。对于json字符串的解析有很多方法和工具类,Retrofit也可以对返回json进行转化,这里我就使用最原始的解析方式。

    execute execute

    以上就是Retrofit+Soap对webservice进行访问请求具体实现,该实现是通过String字符串的拼接,传输过程中转化为xml数据格式来实现的,接下来顺便提一下另一种方式,通过SimpleXml注解实体类的方式实现转化。对SimpleXml不太了解的话可以在网上百度一下,使用很简单,

    奉上SimpleXml的地址:传送门

    废话不多说,我们用代码说话!

    通过对请求数据格式的分析可以清晰的看到,里面包含五个节点,分别为:soap:Envelope,soap:Header,Identify,soap:Body,ADInquiry,好的,就是这样,毫无疑问需要建立五个实体类。

    Envelope实体类:

    根节点Envelope

    Header实体类:

    Header实体类

    Identify实体类:

    Identify实体类

    Body实体类:

    Body实体类

    ADInquiry实体类:

    ADInquiry实体类

    请求接口:

    请求接口

    注意这个时候getInfo()方法里面的参数就是一个实体。

    对实体进行初始化和赋值:

    初始化和赋值

    进行请求:

    进行请求

    OK,大功告成,可以进行数据请求了,请求的结果我就通过log输出,亲自测试成功访问。

    相关文章

      网友评论

      • c84a6998a6c6:总是出现500的问题,是拼参数的时候哪里出错了吗??加了logging 说是System.Web.Services.Protocols.SoapException: SOAP 版本可能不匹配: 出现意外的 Envelope 命名空间
        c84a6998a6c6:@Mersens 多谢指点
        Mersens:你看一下服务器返回的xml头部信息和拼接的字符串对比一下
      • 0d6c212d0f7f:服务器报了400的错误,这里需要对“<”和“>”进行 转义字符处理。这个是什么意思
        Mersens:400错误应该是没有访问到服务器,或者是无效的请求,“”对“<”和“>”进行 转义字符处理“”进行转义就是&lt和>表示
      • 0d6c212d0f7f:模拟请求的工具是什么楼主?
        Mersens:DHC - REST,很多这样类似的工具的
      • IsCrazyCat:哈喽,楼主我试验了您说的这两种方式,使用simpleXml是成功的,但是使用拼接字符串的方式一致返回500错误,问下知道是什么原因么?
        Mersens:500错误你看一下请求字符串的拼接,参数格式是否正确
      • 0156770c53ab:你好,我刚刚用了你这个拼接的方法,我用的是retrofit+rxjava的观察者模式请求的,报http500异常,请问能跟你讨论下吗?
        0156770c53ab:@彭八面 我就添加了一行, envelop.dotNet=true
        0156770c53ab:@IsCrazyCat 解决了
        IsCrazyCat:哈喽 问下您这个问题解决了吗? 我也是报的这个错误。
      • 敬楠:你好,如何处理节点自动排序的问题?比如<impl:userName>135144</impl:userName>
        <!--Optional:-->
        <impl:passWord>12345</impl:passWord>
        </impl:loginNew>就访问成功。
        <impl:passWord>2</impl:passWord>
        <impl:userName>1</impl:userName>就访问失败。如何处理节点自动排序的问题,求教
      • 500英里的思念:最后一步的manager.creat(ServiceStore.class)是个什么操作?实在是看不懂。:cold_sweat: 求解释。
        Mersens:你想了解一下retrofit的框架QAQ
      • 422082ef01b0:已经拜读了作者的文章,很不错,并已成功摒弃了之前的ksoap包。。。有个问题:String类型的数组参数要怎么传递呢?
      • gyymz1993:wsdl结尾的
        Mersens:与什么样的结尾没关系的:blush:
      • gyymz1993:你好,我想请教下你这个能访问服务的地址
        URL wsUrl = new URL("http://wstest.51book.com:55779/ltips/services/XXXXXXXXService1.0?wsdl&quot;);
        这样的接口吗
      • 6c2f07d1f89f:我有个疑问,每个请求接口都在headers注解里添加webservice信息吗,这样也有好多重复代码,有没有封装的办法
        Mersens:@6c2f07d1f89f 暂时我还没找到,之前写了个拦截器,然后在里面配置,也不行~~我在研究一下:blush:
      • 梨落520:我们公司接口访问是通过SOAP协议实现,以前都是自己随便搞的网络请求,妈的感觉乱的一匹,先mark,闲了好好研究研究,多谢楼主分享!
      • kibou: sbf.append(Node.toStart("Request"));为啥要添加,市公司的需求么
        Mersens:@kibou 呃……这个没遇到过,难道不应该分页么……
        kibou:@Mersens 返回的数据很多,几千条数据,simple-xml解析时间很长,遇到这个问题么
        Mersens:@kibou 嗯嗯,公司请求的数据格式必须要添加
      • c82c1610c288:你好,我在Android Studio添加compile('com.squareup.retrofit2:converter-simplexml:2.1.0') {
        excludegroup:'xpp3',module:'xpp3'
        excludegroup:'stax',module:'stax-api'
        excludegroup:'stax',module:'stax'
        } 这个的时候出错了,提示

        Error:(40, 0) Cause: startup failed:
        build file 'E:\Studio Space\SoapDemo\app\build.gradle': 40: unexpected token: xpp3 @ line 40, column 23.
        excludegroup: 'xpp3',
        ^
        请问这个怎么解决?
        FrankDaddy:楼主写的很好呀,我有个问题想咨询一下,平常我们请求http的话,过来的数据我们可以通过事先定义泛型,传过去我们需要的数据类型,然后retrofit还是rxjava就自动帮我们做json转换了,那现在回传回来的是string,还要根据不同的字段去截取出我们需要的数据,像我这边是<方法请求名Result>string</方法请求名Result>,那等于每次都是要获取到数据后我们手动做了一次截取的动作,如果同步调用多次不同的请求,过来的都是String类型的,没办法判断类型了,还是说等到手工转换完类型后我们再去取
        FrankDaddy:@c82c1610c288 excludegroup 少了个空格 exclude group
        Mersens:@c82c1610c288 ??..。这个怎么会构建失败~
      • DoneWillianm:大神,github上有地址吗
        Mersens:@MonkeyIoT 没有~~何不自己动手尝试一下 :smile:
      • Zsago:请教楼主,Request的方法没有参数的情况怎么弄呢?
      • dfe147f4a102:```java
        java code here
        ```
        这样才能显示Java code
        Mersens:@_v君 恩恩,好哒
        dfe147f4a102:@_v君 markdown 模式
      • 暖宝宝1l:好高深的技术
      • 辉辉菜:公司用的也是web service,用了soap的一个jar包,每个请求用AsyncTask,很繁琐还极有可能内存泄漏,正好借鉴下你的写法。 :+1:
      • Passon_Fang:刚好公司也是webservice mark
        Passon_Fang:解决了: Simple xml 解析对象的时候生成的 xml string 的结点顺序好像和类的属性名的字母在字母表排列顺序有关。

        Passon_Fang:@Mersens 遇到个问题,不知道你遇到过没: 最后生成的请求 body再header之上, 请求失败
        Mersens:@DoubleFang 希望对你有所帮助 :smile:
      • 4bc0191124bc:感谢,重要找到了这方面的技术剖析。谢谢~
        Mersens:@4bc0191124bc 感谢支持 :smile:

      本文标题:Retrofit2+Okhttp3+Rxjava通过SOAP协议

      本文链接:https://www.haomeiwen.com/subject/ocututtx.html