背景
沒啥背景,實在是受夠了ksoap2這個jar包,而公司服務(wù)端是基于c#語言的.net開發(fā),不懂他們的技術(shù),而他們好像只能通過WebService與我們Android端進行數(shù)據(jù)交互(如果有前輩知道別的技術(shù),望指點!謝謝~).
Retrofit作為當(dāng)前最火的網(wǎng)絡(luò)請求框架.如果不去學(xué),永遠不會用.因此,我想把這個框架引入到公司項目里邊來,把ksoap2替換掉,用Retrofit來訪問WebService
既然有了想法,就要去做.網(wǎng)上關(guān)于Retrofit的講解一大堆,奈何關(guān)于使用Retrofit去訪問WebService的文章少之又少,并且各位前輩的經(jīng)驗都是基于自己公司的業(yè)務(wù)情況總結(jié)出來的,和我現(xiàn)在的情況多少有些出入,因此,我把各位前輩的經(jīng)驗綜合起來,寫了這篇文章,一來,總結(jié)下經(jīng)驗;二來,希望以后有朋友再遇到這種問題的時候,能少走些彎路.
首先將前輩的鏈接奉上:
Retrofit2+Okhttp3+Rxjava通過SOAP協(xié)議請求WebService
在WebService中使用Retrofit+RxJava
轉(zhuǎn)載----使用 Retrofit 操作 SOAP Web Service
也正是有了各位前輩總結(jié)的經(jīng)驗,才有了我今天這篇文章,向各位前輩致敬!
第一次寫文章,如有不妥之處,希望各位前輩指出!
工具準(zhǔn)備
- FireFox(火狐瀏覽器)
- RESTClient(火狐瀏覽器調(diào)試插件)
這里,我使用的是火狐瀏覽器+RESTClient去調(diào)試http請求.
因為每個公司定義的格式可能會不一樣.我們在封裝以及分析數(shù)據(jù)的時候需要與之對應(yīng).所以我們?nèi)フ{(diào)試分析http請求.弄清每次請求及響應(yīng)的格式.
關(guān)于soap,WebService以及http,各位前輩已經(jīng)分析的很透徹了,這里我就不多說了,如果想了解的話,可以去看下前輩總結(jié)的文章.下面,咱們正式開始.
開工
RESTClient界面
在火狐瀏覽器中安裝好RESTClient插件后,將其打開,界面應(yīng)該會和圖1類似,是空的,沒有任何數(shù)據(jù).為了方便分析,我又截取了圖2,明顯是一次成功請求后的界面,我先用圖2分析下整個界面,然后再告訴你,這些數(shù)據(jù)都是怎么填上去的.
在圖2中,我把一次請求分為了兩個部分,分別用綠線和藍線框了起來.其中,綠線內(nèi)是本次http請求發(fā)送的數(shù)據(jù),也就是我們作為Android端需要封裝的數(shù)據(jù),藍線內(nèi),就是本次請求服務(wù)端返回給我們的數(shù)據(jù),也就是我們需要解析的數(shù)據(jù).
我們先來分析下我們需要發(fā)送的數(shù)據(jù),如圖所示,我用紅線標(biāo)出了4個位置,它們分別表示什么意思呢?
- Method: 表示這次請求的請求方式,一般常用的有g(shù)et和post,這里當(dāng)然選post(因為WebService就是post請求的一種)
- URL: 表示這次請求的地址
- Headers: 請求頭
-
Body: 請求體
調(diào)試http請求
介紹完了RESTClient的界面,下面就正式開始http調(diào)試.
所謂調(diào)試,無非就是模擬一次http請求,我們把需要發(fā)送給服務(wù)端的數(shù)據(jù)填到Request(圖2綠線內(nèi))中,點擊send按鈕,然后Response(圖2藍線內(nèi))中顯示服務(wù)端返回給我們的數(shù)據(jù).我們就是分析這堆數(shù)據(jù)而已.那么問題來了,Request中的這些數(shù)據(jù),是從哪來的?
我另外打開了一個瀏覽器頁面,在地址欄中輸入想要調(diào)試的地址,如圖3所示,為了方便,我讓服務(wù)端同事把服務(wù)器部署到我的電腦上了,所以看到的地址ip是192.168.191.1,
剛剛在地址欄輸入的就是這次要調(diào)試的地址,因此,我把他填入到了RESTClient的URL中.
既然url確定了,那請求頭(Headers)和請求體(Body)又該填什么呢?別急,接著往下看.
這次我要調(diào)試的就是圖3中紅線內(nèi)的接口.名為AssetMaterialInfo,點擊這個接口,打開的界面如圖4所示.圖中有SOAP1.2請求和響應(yīng)示例,紅線標(biāo)注的是占位符,在模擬數(shù)據(jù)的時候需要將其替換為真實數(shù)據(jù).
而我們所需要的請求頭(Headers)和請求體(Body)就藏在請求示例中.為了方便分析,我單獨將請求示例截取了圖片,放在下邊,也就是圖5.
圖5中,紅線內(nèi)這兩行內(nèi)容,就是請求頭,藍線內(nèi)的就是請求體.請求體很簡單,我們只需要將藍線中內(nèi)容復(fù)制到RESTClient界面的Body中,然后把占位符替換掉就可以了(如圖2所示),請求頭怎么弄呢?
關(guān)于在RESTClient中添加請求頭,我舉一個例子(圖6),大家就都明白了.
在RESTClient界面中,點擊頂部Headers,再點擊CustomHeader,會打開圖7這個界面.
在圖7所示界面,Name欄中填入Content-Type,Value欄中填入text/xml; charset=utf-8,然后點擊Okay,我們就將一個請求頭添加到本次請求中了.
同理,將第二個請求頭也添加進來.我就不再演示了.
這樣,我們就將本次請求需要攜帶的數(shù)據(jù)都添加進來了.點擊SEND按鈕,就完成了本次請求.
開始寫Demo
拿到了http請求的數(shù)據(jù),我們就可以根據(jù)數(shù)據(jù)去寫我們的例子程序了,在我們Android端通過Retrofit使用post請求去訪問剛剛我們調(diào)試的WebService接口.
首先,在看下邊代碼之前,你要保證自己已經(jīng)基本了解了Retrofit框架.關(guān)于Retrofit不會介紹太多,因為它不是本篇文章的重點.
導(dǎo)包
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'
}
請求體實例
對應(yīng)圖2中Request的Body部分,需要寫三個類來作為請求體.分別對應(yīng)請求體中的三個節(jié)點
根據(jù)由外到內(nèi)的層級關(guān)系,它們分別是(類名 <---> 節(jié)點名):
- 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注解用來指定節(jié)點名稱,NamespaceList用來指定多個命名空間,Element用來指定子節(jié)點名稱.
@Root(name = "soap:Body", strict = false)
public class RequestBody {
@Element(name = "AssetMaterialInfo", required = false)
public RequestModel AssetMaterialInfo;
}
同理,RequestBody類中,Root注解用來指定節(jié)點名稱,Element注解用來指定子節(jié)點名稱,因為當(dāng)前節(jié)點沒有命名空間,因此不需要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注解用來指定節(jié)點名稱,因為當(dāng)前節(jié)點只有一個命名空間,因此使用Namespace注解而不是NamespaceList.Element注解指定子節(jié)點名稱,因為當(dāng)前節(jié)點有兩個子節(jié)點,因此這個類有兩個參數(shù),且都被Element注解修飾.
到這里,關(guān)于請求體的實體類,就創(chuàng)建好了.下面我們準(zhǔn)備響應(yīng)體的實體類.
響應(yīng)體實例
跟請求體類似,我們也是需要根據(jù)服務(wù)器響應(yīng)的xml文件格式來創(chuàng)建實體類.
通過分析xml格式,我們也需要創(chuàng)建三個實體類來分別對應(yīng)三個節(jié)點.它們的對應(yīng)關(guān)系如下(類名 <---> 節(jié)點名):
- AssetResponseEnvelope <---> soap:Envelope
- AssetResponseBody <---> Body
- AssetResponseModel <---> AssetMaterialInfoResponse
為了與系統(tǒng)類名區(qū)分開,我為這三個類名添加了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的地址,上面有關(guān)于xml與實體類之間綁定的詳細介紹:
SimpleXml
創(chuàng)建Interface
三個請求類和三個響應(yīng)類創(chuàng)建好了,下一步就是創(chuàng)建請求接口Interface.(不明白為啥要這樣做的,可以去復(fù)習(xí)下Retrofit,哦不對,是預(yù)習(xí)~)
直接上代碼:
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);
}
在請求接口中,我們定義了一個函數(shù),將其請求方式制定為post請求,并且為其添加了請求頭.這個函數(shù)接收一個RequestEnvelope參數(shù).
使用Retrofit請求
所有初始化工作都做完之后,使用Retrofit去請求WebService這塊還是蠻簡單的.
不得不說,Retrofit確實很強大,整個請求流程下來,結(jié)構(gòu)清晰明了,一點都不拖泥帶水.如果再結(jié)合上RxJava,豈不是更爽?
/**
* 去服務(wù)端請求數(shù)據(jù)
*/
private void request() {
String url = "http://192.168.191.1:2000/";
// 初始化Retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(SimpleXmlConverterFactory.create()) // 返回數(shù)據(jù)為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) {
// 處理響應(yīng)體
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) {
}
});
}
總結(jié)
到此為止,一個基于Retrofit使用post請求訪問WebService的小Demo就算寫完了.我已經(jīng)將代碼提交到了GitHub,感興趣的同學(xué)可以去看一下,很簡單的小程序.
項目地址
有不明白的同學(xué),歡迎向我提出問題,我們共同學(xué)習(xí).
第一次寫文章,還望各位前輩多多批評指正,不勝感激!