Android 改进 POST 方法,在较新版本中使用字符串查询参数双引号

Android retrofit POST method with String Query param double quotes in newer versions

我有这个改造方法 API:

@POST("/searchCity/byname")
Call<Cities> searchCityByName(@Query("name") String name);

从代码中以这种方式调用它:

final String cityName ="City"
restClient.getApiService().searchCityByName(cityName);

每当我使用 retrofit 2.1.0 发送请求时,url 就是以下一个(正确):

searchCity/byname?name=City

但是,当我使用 retrofit 2.2.0 及更高版本 发送相同的请求时,url 更改为(不正确 ):

searchCity/byname?name=%22City%22

至此,由于编码双引号(%22 解码为 "),服务器在处理请求时失败。

我已经按照程序流程进行操作,但我什么也没发现...可能发生了什么?

我需要在使用第一个 URL(没有引号的)的同时使用改造 2.3.0。有什么办法可以实现吗?

编辑 - 03/Apr/2018:

这里是用过的类:

RetrofitClient.java

@EBean(scope = EBean.Scope.Singleton)
public class RetrofitClient {
    private static volatile Retrofit retrofit;
    private static Retrofit.Builder builder = new Retrofit.Builder().baseUrl("https://exampleserver.com/");
    private PrivateApi apiService;

    public static <S> S createService(Class<S> serviceClass) {
        final GsonBuilder gsonBuilder = new GsonBuilder();
        final Gson gson = gsonBuilder.create();
        retrofit = builder.client(new WebClient().getClient())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();
        return retrofit.create(serviceClass);
    }

    @AfterInject
    public void privateRestClientAfterInject() {
        final PrivateApi apiService = createService(PrivateApi.class);
        final PrivateApiServiceInvocationHandler publicApiServiceInvocationHandler = new
                PrivateApiServiceInvocationHandler(apiService);
        this.apiService = (PrivateApi) Proxy.newProxyInstance(PrivateApi.class.getClassLoader(),
                new Class[]{PrivateApi.class}, publicApiServiceInvocationHandler);
    }

    public PrivateApi getApiService() {
        if (apiService == null) {
            privateRestClientAfterInject();
        }
        return apiService;
    }
}

WebClient.java

public class WebClient {

    public OkHttpClient getClient() {
        final HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor
        .Level.BODY);

        final OkHttpClient.Builder builder = new OkHttpClient.Builder().readTimeout
                (30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .connectTimeout(30, TimeUnit.SECONDS)
                .addInterceptor(logging)
                .followRedirects(false)
                .followSslRedirects(false);



                builder.addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        final Request originalRequest = chain.request();
                        final HttpUrl newurl = originalRequest.url().newBuilder().build();
                        final Request newRequest = originalRequest.newBuilder()
                                .url(newurl)
                                .headers(originalRequest.headers())
                                .header("Connection", "close")
                                .header("User-Agent",
                                        getUserAgentAndKeyboard())
                                .method(originalRequest.method(), originalRequest.body())
                                .build();
                        return chain.proceed(newRequest);
                    }
                });
        return builder.build();
    }

    /**
     * Returns the standard user-agent with the default input method brand name
     *
     * @return The concatenated "user-agent; keyboard" string
     */
    private static String getUserAgentAndKeyboard() {
        // WEWE-857
        ContentResolver contentResolver = BaseApplication.getInstance().getContentResolver();
        if (contentResolver != null) {
            String keyboard = Settings.Secure.getString(contentResolver, Settings.Secure
                    .DEFAULT_INPUT_METHOD);
            return String.format(Locale.getDefault(), "%s;%s", System.getProperty("http.agent"),
                    keyboard);
        }
        return System.getProperty("http.agent");
    }

}

RetrofitInvocationHandler.java

public class RetofitInvocationHandler implements InvocationHandler {

    private final Object target;

    public RetofitInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(target, args);
    }
}

PrivateApi.java

public interface PrivateApi {
  @Headers({
    "Content-Type:application/json"
  })
  @POST("/searchCity/byname")
  Call<Cities> searchCityByName(@Query("name") String name);
}

终于找到解决办法了:

只需将自定义转换器添加到 re-write 查询参数:

public static <S> S createService(Class<S> serviceClass) {
    final GsonBuilder gsonBuilder = new GsonBuilder();
    final Gson gson = gsonBuilder.create();
    retrofit = builder.client(new WebClient().getClient())
            .addConverterFactory(GsonConverterFactory.create(gson))
            // custom interceptor
            .addConverterFactory(new StringConverterFactory(GsonConverterFactory.create(gson)))
            // custom interceptor
            .build();
    return retrofit.create(serviceClass);
}

StringCoverterFactory.java

public class DateStringConverterFactory extends Converter.Factory {

    private final Converter.Factory delegateFactory;

    DateStringConverterFactory(Converter.Factory delegateFactory) {
        super();
        this.delegateFactory = delegateFactory;
    }

    @Override
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit
            retrofit) {
        for (final Annotation annotation : annotations) {
            if (annotation instanceof Query) {

                final Converter<?, RequestBody> delegate = delegateFactory
                        .requestBodyConverter(type, annotations, new Annotation[0], retrofit);
                return new DelegateToStringConverter<>(delegate);
            }
        }
        return null;
    }


    static class DelegateToStringConverter<T> implements Converter<T, String> {
        private final Converter<T, RequestBody> delegate;

        DelegateToStringConverter(Converter<T, RequestBody> delegate) {
            this.delegate = delegate;
        }

        @Override
        public String convert(T value) throws IOException {
            final okio.Buffer buffer = new okio.Buffer();
            delegate.convert(value).writeTo(buffer);
            if(value instanceof String){
                return value.toString();
            }
            else {
                return buffer.readUtf8();
            }
        }
    }
}

感谢所有努力做出贡献的人!!