Retrofit 2.0-beta-2 正在为 MultiPart 值添加字面引号

33

我想升级到Retrofit 2.0,但遇到了奇怪的问题。

我有一个用于登录用户的方法。

public interface ApiInterface {

    @Multipart
    @POST("user/login/")
    Call<SessionToken> userLogin(@Part("username") String username, @Part("password") String password);
}
当我查看服务器端的关键值POST参数时,它们会像这样打印。
username : "brian"
password : "password"

使用Retrofit 1.9的相同方法,K:V对如下所示:

username : brian
password : password

它是将双引号添加到POST变量中。

如果我使用其他REST客户端,则变量会像第二种方式一样打印,没有引号。

以下是我使用拦截器构建Retrofit实例的方法。

 OkHttpClient client = new OkHttpClient();
    client.interceptors().add(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request original = chain.request();

            // Customize the request
            Request request = original.newBuilder()
                    .header("Accept", "application/json")
                    .header("Authorization", myPrefs.accessToken().getOr(""))
                    .method(original.method(), original.body())
                    .build();

            Response response = chain.proceed(request);

            // Customize or return the response
            return response;
        }
    });

    Ok2Curl.set(client);

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(apiEndpoint)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .build();

我想我在使用转换器时做错了什么,但不确定具体是哪里出了问题。

还有其他人遇到过这个问题吗?我知道它还处于测试阶段,但使用范围相当广泛。


2
完全一样。刚完成了为期两天的Retrofit 2.0大规模升级,大部分功能都运行良好,但我正在为这些额外引号添加到字符串而发疯。使用@Multipart Retrofit API方法,字符串是一个@Part项。服务器将字符串“test”作为“”test“”接收。真让人抓狂!!! - Matthew Housser
@MatthewHousser 我还没有找到真正的解决方案,但由于我也控制着后端,因此我在客户端应用程序中设置了一个特殊的标头。然后,如果该标头存在,我运行一个方法,以从GET和POST参数中去除引号,这很不规范,但至少请求现在可以工作。我认为我会在GitHub存储库上开立一个问题。 - Brian
请在您创建问题后在这里发布,谢谢! - Matthew Housser
@MatthewHousser 你尝试降级到2.0-beta1了吗?我也试过,但要重构太多的代码才能让它正常工作。 - Brian
Github问题 https://github.com/square/retrofit/issues/1210 - Brian
显示剩余2条评论
7个回答

40

这是因为它正在通过JSON转换器运行。

解决方案1:使用RequestBody而不是String

public interface ApiInterface {
    @Multipart
    @POST("user/login/")
    Call<SessionToken> userLogin(@Part("username") RequestBody username, @Part("password") RequestBody password);
}

构建RequestBody:

RequestBody usernameBody = RequestBody.create(MediaType.parse("text/plain"), usernameStr);
RequestBody passwordBody = RequestBody.create(MediaType.parse("text/plain"), passwordStr);

启动网络操作:

 retrofit.create(ApiInterface.class).userLogin(usernameBody , passwordBody).enqueue()....

解决方案2:创建一个定制的ConverterFactory,用于处理字符串部分值。

适用于:Retrofit2正式版本(非beta)。(com.squareup.retrofit2:retrofit:2.0.0)

创建您自己的StringConverterFactory:

public class StringConverterFactory extends Converter.Factory {
private static final MediaType MEDIA_TYPE = MediaType.parse("text/plain");

public static StringConverterFactory create() {
    return new StringConverterFactory();
}

@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
    if (String.class.equals(type)) {
        return new Converter<ResponseBody, String>() {

            @Override
            public String convert(ResponseBody value) throws IOException {
                return value.string();
            }
        };
    }
    return null;
}

@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    if(String.class.equals(type)) {
        return new Converter<String, RequestBody>() {
            @Override
            public RequestBody convert(String value) throws IOException {
                return RequestBody.create(MEDIA_TYPE, value);
            }
        };
    }

    return null;
}

}
将以下内容添加到您的 retrofit 实例中:

Add to your retrofit instance:


retrofit = new Retrofit.Builder()
            .baseUrl(SERVER_URL)
            .client(client)
            .addConverterFactory(StringConverterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build();

注意:StringConverterFactory 应该在 GsonConverterFactory 之前添加!

然后你可以直接将 String 作为部分值使用。

关于此问题,您可以在https://github.com/square/retrofit/issues/1210找到更多信息。


1
为什么这个“Solution2”不能提交到Retrofit?我使用的是Retrofit:2.0.2,而只有这个答案能够解决问题,其他几个解决方案都没有起作用。 - Janaka R Rajapaksha
@JanakaRRajapaksha 正如Jake Wharton所说:“我不确定我想要添加它”。证据:https://github.com/square/retrofit/issues/1210 - Yazon2006
非常感谢!把文件放到这个请求里怎么样? - AlexS

8
我有同样的问题,解决方法如下:
1)在 build.gradle 中添加:
compile 'com.squareup.retrofit2:converter-scalars:2.1.0' // Remember to add the same version

在这里添加一行:
Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(URL_BASE)
                .addConverterFactory(ScalarsConverterFactory.create()) // this line
                .addConverterFactory(GsonConverterFactory.create(gson))
                .client(getUnsafeOkHttpClient())
                .build();

4

那么采取这种方式如何呢?

RequestBody caption = RequestBody.create(MediaType.parse("text/plain"), new String("caption"));

3
这里是如何解决它的方法,
首先:
 return new Retrofit.Builder()
    .baseUrl(Env.GetApiBaseUrl())
    .addConverterFactory(new GsonStringConverterFactory())
    .addConverterFactory(GsonConverterFactory.create(gson))
    .client(getHttpClient())
    .build();

创建一个类似于这个的CustomConverter,这是Retrofit 2所需的,除非有人修复了v2中添加的“功能”。
public class GsonStringConverterFactory extends Converter.Factory {
    private static final MediaType MEDIA_TYPE = MediaType.parse("text/plain");

    @Override
    public Converter<?, RequestBody> toRequestBody(Type type, Annotation[] annotations) {
        if (String.class.equals(type))// || (type instanceof Class && ((Class<?>) type).isEnum()))
        {
            return new Converter<String, RequestBody>() {
                @Override
                public RequestBody convert(String value) throws IOException {
                    return RequestBody.create(MEDIA_TYPE, value);
                }
            };
        }
        return null;
    }
}

3

除了这些之外,我找到了另一个解决方案。适用于Retrofit 2.1.0。(此处Rx适配器是可选的)

我的Retrofit接口如下:

@POST("/children/add")
Observable<Child> addChild(@Body RequestBody requestBody);

在ApiManager中,我这样使用它:

@Override
    public Observable<Child> addChild(String firstName, String lastName, Long birthDate, @Nullable File passportPicture) {
        MultipartBody.Builder builder = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("first_name", firstName)
                .addFormDataPart("last_name", lastName)
                .addFormDataPart("birth_date", birthDate + "");

        //some nullable optional parameter
        if (passportPicture != null) {
            builder.addFormDataPart("certificate", passportPicture.getName(), RequestBody.create(MediaType.parse("image/*"), passportPicture));
        }
        return api.addChild(builder.build());
    }

这与Loyea的Solution1类似,但我认为它更加优雅一些。


0
如果您的用户界面以引号显示响应,则可以使用getAsString而不是toString

0

我不知道是否太晚了,但我们也可以使用RequestBody发送请求。

示例:

public interface ApiInterface {
   @Multipart
   @POST("user/login/")
   Call<SessionToken> userLogin(@Part("username") String username, @Part("password") String password);
}

我们可以按以下方式进行转换:
public interface ApiInterface {
   @Multipart
   @POST("user/login/")
   Call<SessionToken> userLogin(@Part("username") RequestBody username, @Part("password") String password);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接