如何使用 Retrofit 2 处理空响应 body?

How can I handle empty response body with Retrofit 2?

最近我开始使用 Retrofit 2,但遇到了解析空响应的问题 body。我有一台服务器只响应 http 代码,响应中没有任何内容 body.

如何只处理有关服务器响应的元信息(headers、状态代码等)?

编辑:

正如杰克沃顿指出的那样,

@GET("/path/to/get")
Call<Void> getMyData(/* your args here */);

与我最初的回复相比,这是最好的方式 --

您可以 return 一个 ResponseBody,这将绕过解析响应。

@GET("/path/to/get")
Call<ResponseBody> getMyData(/* your args here */);

然后在你的通话中,

Call<ResponseBody> dataCall = myApi.getMyData();
dataCall.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Response<ResponseBody> response) {
        // use response.code, response.headers, etc.
    }

    @Override
    public void onFailure(Throwable t) {
        // handle failure
    }
});

如果您使用的是 rxjava,请使用类似 :

@GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);

以下是我如何将它与 Rx2 和 Retrofit2 一起使用,并带有 PUT REST 请求: 我的请求有一个 json 主体,但只有带有空主体的 http 响应代码。

Api 客户:

public class ApiClient {
public static final String TAG = ApiClient.class.getSimpleName();


private DevicesEndpoint apiEndpointInterface;

public DevicesEndpoint getApiService() {


    Gson gson = new GsonBuilder()
            .setLenient()
            .create();


    OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);
    okHttpClientBuilder.addInterceptor(logging);

    OkHttpClient okHttpClient = okHttpClientBuilder.build();

    apiEndpointInterface = new Retrofit.Builder()
            .baseUrl(ApiContract.DEVICES_REST_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
            .create(DevicesEndpoint.class);

    return apiEndpointInterface;

}

界面:

public interface DevicesEndpoint {
 @Headers("Content-Type: application/json")
 @PUT(ApiContract.DEVICES_ENDPOINT)
 Observable<ResponseBody> sendDeviceDetails(@Body Device device);
}

然后使用它:

    private void sendDeviceId(Device device){

    ApiClient client = new ApiClient();
    DevicesEndpoint apiService = client.getApiService();
    Observable<ResponseBody> call = apiService.sendDeviceDetails(device);

    Log.i(TAG, "sendDeviceId: about to send device ID");
    call.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<ResponseBody>() {
        @Override
        public void onSubscribe(Disposable disposable) {
        }

        @Override
        public void onNext(ResponseBody body) {
            Log.i(TAG, "onNext");
        }

        @Override
        public void onError(Throwable t) {
            Log.e(TAG, "onError: ", t);

        }

        @Override
        public void onComplete() {
            Log.i(TAG, "onCompleted: sent device ID done");
        }
    });

}

如果你使用RxJava,那么在这种情况下最好使用Completable

Represents a deferred computation without any value but only indication for completion or exception. The class follows a similar event pattern as Reactive-Streams: onSubscribe (onError|onComplete)?

http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Completable.html

在接受的答案中:

@GET("/path/to/get")
Observable<Response<Void>> getMyData(/* your args here */);

如果端点 returns 失败响应代码,它仍然会在 onNext 中,您必须自己检查响应代码。

但是,如果您使用 Completable

@GET("/path/to/get")
Completable getMyData(/* your args here */);

您将只有 onCompleteonError。 如果响应代码成功,它将触发 onComplete 否则它将触发 onError.

对于 kotlin,使用 return 类型 Call<Void> 仍然会抛出 IllegalArgumentException: Unable to create converter for retrofit2.Call<java.lang.Void>

使用 Response 而不是 Call 解决了问题

@DELETE("user/data")
suspend fun deleteUserData(): Response<Void>

你可以试试这个

Retrofit retrofit = new Retrofit.Builder().baseUrl(baseUrl)
                .addConverterFactory(new NullOnEmptyConverterFactory())
.client(okHttpClient).build();
 class NullOnEmptyConverterFactory extends Converter.Factory {
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
        return (Converter<ResponseBody, Object>) body -> {
            if (body.source().exhausted()) return null;
            return delegate.convert(body);
        };
    }
}

下面是一个 MVVM 中的 Kotlin 示例,其中包含服务、存储库和 ViewModel:

服务:

@POST("/logout")
suspend fun logout(@Header("Authorization") token: String):Response<Unit>

存储库:

    //logout
        private val mLogoutResponse = MutableLiveData<String>()
        val logoutResponse: LiveData<String>
            get() {
                return mLogoutResponse
            }
        suspend fun logout(token: String) {
            try {
                val result=quizzerProfileApi.logout(token)
                if(result.code()!=0)
                {
                    mLogoutResponse.postValue(result.code().toString())
                }
            } catch (e: Exception) {
                Log.d("ProfileRepository", "logout: Error: $e")
            }
        }

视图模型:

fun logout(token: String) {
    viewModelScope.launch {
        repository.logout(token)
    }
}
val logoutResponseCd: LiveData<String>
        get() = repository.logoutResponse

在Activity中:

 private fun logout() {
        myViewModel.logout(token)
        myViewModel.logoutResponseCd.observe(this, Observer {
            if(it!="0"){
                Log.d(TAG, "logout: code= $it")
                finish()
            }
            else
                Toast.makeText(this, "Error logging out: $it", Toast.LENGTH_SHORT).show()
        })
    }