Retrofit之解析xml (詳細(xì))

前言

在開發(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抓包工具使用連接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容