Android中Retrofit Callback获取JWT Token时response.body()为空问题
我来帮你梳理下可能导致这个问题的几个常见原因,咱们一步步排查:
1. 先确认后端响应是否符合预期(最关键的一步)
很多时候问题出在后端返回的内容和我们预期的不一样,建议你给Retrofit加上OkHttp日志拦截器,这样能看到完整的请求和响应细节,包括后端返回的原始JSON。
添加日志拦截器的步骤:
首先在你的build.gradle(Module级别)里添加依赖:
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
然后修改RetrofitClient的配置,加入日志拦截器:
private static Retrofit ourInstance; public static Retrofit getInstance(){ if(ourInstance==null) { // 日志拦截器,打印完整请求响应 HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BODY); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(logging) .build(); ourInstance = new Retrofit.Builder() .baseUrl("http://172.20.10.2:8080") .client(client) // 加入配置好的OkHttpClient .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); } return ourInstance; }
添加后运行App,在Logcat里就能看到请求的URL、参数,以及后端返回的状态码、JSON内容。如果后端返回的不是{"token":"xxx"},或者状态码不是200-300之间,那response.body()自然会为空。
2. 检查实体类的Gson序列化是否正确
你的TokenInfo类用了@SerializedName("token")和@Expose,这里有几个要注意的点:
- 默认的
GsonConverterFactory.create()不会忽略没有@Expose注解的字段,但如果你自定义了Gson实例并开启了excludeFieldsWithoutExposeAnnotation(),那会有问题。不过看你的代码是用的默认Gson,所以这个可能性不大。 - 可以手动测试Gson解析是否正常,比如在
MainActivity里加一段测试代码:
String testJson = "{\"token\":\"test123456\"}"; TokenInfo testToken = new Gson().fromJson(testJson, TokenInfo.class); Log.d("GsonTest", "解析后的token: " + testToken.token);
如果这段代码能正常打印出test123456,说明实体类的序列化没问题;如果不行,那就是实体类的注解或者字段定义有问题(比如字段是private但没加getter,不过你的字段是public的,应该没问题)。
3. 检查Retrofit的baseUrl和接口路径是否匹配
你的baseUrl是"http://172.20.10.2:8080",接口是@POST("/login"),拼接后的完整URL是http://172.20.10.2:8080/login。要确认后端的接口是不是这个路径:
- 如果后端Spring Boot应用有上下文路径(比如配置了
server.servlet.context-path=/api),那你的baseUrl应该改成"http://172.20.10.2:8080/api/",接口改成@POST("login")(去掉前面的斜杠)。 - 另外,确保你的手机和后端服务器在同一个局域网里,能访问到
172.20.10.2:8080这个地址。
4. 检查响应状态码和错误信息
你的代码里只判断了response.isSuccessful(),但这个方法只在响应码为200-300之间时才返回true。如果后端返回了400(参数错误)、401(认证失败)等状态码,response.body()会是空的,这时候要查看response.errorBody()的内容:
@Override public void onResponse(Call<TokenInfo> call, Response<TokenInfo> response) { if(response.isSuccessful()){ Log.d("token", response.body().token); } else { // 打印错误状态码和错误内容 Log.e("LoginError", "状态码: " + response.code()); try { Log.e("LoginError", "错误内容: " + response.errorBody().string()); } catch (IOException e) { e.printStackTrace(); } } openDeviceChoiceActivity(); }
这样就能知道后端为什么返回非成功状态码,进而排查问题。
5. Android版本的明文HTTP限制
如果你的App运行在Android 9(API 28)及以上版本,默认是禁止明文HTTP请求的(你的后端用的是http而不是https)。这时候需要在AndroidManifest.xml的application标签里添加android:usesCleartextTraffic="true":
<application ... android:usesCleartextTraffic="true"> ... </application>
如果没有这个配置,请求可能被系统拦截,导致无法拿到响应内容。
6. RxJava CallAdapter的潜在冲突
你的Retrofit配置了RxJava2CallAdapterFactory,但接口定义的是Call<TokenInfo>(而非Observable)。虽然Retrofit支持同时使用Call和RxJava类型,但理论上可能存在兼容性问题。你可以暂时去掉addCallAdapterFactory(RxJava2CallAdapterFactory.create()),看看问题是否解决。如果之后需要用RxJava,再重新配置即可。
内容的提问来源于stack exchange,提问作者Bartosz20188

