[note] encapsulate the pit encountered by okhttp + rtofit + rxjava + simplexml

Keywords: xml Retrofit encoding Attribute

First of all, in the context of this new project, the docking Party's high cold exception, all the requests and returns are in XML format, so I'm very tired. At present, the company doesn't have any well packaged network request package. Thinking of the RXjava used in the past, it just picked it up and started again. It is not clear whether the other party uses POST or GET requests. After all the interfaces are tested, locate the POST request. The prototype of the package is an open source project found on the Internet, and then modified based on this open source project, which will be faster and easier to write than yourself. Here are the pits I stepped on:

1, Pit on interface definition

public interface NetServer{

    @GET
    Call<ResponseBody> getRequest(@Url String url, @QueryMap Map<String, String> map);

    @FormUrlEncoded
    @POST
    Call<ResponseBody> postRequest(@Url String url, @FieldMap Map<String, String> postMap);
    @FormUrlEncoded
    @POST
    Call<ResponseBody> postRequest(@Url String url, @Field("params") String body);

    @GET("{url}/{body}")
    Call<ResponseBody> getRequest(@Path("url") String url, @Path("body") String body);

    @POST
    Call<ResponseBody> postRequest(@Url String url, @Body Object body);
}

The above is the code defined by my interface, which has been modified. There are a lot of comments in it. I have recorded them one by one:

1. Error: onError: @Path parameters may not be used with @Url

Error reason: path annotation and URL annotation cannot be used at the same time. Path annotation is used to replace the parameters in URL path. This requires that the request path must already exist when path annotation is used. Otherwise, the parameters specified in the path cannot be replaced. URL annotation is the request path specified in the parameters. At this time, it is too late to specify the request path. Path annotation cannot find the request path.

Solution: use them differently, or refer to the fourth interface writing method, or add placeholders in the url, for example:

//www.mylist.com/get{params}
@GET
Call<ResponseBody> postRequest(@Url String url, @Path("params") String body);

There are also detailed explanations for annotations Reference here.

2. Error: @ Body parameters cannot be used with form or multi-part encoding

Error reason: in fact, the error message is very clear. The @ Body tag cannot be used at the same time as the @ FormUrlEncoded and @ Multipart tags.

 

The third interface I used to use was not @ Field but @ Body. Then I encountered the above error. The solution is to remove the @ FormUrlEncoded annotation. I won't explain the specific reasons. If you want to see the details, you can click here.

3. error: Unable to create @Body converter for XXXEntry

Error reason: cannot create converter for corresponding entity. This is my pot. For some reasons, I want to pass in parameters that I manually convert, and use them directly as a String object. But the reality is, @ Body is forced to rely on the converter, so when using Retrofit, you must add the corresponding ConvertFactory.

The solution is simple: add the corresponding ConvertFactory:

 mRetrofit = new Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(XMLConvertFactory.create())
                .client(mHttpClient)
                .build();

Please refer to the link given in error 2 for specific explanation. It's just that my mistake is not quite the same as that blogger's, but it's not much different.

4. Error: onerror: org.simpleframework.xml.core.valuerequiredexception: unable to satisfy @ org.simpleframework.xml.element (data = false, name = userid, required = true, type = void) on field 'userid' private java.lang.string xxx.xxx.yuser.userid for class xxx.xxx.xxx.yhuser at line 3

Error reason: improper use of xml annotations. Here I avoid the pit of inner class, but step on the pit of annotation. When simplexml is used to parse the received data, it is very important to use the annotation correctly, almost all of which are parsing failures.

The data I receive has the following two data formats:

<?xml version='1.0' encoding='UTF-8'?>
  <results code="0">
      <user userId="1" name="Fishery testing" checkDistrict="Test site"/>
  </results>

<?xml version='1.0' encoding='UTF-8'?>
  <results code="1">
    <message>Password cannot be empty</message>
  </results>

Incorrect annotations are used as follows:

@Root(name ="results",strict = false)
public class UserResult  implements Serializable {
    @Element(name ="user",required = false)
    private YHUser user;
    @Element(name ="code")
    private String code;
    @Element(name ="message",required = false)
    private String message;
}

@Root(name ="user",strict = false)
public class YHUser {
    @Element(name ="userId")
    private String userId;
    @Element(name ="name")
    private String name;
    @Element(name ="checkDistrict")
    private String checkDistrict;
}

The correct way of writing is as follows:

// @Root(strict = false) @ comment (not strict check)
@Root(name ="results",strict = false)
public class UserResult  implements Serializable {
    @Element(name ="user",required = false)
    private YHUser user;
    @Attribute(name ="code")
    private String code;
    @Element(name ="message",required = false)
    private String message;
}
@Root(name ="user",strict = false)
public class YHUser {
    @Element(name ="userId")
    private String userId;
    @Attribute(name ="name")
    private String name;
    @Attribute(name ="checkDistrict")
    private String checkDistrict;
}

Carefully observe the above two writing methods and the format of the converted data, that is, the difference between the two annotations @ Attribute and @ Element. In addition, strict = false, which is required if not required, and is true by default. If the background is as casual as the background I docked, some non required fields must be added with required = false, or the result will be various error reports. In addition, we should pay attention to the problem of constructing parameters here. If you don't have a constructor with parameters, you can not write it. If you have one, you must write an empty constructor!!!

5. Error: java.lang.IllegalStateException: Already executed

Error reason: a call can only be run once, and it can be cancel led after running. I made multiple calls for printing and debug ging, so you need clone.

 T t = NetParserXMLUtil.parserT(mClazz, responseBody.clone().execute().body());

The above is my code modification, but when I delete the log print, I find that I will still report this error as before. Although clone can solve the problem, I am still confused here. I don't know where the method is called repeatedly. But according to the law, if I interrupt debugging in debug mode, I will report 100% errors here. If it is normal operation, it is not necessarily to report errors.

2, About debug and interceptor

I don't know if it's my debugging tool or how it works. The fifth error above is bound to appear in Debug mode, especially if you hit a breakpoint.

This time, I paid attention to a very practical thing, interceptor. My OKhttp can customize interceptor, which is super easy to use!!!!

mManger = new HttpManager(BASE_URL, new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request originalRequest = chain.request();
                Request.Builder builder = originalRequest.newBuilder();
//                HttpUrl originalHttpUrl = originalRequest.url();
//                String queryString = originalHttpUrl.encodedQuery(); / / get the parameter part of the url
//                Log.i(getClass().getName(), "-------queryString------>" + queryString);
//                String path = originalHttpUrl.url().getPath(); / / get the relative address
//                Log.i(getClass().getName(), "-------path------>" + path);
//                Buffer buffer = new Buffer();
//                originalRequest.body().writeTo(buffer);
//                String requestContent = buffer.readUtf8(); / / used for post request to get form content
//                Log.i(getClass().getName(), "-------requestContent------>" + requestContent);
        
                builder.addHeader("Content-Type", "application/xml; charset=UTF-8");
                Request request = builder.build();
                return chain.proceed(request);
            }
        }, new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Response response = chain.proceed(request);
                Response.Builder builder = response.newBuilder();
                if (response.header("Content-Encoding", "").contains("gzip")) {
                    BufferedSource bufferedSource = Okio.buffer(new GzipSource(response.body().source()));
                    String temStr = bufferedSource.readUtf8();
                    bufferedSource.close();
                    ResponseBody body = ResponseBody.create(MediaType.parse("application/json"), temStr);
                    builder.body(body);
                } else {
                    BufferedSource bufferedSource = Okio.buffer(response.body().source());
                    String temStr = bufferedSource.readUtf8();
                    bufferedSource.close();
                    ResponseBody body = ResponseBody.create(MediaType.parse("application/xml"), temStr);
//                    Log.i(getClass().getName(), "--------temStr-------->" + temStr);
//                    ResponseBody body = ResponseBody.create(MediaType.parse("application/json"), temStr);
                    builder.body(body);
                }
                builder.removeHeader("Pragma");
                builder.header("Cache-Control", "public , only-if-cached");
                return builder.build();
            }
        });

The above commented part is about the use of interceptor debugging. When you don't make a server and always say that your parameters are wrong, the interceptor will be used. When I stepped on the 1 and 2 pits, I couldn't find the problem. Because the server always reported that the parameter could not be empty. Before I sent the request, I printed the parameters clearly, and the format was correct. I couldn't find the problem. Finally, my colleague reminded me that I used the interceptor to find the problem.

3, In addition, there are some materials I found in the process of solving the problem:

1. It's time to evaluate Retrofit objectively. You must understand these points!

2. The meaning and function of various notes of retrofit

3. This is a very detailed tutorial of Retrofit 2.0 (including example explanation)

4. Introduction to new features of retrofit2.0

Published 13 original articles, won praise 3, visited 9855
Private letter follow

Posted by Desertwar on Wed, 19 Feb 2020 23:37:39 -0800