前言
在开发中, 一般都使用json解析, 但在最近搞的一个项目中, 需要接入固有的老接口,必须用xml进行解析. 搜索网上关于xml解析的文章不多, 也不够详细, 所以在经过一系列采坑之后, 我决心贡献自己的微薄经验. retrofit自带的json解析是GsonConverterFactory, xml解析时需替换为SimpleXmlConverterFactory, 此项目我采用了retrofit + rxjava2 + dagger2 + mvp + xml
准备工作:建议先使用postman测试接口
本文侧重于介绍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、请求体实例
可以看到根节点为soap:Envelope, 第二层节点有soap:Header和soap:Body; 在soap:Header内部包含两层节点为kios:kioskSoapHeader以及最内层两个元素; 在soap:Body内部也包含两层节点为kios:assignRoom和其内部AssignRoom
以下为soap:Envelope节点写法
@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;
}
因为xml请求时是无序的, 有可能造成soap:Body在soap:Header节点上部, 造成请求参数错误; 所以此处采用了@Order与@path结合, 是为了保证soap:Header和soap:Body的上下次序; @Root注解用来指定节点名称, @NamespaceList用来指定多个命名空间, @Element用来指定子节点名称.
此处需注意@path内部路径指向其下部的变量名, 此处变量名代表节点名称, 大小写必须与节点名称相一致, 所以可以看到此处第三个变量首字母为大写.
以下为AssignRoom节点写法
@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.响应体实例
以下为soap:Envelope节点写法
@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,与节点名称一致, 但是@Element中name为Body,需要将soap:去掉, 这点很容易忽略, 在请求体中不能去掉, 而在响应体内部需要去掉这些头目
以下为Body节点写法
@Root(name = "Body", strict = false)
public class AssignRoomBody {
@Element(name = "assignRoomResponse")
public AssignRoomResponse response;
}
注意此处@Root内部name一致去掉soap:头目, 以下不再提示注意这点,
以下为assignRoomResponse节点写法
@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节点写法
@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表示此元素在响应体中可能存在也可能不存在, 而在@Root中的strict = false也表示内部有些元素可能在响应体中不存在
以下为Result 节点写法
@Root(name = "Result", strict = false)
public class Result {
@Attribute(name = "ResultType")
public String ResultType;
@Attribute(name = "ResultCode")
public String ResultCode;
}
以下为Room节点写法
@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 节点写法
@Root(name = "Features", strict = false)
public class Features {
@ElementList(required = false,inline = true,entry = "Feature")
public List Feature;
}
注意此处使用@ElementList,代表集合,表示内部可能会有多个Feature元素.
以下为Feature节点写法
@Root(name = "Feature", strict = false)
public class Feature {
@Attribute(name = "Code", required = false)
public String Code;
}
此处为重要参考链接:
参考博客,对我内容的全面补充
SimpleXml的文档
创建Interface
@POST("kunlun-kiosk-new/KioskWebService?wsdl")
Observable assignRoom(@Body AssignRoomParams params);
创建Contract
public interface AssignRoomContract {
interface IAssignRoomModel {
Observable assignRoom(AssignRoomParams params);
}
interface IAssignRoomView extends IBaseView {
void success(AssignRoomBean bean);
}
}
创建Model
public class AssignRoomModel implements AssignRoomContract.IAssignRoomModel {
private ApiService apiService;
public AssignRoomModel(ApiService apiService) {
this.apiService = apiService;
}
@Override
public Observable assignRoom(AssignRoomParams params) {
return apiService.assignRoom(params);
}
}
创建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;
}
}
创建Presenter
public class AssignRoomPresenter extends BasePresenter {
@Inject
public AssignRoomPresenter(AssignRoomContract.IAssignRoomModel model, AssignRoomContract.IAssignRoomView view) {
super(model, view);
}
public void assignRoom(AssignRoomParams params) {
mModel.assignRoom(params)
.compose(RxSchedulers.io_main())
.subscribe(new ErrorHandleObserver(mContext) {
@Override
public void onNext(@NonNull AssignRoomBean assignRoomBean) {
mView.success(assignRoomBean);
}
});
}
}
在fragement中调用
@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);
}
相关代码
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 ObservableTransformer io_main() {
return new ObservableTransformer() {
@Override
public ObservableSource apply(Observable upstream) {
return upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}
}
public abstract class ErrorHandleObserver extends DefaultObserver {
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() {
}
}
总结
xml解析的bean类需要手写, 容易出错, 建议解析接口出错时,可以使用抓包工具Fiddler分析, 还是挺好用的; 当然通过拦截器打印的logger日志也是一样的作用, 但本人还是喜欢用Fidder
Fiddler抓包工具使用连接