Retrofit2+Okhttp3+Rxjava通过SOAP协议请求WebService

qsbn8763 8年前
   <h3>前言</h3>    <p>刚入职新公司,负责其Android App的开发,他们的接口访问是通过SOAP协议实现的(基于xml数据格式的数据交换规范),由于之前也没有接触过这种东西,对此也是一脸懵逼,好在公司之前通过AnsysTask封装了SOAP的工具类,那就先做个一不明真相的吃瓜群众,直接拿来使用吧。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/8e0595a0b5644fe56429512226fea4ef.jpg"></p>    <p style="text-align:center">先用着再说</p>    <p>作为一个有上进心的程序员,你会甘心停止不前么?管你会不会,反正我是不会哈哈哈.....在使用的过程中发现AnsysTask封装的SOAP还是存在一定的 缺陷(具体是什么就不阐述了QAQ),由于项目的功能还在扩充,需求也在不断的改,我就想对 这种请求进行优化,可是那个封装代码写得那叫一个乱,关键还没注释(哎呀。。。脑袋瓜子疼,你知道这是有多么痛苦!),受尽折磨,果断放弃不干。前段时间公司要重新开发一个App,我打算放弃之前的架构,重新开始,包括网络请求框架,可是选择什么比较好呢?打开技术论坛你就会发现什么Retrofit啊,okhttp,Rxjava,MVP的架构模式之间的各种整合的Demo,发展可谓是风生水起,这么好的东西我也不会错过嘿嘿,当然每个工具都有它流行的意义,经过一段时间对它们的了解,最终决定使用etrofit2+Okhttp3+Rxjava封装SOAP来请求WebService,当然这其中也有过坎坷,毕竟网上对于这种问题的提问和回答少之又少。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/15ac8704fa3a00644892dd3b684bfcca.png"></p>    <p>辗转很久,最终谷歌给我了答案,所以我决定把我的经验进行分享,希望对有需求的小伙伴有所帮助。接下来我会对具体实现进行详细分析。</p>    <h3><strong>分析</strong></h3>    <p>什么是SOAP?</p>    <p>它是基于xml规范的数据交换协议,WebServices就是基于 XML 和 HTTP 的,简单点讲使用soap对WebServices进行访问请求就是普通的HTTP,只不过参数类型是以XML的格式进行传递和交流,明白了这点,我们就可以对我们的请求参数进行封装,封装成为WebServices能够识别的数据格式进行普通的Http请求就可以,管它后台服务器是java写的还是.NET写的,这时就与我们无关了,但是每个公司定义的数据类型各不相同,在拼接封装时候需要对数据进行分析和调试。</p>    <p>首先我们需要使用HTTP请求的调试工具,这类工具很多,我这里使用REST Client,它是一个谷歌浏览器的插件,安装到谷歌浏览器里很方便使用,在调试之前还是先看一下请求和返回的数据格式规范(这是我们公司的一个接口数据规范)。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/b714adb0c91df16516b04aeb3bcfb064.png"></p>    <p style="text-align:center">这个是数据请求的格式</p>    <p>每次请求服务器都会对你的请求进行验证,里面有“UserName”和“PassWord”(可能是为了安全性考虑需要验证吧),然后就是请求参数,该参数是一个String类型的字符串,如果是多个参数的话需要拼接成一个字符串进行处理。该字符串也是有严格的规范,一不小心就会掉入坑中,别问我是怎么知道的O.o(曾经爬了一天才脱坑)。</p>    <p><img src="https://simg.open-open.com/show/cce032812bdf905f7a5d1371cce2b50f.png"></p>    <p style="text-align:center">这个是返回数据的格式</p>    <p>真正的返回有效数据就是里面的String。</p>    <p>知道了数据的请求和返回的数据格式,接着我们就可以进行调试了</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/a180c2df4ceb50f810e58fed448152bb.png"></p>    <p style="text-align:center">http模拟请求</p>    <p>参数类型的拼接需要遵循这样的规范,这里需要对“<”和“>”进行 转义字符处理,否则的话就直接当作xml的节点了,这样的话是不符合服务器的参数规法就会返回错误数据,可以测试一下,效果如下:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/313efe6c54134419bd5a281014f4cdc2.png"></p>    <p style="text-align:center">参数没有进行处理</p>    <p>这个时候就会返回失败:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/eb2519e0670bc52b1829d8397601828c.png"></p>    <p style="text-align:center">参数错误,返回失败</p>    <p><img src="https://simg.open-open.com/show/d1a0bb198ec25fca56d33a1b267919d6.png"></p>    <p>请求参数规范,需要转义字符</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/33140d92e78262b2451f8ca712d6e8e9.png"></p>    <p style="text-align:center">请求成功返回的json数据</p>    <p>由以上的调试我们可以发现,在进行soap请求只是参数和头部信息不同而已,事实就是这样,我们把它当作普通http请求,在参数上动点手脚就可以了。从数据的请求格式可以看出每一个请求都不许要添加Content-Type: text/xml; charset=utf-8,这是用来说明我们上传的参数类型是xml格式的,SOAPAction: "http://tempuri.org/ADInquiry"这里调用的每个接口都不一样,其实不一样的就是“ADInquiry”这个参数会变化。</p>    <p>返回的数据类型也是xml形式的,我们可以把返回数据看作字符串来进行处理。对数据进行适当截取,里面的json才是我们真正想要的。</p>    <p>分析到这里已经差不多了,废话不多说,直接撸代码!</p>    <h3><strong>具体实现</strong></h3>    <p>开始之前先放出锁需要的依赖:</p>    <p>Rxjava依赖:</p>    <pre>  <code class="language-java">compile'io.reactivex:rxandroid:1.2.1'    compile'io.reactivex:rxjava:1.1.6'  </code></pre>    <p>OkHttp依赖:</p>    <pre>  <code class="language-java">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'</code></pre>    <p>Retrofit依赖:</p>    <pre>  <code class="language-java">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'    }</code></pre>    <p>新建Interface请求接口:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/e59cf61c8cff57a2bb9be2797f12a472.png"></p>    <p style="text-align:center">需要通过注解方法添加头部信息</p>    <p>需要对请求的参数进行拼接处理,这里我们选择字符串的拼接,可能有的人会问,simplexml支持实体类的拼接啊,通过注解就可以轻松实现xml数据的转化,为什么不用而选择字符串呢?答案就是封装!封装!封装!(重要话说三遍),因为我们的请求数据格式字段比较多,每个接口请求的话大约需要建立五个实体并且进行赋值,记得,是每个接口,这是多么繁重的工作,果断抛弃,但是呢,我还是打算在文章最后对这种使用方法进行简单说明一下,谁让Retrofit如此强大呢!</p>    <p><img src="https://simg.open-open.com/show/0bc04d8c4e136c3bdcd393361a6a79b1.png"></p>    <p style="text-align:center">对请求参数进行拼接处理</p>    <p>封装RequestManager网络请求工具类,采用单例模式:</p>    <pre>  <code class="language-java">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实例</code></pre>    <p>在构造方法中进行数据初始化:</p>    <p>1:对OkHttp进行缓存,请求超时,重连机制进行设置;</p>    <p>2:对Retrofit进行设置并与OkHttp进行关联;</p>    <pre>  <code class="language-java">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();    }</code></pre>    <p>新建execute()方法,主要请求和处理网络数据,服务器返回的 数据是xml形式,我们需要对数据进行解析,把结果看作String类型的字符串,然后进行分割就可以得到json数据:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/4a025822bfbea657a265051155ec3ac0.png"></p>    <p style="text-align:center">execute</p>    <p>铛铛.....Rxjava上场了,doRequest方法内部就是通过Rxjava,把异步的结果返回到主线程,轻松实现主线程与非主线程之间的相互切换(是不是突然也感觉到Rxjava真是太好用了),否则的话,你第一时间想到的可能就是Handler。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/cbca1117ae0319df964831282096e6e9.png"></p>    <p style="text-align:center">doRequest</p>    <p>对OkHttp拦截器进行设置,有网络时候请求数据,没有网络从缓存读取 数据,比较遗憾的是还没有找到更好的方法去设置SOAP的缓存(貌似SOAP不支持缓存),普通的请求是完全支持的:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/b64e3cc4c554550e1ee51ccbbce276c4.png"></p>    <p style="text-align:center">拦截器设置</p>    <p>定义一个RequestCallBack接口,用于把请求结果进行回调处理:</p>    <pre>  <code class="language-java">public interfaceRequestCallBack {    voidonSueecss(String msg);    voidonError(String msg);    voidonStart();    voidonFinish();    }  </code></pre>    <p>调用方式,由于项目是基于MVP模式来写的,接口层次比较多,这里就简单的说明一下调用方式:</p>    <pre>  <code class="language-java">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);</code></pre>    <p>Map集合里面存放的就是参数的值,然后通过Node的getResult()方法进行数据拼接,返回拼接后带参数的字符串。对于json字符串的解析有很多方法和工具类,Retrofit也可以对返回json进行转化,这里我就使用最原始的解析方式。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/0ef80228666c2fbea67e5d10d34f6728.png"></p>    <p style="text-align:center">execute</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/4d11eafc81cc06df38f6e9604b127662.png"></p>    <p style="text-align:center">execute</p>    <p>以上就是Retrofit+Soap对webservice进行访问请求具体实现,该实现是通过String字符串的拼接,传输过程中转化为xml数据格式来实现的,接下来顺便提一下另一种方式,通过SimpleXml注解实体类的方式实现转化。对SimpleXml不太了解的话可以在网上百度一下,使用很简单,</p>    <p>废话不多说,我们用代码说话!</p>    <p>通过对请求数据格式的分析可以清晰的看到,里面包含五个节点,分别为:soap:Envelope,soap:Header,Identify,soap:Body,ADInquiry,好的,就是这样,毫无疑问需要建立五个实体类。</p>    <p>Envelope实体类:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/babebb8475aef540cb600e78dcc77523.png"></p>    <p style="text-align:center">根节点Envelope</p>    <p>Header实体类:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/1cb16625bfebaa9224e3646002d18cff.png"></p>    <p style="text-align:center">Header实体类</p>    <p>Identify实体类:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/db063e81d1960560f553220930a7f416.png"></p>    <p style="text-align:center">Identify实体类</p>    <p>Body实体类:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/0750ff5d0f7f760acedaaef02c4fed11.png"></p>    <p style="text-align:center">Body实体类</p>    <p>ADInquiry实体类:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/6c6129e6ac4518a203abfcf7f884c662.png"></p>    <p style="text-align:center">ADInquiry实体类</p>    <p>请求接口:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/a9b560e6744493efdbea529a9e9ae34c.png"></p>    <p style="text-align:center">请求接口</p>    <p>注意这个时候getInfo()方法里面的参数就是一个实体。</p>    <p>对实体进行初始化和赋值:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/40d288b33bb29b23723a2987b3a2dd62.png"></p>    <p style="text-align:center">初始化和赋值</p>    <p>进行请求:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/590b53dcc08f21bc56716d997e649ee1.png"></p>    <p style="text-align:center">进行请求</p>    <p>OK,大功告成,可以进行数据请求了,请求的结果我就通过log输出,亲自测试成功访问。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/b865c855a1e8</p>    <p> </p>