结合 Retrofit 使用 post 请求访问 WebService

1,701 阅读9分钟

背景

没啥背景,实在是受够了ksoap2这个jar包,而公司服务端是基于c#语言的.net开发,不懂他们的技术,而他们好像只能通过WebService与我们Android端进行数据交互(如果有前辈知道别的技术,望指点!谢谢~).
Retrofit作为当前最火的网络请求框架.如果不去学,永远不会用.因此,我想把这个框架引入到公司项目里边来,把ksoap2替换掉,用Retrofit来访问WebService
既然有了想法,就要去做.网上关于Retrofit的讲解一大堆,奈何关于使用Retrofit去访问WebService的文章少之又少,并且各位前辈的经验都是基于自己公司的业务情况总结出来的,和我现在的情况多少有些出入,因此,我把各位前辈的经验综合起来,写了这篇文章,一来,总结下经验;二来,希望以后有朋友再遇到这种问题的时候,能少走些弯路.
首先将前辈的链接奉上:
Retrofit2+Okhttp3+Rxjava通过SOAP协议请求WebService
在WebService中使用Retrofit+RxJava
转载----使用 Retrofit 操作 SOAP Web Service
也正是有了各位前辈总结的经验,才有了我今天这篇文章,向各位前辈致敬!
第一次写文章,如有不妥之处,希望各位前辈指出!

工具准备

  • FireFox(火狐浏览器)
  • RESTClient(火狐浏览器调试插件)

这里,我使用的是火狐浏览器+RESTClient去调试http请求.
因为每个公司定义的格式可能会不一样.我们在封装以及分析数据的时候需要与之对应.所以我们去调试分析http请求.弄清每次请求及响应的格式.
关于soap,WebService以及http,各位前辈已经分析的很透彻了,这里我就不多说了,如果想了解的话,可以去看下前辈总结的文章.下面,咱们正式开始.

开工

RESTClient界面

在火狐浏览器中安装好RESTClient插件后,将其打开,界面应该会和图1类似,是空的,没有任何数据.为了方便分析,我又截取了图2,明显是一次成功请求后的界面,我先用图2分析下整个界面,然后再告诉你,这些数据都是怎么填上去的.


图1

在图2中,我把一次请求分为了两个部分,分别用绿线和蓝线框了起来.其中,绿线内是本次http请求发送的数据,也就是我们作为Android端需要封装的数据,蓝线内,就是本次请求服务端返回给我们的数据,也就是我们需要解析的数据.
我们先来分析下我们需要发送的数据,如图所示,我用红线标出了4个位置,它们分别表示什么意思呢?

  1. Method: 表示这次请求的请求方式,一般常用的有get和post,这里当然选post(因为WebService就是post请求的一种)
  2. URL: 表示这次请求的地址
  3. Headers: 请求头
  4. Body: 请求体

    图2

    调试http请求

    介绍完了RESTClient的界面,下面就正式开始http调试.
    所谓调试,无非就是模拟一次http请求,我们把需要发送给服务端的数据填到Request(图2绿线内)中,点击send按钮,然后Response(图2蓝线内)中显示服务端返回给我们的数据.我们就是分析这堆数据而已.那么问题来了,Request中的这些数据,是从哪来的?
    我另外打开了一个浏览器页面,在地址栏中输入想要调试的地址,如图3所示,为了方便,我让服务端同事把服务器部署到我的电脑上了,所以看到的地址ip是192.168.191.1,
    刚刚在地址栏输入的就是这次要调试的地址,因此,我把他填入到了RESTClient的URL中.
    既然url确定了,那请求头(Headers)和请求体(Body)又该填什么呢?别急,接着往下看.

    图3

    这次我要调试的就是图3中红线内的接口.名为AssetMaterialInfo,点击这个接口,打开的界面如图4所示.图中有SOAP1.2请求和响应示例,红线标注的是占位符,在模拟数据的时候需要将其替换为真实数据.
    而我们所需要的请求头(Headers)和请求体(Body)就藏在请求示例中.为了方便分析,我单独将请求示例截取了图片,放在下边,也就是图5.

    图4

    图5中,红线内这两行内容,就是请求头,蓝线内的就是请求体.请求体很简单,我们只需要将蓝线中内容复制到RESTClient界面的Body中,然后把占位符替换掉就可以了(如图2所示),请求头怎么弄呢?

    图5

    关于在RESTClient中添加请求头,我举一个例子(图6),大家就都明白了.
    在RESTClient界面中,点击顶部Headers,再点击CustomHeader,会打开图7这个界面.

    图6

    在图7所示界面,Name栏中填入Content-Type,Value栏中填入text/xml; charset=utf-8,然后点击Okay,我们就将一个请求头添加到本次请求中了.
    同理,将第二个请求头也添加进来.我就不再演示了.

    图7

    这样,我们就将本次请求需要携带的数据都添加进来了.点击SEND按钮,就完成了本次请求.

    开始写Demo

    拿到了http请求的数据,我们就可以根据数据去写我们的例子程序了,在我们Android端通过Retrofit使用post请求去访问刚刚我们调试的WebService接口.
    首先,在看下边代码之前,你要保证自己已经基本了解了Retrofit框架.关于Retrofit不会介绍太多,因为它不是本篇文章的重点.

导包
compile'com.squareup.retrofit2:retrofit:2.0.1'
compile('com.squareup.retrofit2:converter-simplexml:2.1.0') {
        exclude group:'xpp3',module:'xpp3'
        exclude group:'stax',module:'stax-api'
        exclude group:'stax',module:'stax'
    }
请求体实例

对应图2中Request的Body部分,需要写三个类来作为请求体.分别对应请求体中的三个节点
根据由外到内的层级关系,它们分别是(类名 <---> 节点名):

  • RequestEnvelope <---> soap:Envelope
  • RequestBody <---> soap:Body
  • RequestModel <---> AssetMaterialInfo

下面,我详细介绍下这三个类.

@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://schemas.xmlsoap.org/soap/envelope/", prefix = "soap")
})
public class RequestEnvelope {
      @Element(name = "soap:Body", required = false)
      public RequestBody body;
}

RequestEnvelope类中,Root注解用来指定节点名称,NamespaceList用来指定多个命名空间,Element用来指定子节点名称.

@Root(name = "soap:Body", strict = false)
public class RequestBody {
    @Element(name = "AssetMaterialInfo", required = false)
    public RequestModel AssetMaterialInfo;
}

同理,RequestBody类中,Root注解用来指定节点名称,Element注解用来指定子节点名称,因为当前节点没有命名空间,因此不需要NamespaceList注解

@Root(name = "AssetMaterialInfo", strict = false)
@Namespace(reference = "http://tempuri.org/")
public class RequestModel {
    @Element(name = "date", required = false)
    public String date;
    @Element(name = "page", required = false)
    public int page;
}

RequestModel类中,Root注解用来指定节点名称,因为当前节点只有一个命名空间,因此使用Namespace注解而不是NamespaceList.Element注解指定子节点名称,因为当前节点有两个子节点,因此这个类有两个参数,且都被Element注解修饰.
到这里,关于请求体的实体类,就创建好了.下面我们准备响应体的实体类.

响应体实例

跟请求体类似,我们也是需要根据服务器响应的xml文件格式来创建实体类.
通过分析xml格式,我们也需要创建三个实体类来分别对应三个节点.它们的对应关系如下(类名 <---> 节点名):

  • AssetResponseEnvelope <---> soap:Envelope
  • AssetResponseBody <---> Body
  • AssetResponseModel <---> AssetMaterialInfoResponse

为了与系统类名区分开,我为这三个类名添加了Asset前缀.

@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://schemas.xmlsoap.org/soap/envelope/", prefix = "soap")
})
public class AssetResponseEnvelope {
    @Element(name = "Body", required = false)
    public AssetResponseBody responseBody;
}
@Root(name = "Body", strict = false)
public class AssetResponseBody {
    @Element(name = "AssetMaterialInfoResponse", required = false)
    public AssetResponseModel responseModel;
}
@Root(name = "AssetMaterialInfoResponse")
public class AssetResponseModel {
    @Attribute(name = "xmlns", empty = "http://tempuri.org/", required = false)
    public String nameSpace;
    @Element(name = "AssetMaterialInfoResult")
    public String result;
}

因为与请求体类似,这里我也不用对这三个类做过多介绍.不过,值得一提的是,在AssetResponseModel类中,我没有再用Namespace注解去指定命名空间,而是添加了一个成员变量,用Attribute注解将其指定.需要注意的是,再用Attribute注解指定的时候,name,empty,required三个属性,缺一不可.
下面奉上SimpleXml的地址,上面有关于xml与实体类之间绑定的详细介绍:
SimpleXml

创建Interface

三个请求类和三个响应类创建好了,下一步就是创建请求接口Interface.(不明白为啥要这样做的,可以去复习下Retrofit,哦不对,是预习~)
直接上代码:

public interface ApiStore {
    @Headers({
            "Content-Type: text/xml; charset=utf-8",
            "SOAPAction: http://tempuri.org/AssetMaterialInfo"
    })
    @POST("GetService.asmx")
    Call<AssetResponseEnvelope> getAssetInfo(@Body RequestEnvelope requestEnvelope);
}

在请求接口中,我们定义了一个函数,将其请求方式制定为post请求,并且为其添加了请求头.这个函数接收一个RequestEnvelope参数.

使用Retrofit请求

所有初始化工作都做完之后,使用Retrofit去请求WebService这块还是蛮简单的.
不得不说,Retrofit确实很强大,整个请求流程下来,结构清晰明了,一点都不拖泥带水.如果再结合上RxJava,岂不是更爽?

    /**
     * 去服务端请求数据
     */
    private void request() {
        String url = "http://192.168.191.1:2000/";
        // 初始化Retrofit
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(SimpleXmlConverterFactory.create()) // 返回数据为xml,因此要加入xml解析
                .build();
        ApiStore apiStore = retrofit.create(ApiStore.class);
        // 初始化请求体
        RequestModel requestModel = new RequestModel("2012-01-01", 0);
        RequestBody requestBody = new RequestBody(requestModel);
        RequestEnvelope requestEnvelope = new RequestEnvelope(requestBody);
        // 开始请求
        Call<AssetResponseEnvelope> call = apiStore.getAssetInfo(requestEnvelope);
        call.enqueue(new Callback<AssetResponseEnvelope>() {
            @Override
            public void onResponse(Call<AssetResponseEnvelope> call, Response<AssetResponseEnvelope> response) {
                // 处理响应体
                AssetResponseEnvelope responseEnvelope = response.body();
                if (responseEnvelope == null) {
                    Log.d(TAG, "onResponse: responseEnvelope == null");
                    return;
                }
                AssetResponseBody responseBody = responseEnvelope.responseBody;
                if (responseBody == null) {
                    Log.d(TAG, "onResponse: responseBody == null");
                    return;
                }
                AssetResponseModel responseModel = responseBody.responseModel;
                if (responseModel == null) {
                    Log.d(TAG, "onResponse: responseModel == null");
                    return;
                }
                String result = responseModel.result;
                Log.d(TAG, "onResponse: result : " + result);
//                showResult(result);
            }

            @Override
            public void onFailure(Call<AssetResponseEnvelope> call, Throwable t) {

            }
        });
    }

总结

到此为止,一个基于Retrofit使用post请求访问WebService的小Demo就算写完了.我已经将代码提交到了GitHub,感兴趣的同学可以去看一下,很简单的小程序.
项目地址
有不明白的同学,欢迎向我提出问题,我们共同学习.
第一次写文章,还望各位前辈多多批评指正,不胜感激!