Retrofit之解析xml (详细)

Retrofit之解析xml (详细)_第1张图片

前言

在开发中, 一般都使用json解析, 但在最近搞的一个项目中, 需要接入固有的老接口,必须用xml进行解析. 搜索网上关于xml解析的文章不多, 也不够详细, 所以在经过一系列采坑之后, 我决心贡献自己的微薄经验. retrofit自带的json解析是GsonConverterFactory, xml解析时需替换为SimpleXmlConverterFactory, 此项目我采用了retrofit + rxjava2 + dagger2 + mvp + xml

准备工作:建议先使用postman测试接口

Retrofit之解析xml (详细)_第2张图片

本文侧重于介绍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抓包工具使用连接

你可能感兴趣的:(Retrofit之解析xml (详细))