
前言
在開發(fā)中, 一般都使用json解析, 但在最近搞的一個(gè)項(xiàng)目中, 需要接入固有的老接口,必須用xml進(jìn)行解析. 搜索網(wǎng)上關(guān)于xml解析的文章不多, 也不夠詳細(xì), 所以在經(jīng)過一系列采坑之后, 我決心貢獻(xiàn)自己的微薄經(jīng)驗(yàn). retrofit自帶的json解析是GsonConverterFactory, xml解析時(shí)需替換為SimpleXmlConverterFactory, 此項(xiàng)目我采用了retrofit + rxjava2 + dagger2 + mvp + xml
準(zhǔn)備工作:建議先使用postman測試接口

本文側(cè)重于介紹xml解析
1、依賴庫
compile('com.squareup.retrofit2:converter-simplexml:2.3.0') {
exclude group: 'xpp3', module: 'xpp3'
exclude group: 'stax', module: 'stax-api'
exclude group: 'stax', module: 'stax'
}
2、請求體實(shí)例
可以看到根節(jié)點(diǎn)為soap:Envelope, 第二層節(jié)點(diǎn)有soap:Header和soap:Body; 在soap:Header內(nèi)部包含兩層節(jié)點(diǎn)為kios:kioskSoapHeader以及最內(nèi)層兩個(gè)元素; 在soap:Body內(nèi)部也包含兩層節(jié)點(diǎn)為kios:assignRoom和其內(nèi)部AssignRoom
以下為soap:Envelope節(jié)點(diǎn)寫法
@Root(name = "soap:Envelope", strict = false)
@NamespaceList({
@Namespace(reference = "http://www.w3.org/2003/05/soap-envelope", prefix = "soap"),
@Namespace(reference = "http://kunlun.shijinet.cn/project/kiosk/", prefix = "kios")
})
@Default
@Order(elements = {
"soap:Header/kios:kioskSoapHeader[1]/hardwareId",
"soap:Header/kios:kioskSoapHeader[1]/stationId",
"soap:Body/kios:assignRoom/AssignRoom"
})
public class AssignRoomParams {
public AssignRoomParams(String hardwareId, String stationId, AssignRoomKey assignRoom) {
this.hardwareId = hardwareId;
this.stationId = stationId;
this.AssignRoom = assignRoom;
}
@Element
@Path("soap:Header/kios:kioskSoapHeader[1]/")
public String hardwareId;
@Element
@Path("soap:Header/kios:kioskSoapHeader[1]/")
public String stationId;
@Element
@Path("soap:Body/kios:assignRoom/")
public AssignRoomKey AssignRoom;
}
因?yàn)閤ml請求時(shí)是無序的, 有可能造成soap:Body在soap:Header節(jié)點(diǎn)上部, 造成請求參數(shù)錯(cuò)誤; 所以此處采用了@Order與@path結(jié)合, 是為了保證soap:Header和soap:Body的上下次序; @Root注解用來指定節(jié)點(diǎn)名稱, @NamespaceList用來指定多個(gè)命名空間, @Element用來指定子節(jié)點(diǎn)名稱.
此處需注意@path內(nèi)部路徑指向其下部的變量名, 此處變量名代表節(jié)點(diǎn)名稱, 大小寫必須與節(jié)點(diǎn)名稱相一致, 所以可以看到此處第三個(gè)變量首字母為大寫.
以下為AssignRoom節(jié)點(diǎn)寫法
@Root(name = "AssignRoom", strict = false)
public class AssignRoomKey {
public AssignRoomKey(String reservationNumber, String roomRequest) {
this.reservationNumber = reservationNumber;
this.roomRequest = roomRequest;
}
@Attribute(name = "ReservationNumber")
public String reservationNumber;
@Attribute(name = "RoomRequest")
public String roomRequest;
}
3.響應(yīng)體實(shí)例
以下為soap:Envelope節(jié)點(diǎn)寫法
@Root(name = "soap:Envelope", strict = false)
@NamespaceList({
@Namespace(reference = "http://www.w3.org/2003/05/soap-envelope", prefix = "soap")
})
public class AssignRoomBean {
@Element(name = "Body")
public AssignRoomBody body;
}
此處注意@Root中name為soap:Envelope,與節(jié)點(diǎn)名稱一致, 但是@Element中name為Body,需要將soap:去掉, 這點(diǎn)很容易忽略, 在請求體中不能去掉, 而在響應(yīng)體內(nèi)部需要去掉這些頭目
以下為Body節(jié)點(diǎn)寫法
@Root(name = "Body", strict = false)
public class AssignRoomBody {
@Element(name = "assignRoomResponse")
public AssignRoomResponse response;
}
注意此處@Root內(nèi)部name一致去掉soap:頭目, 以下不再提示注意這點(diǎn),
以下為assignRoomResponse節(jié)點(diǎn)寫法
@Root(name = "assignRoomResponse", strict = false)
@NamespaceList({
@Namespace(reference = "http://kunlun.shijinet.cn/project/kiosk/", prefix = "ns2"),
@Namespace(reference = "http://webservices.micros.com/kiosk/3.0/", prefix = "ns3")
})
public class AssignRoomResponse {
@Element(name = "AssignRoomResult")
public AssignRoomResult result;
}
以下為AssignRoomResult節(jié)點(diǎn)寫法
@Root(name = "AssignRoomResult", strict = false)
public class AssignRoomResult {
@Element(name = "Result", required = false)
public Result Result;
@Element(name = "Room", required = false)
public Room Room;
}
注意此處在@Element中required = false表示此元素在響應(yīng)體中可能存在也可能不存在, 而在@Root中的strict = false也表示內(nèi)部有些元素可能在響應(yīng)體中不存在
以下為Result 節(jié)點(diǎn)寫法
@Root(name = "Result", strict = false)
public class Result {
@Attribute(name = "ResultType")
public String ResultType;
@Attribute(name = "ResultCode")
public String ResultCode;
}
以下為Room節(jié)點(diǎn)寫法
@Root(name = "Room", strict = false)
public class Room {
@Attribute(name = "Number")
public String Number;
@Attribute(name = "RoomClass")
public String RoomClass;
@Attribute(name = "RoomType")
public String RoomType;
@Attribute(name = "Status")
public String Status;
@Element(name = "Features", required = false)
public Features Features;
}
以下為Features 節(jié)點(diǎn)寫法
@Root(name = "Features", strict = false)
public class Features {
@ElementList(required = false,inline = true,entry = "Feature")
public List<Feature> Feature;
}
注意此處使用@ElementList,代表集合,表示內(nèi)部可能會有多個(gè)Feature元素.
以下為Feature節(jié)點(diǎn)寫法
@Root(name = "Feature", strict = false)
public class Feature {
@Attribute(name = "Code", required = false)
public String Code;
}
此處為重要參考鏈接:
參考博客,對我內(nèi)容的全面補(bǔ)充
SimpleXml的文檔
創(chuàng)建Interface
@POST("kunlun-kiosk-new/KioskWebService?wsdl")
Observable<AssignRoomBean> assignRoom(@Body AssignRoomParams params);
創(chuàng)建Contract
public interface AssignRoomContract {
interface IAssignRoomModel {
Observable<AssignRoomBean> assignRoom(AssignRoomParams params);
}
interface IAssignRoomView extends IBaseView {
void success(AssignRoomBean bean);
}
}
創(chuàng)建Model
public class AssignRoomModel implements AssignRoomContract.IAssignRoomModel {
private ApiService apiService;
public AssignRoomModel(ApiService apiService) {
this.apiService = apiService;
}
@Override
public Observable<AssignRoomBean> assignRoom(AssignRoomParams params) {
return apiService.assignRoom(params);
}
}
創(chuàng)建Module
@Module
public class AssignRoomModule {
private AssignRoomContract.IAssignRoomView view;
public AssignRoomModule(AssignRoomContract.IAssignRoomView view) {
this.view = view;
}
@Provides
public AssignRoomContract.IAssignRoomModel provideModel(ApiService apiService) {
return new AssignRoomModel(apiService);
}
@Provides
public AssignRoomContract.IAssignRoomView provideView() {
return view;
}
}
創(chuàng)建Presenter
public class AssignRoomPresenter extends BasePresenter<AssignRoomContract.IAssignRoomModel, AssignRoomContract.IAssignRoomView> {
@Inject
public AssignRoomPresenter(AssignRoomContract.IAssignRoomModel model, AssignRoomContract.IAssignRoomView view) {
super(model, view);
}
public void assignRoom(AssignRoomParams params) {
mModel.assignRoom(params)
.compose(RxSchedulers.<AssignRoomBean>io_main())
.subscribe(new ErrorHandleObserver<AssignRoomBean>(mContext) {
@Override
public void onNext(@NonNull AssignRoomBean assignRoomBean) {
mView.success(assignRoomBean);
}
});
}
}
在fragement中調(diào)用
@Override
protected void initxChild() {
String reservationNumber = "15021";
String roomRequest = "1001";
AssignRoomKey assignRoom = new AssignRoomKey(reservationNumber, roomRequest);
AssignRoomParams params = new AssignRoomParams(Constant.HARDWARE_ID, Constant.STATION_ID, assignRoom);
presenter.assignRoom(params);
}
相關(guān)代碼
public static final String HARDWARE_ID = "83DBDBAF1B8DE6FF3A3510598BE76B3975807475C4270A9F3B2A98229DB
D29E3"; public static final String STATION_ID = "X20-A";
@NormalScope
@Component(modules = AssignRoomModule.class, dependencies = AppComponent.class)
public interface MainComponent {
void inject(MainFragment fragment);
}
@Singleton
@Component(modules = {AppModule.class, HttpModule.class})
public interface AppComponent {
ApiService getApiService();
BoermanApplication getWomoApplication();
RxErrorHandler getRxErrorHandler();
}
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface NormalScope {
}
@Module
public class HttpModule {
@Provides
@Singleton
public OkHttpClient provideOkHttpClient(BoermanApplication application) {
boolean debug = PropertyUtil.isLogAvailable(application);
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (debug) {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
LogUtil.logger(message);
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder .addInterceptor(loggingInterceptor);
}
return builder
.connectTimeout(8, TimeUnit.SECONDS)
.readTimeout(8, TimeUnit.SECONDS)
.build();
}
@Provides
@Singleton
public Retrofit provideRetrofit(OkHttpClient okHttpClient, BoermanApplication application) {
boolean debug = PropertyUtil.isLogAvailable(application);
Retrofit.Builder builder = new Retrofit.Builder();
if (debug) {
builder = builder.baseUrl(ApiService.BASE_URL_TEST);
} else {
builder = builder.baseUrl(ApiService.BASE_URL_LINE);
}
return builder
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(SimpleXmlConverterFactory.create())
.client(okHttpClient)
.build();
}
@Singleton
@Provides
public ApiService provideApiService(Retrofit retrofit) {
return retrofit.create(ApiService.class);
}
@Singleton
@Provides
public RxErrorHandler provideRxHandler(BoermanApplication application) {
return new RxErrorHandler(application);
}
}
public class RxSchedulers {
public static <T> ObservableTransformer<T, T> io_main() {
return new ObservableTransformer<T, T>() {
@Override
public ObservableSource<T> apply(Observable<T> upstream) {
return upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}
}
public abstract class ErrorHandleObserver<T> extends DefaultObserver<T> {
private Context context;
protected RxErrorHandler rxErrorHandler;
public ErrorHandleObserver(Context context) {
this.context = context;
this.rxErrorHandler = new RxErrorHandler(context);
}
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onError(@NonNull Throwable e) {
BaseException baseException = rxErrorHandler.onErrorHandle(e);
if (baseException == null) {
LogUtil.logger(e.getMessage());
} else {
rxErrorHandler.showErrorMessage(baseException);
}
}
@Override
public void onComplete() {
}
}
總結(jié)
xml解析的bean類需要手寫, 容易出錯(cuò), 建議解析接口出錯(cuò)時(shí),可以使用抓包工具Fiddler分析, 還是挺好用的; 當(dāng)然通過攔截器打印的logger日志也是一樣的作用, 但本人還是喜歡用Fidder
Fiddler抓包工具使用連接