Retrofit结合Kotlin序列化响应为Session类报异常的解决方法
问题原因
类继承特性和本次报错没有关联,Gson可以正常识别继承自父类的字段,序列化/反序列化不会受影响。本次报错核心是Retrofit接口定义不符合规范,另外代码中还存在两个会在主报错修复后触发的隐藏问题:
- 核心报错原因:Retrofit 2 接口方法默认仅支持返回
Call<T>类型的对象,如果需要直接返回Response<T>或者业务实体类,要么给方法添加suspend修饰符配合协程使用,要么引入对应CallAdapter依赖(比如RxJava、Guava适配器)并在构建Retrofit实例时注册。当前代码中requestToken方法既没有suspend修饰,返回值也不是Call<Session>类型,Retrofit找不到匹配的适配器处理该返回类型,就会抛出Unable to create call adapter的异常。 - 隐藏问题1:当前网络请求是同步执行逻辑,直接在主线程调用会触发
NetworkOnMainThreadException,导致应用崩溃。 - 隐藏问题2:接口返回的
imagen字段值为null,但BaseUser类中imagen被定义为非空String类型,Gson反序列化时会因为空值赋值给非空类型抛出异常。 - 额外配置问题:Retrofit要求传入的
baseUrl必须以/结尾,否则初始化阶段会抛出参数非法异常。
修复方案
按以下步骤修改代码即可解决问题:
- 修改
APIService接口定义,给请求方法添加suspend修饰符,适配协程调用模式:
interface APIService { @POST("api/Auth/SignIn") suspend fun requestToken(@Body requestBody: RequestBody): Response<Session> }
- 修改数据类定义,将可能返回null的字段声明为可空类型,避免反序列化崩溃:
open class BaseUser { var id: Int = 0 var correo: String = "" var nombre: String = "" var apellido: String = "" var direccion: String = "" var telefono: String = "" // 接口可能返回null,声明为可空类型 var imagen: String? = null } class Permiso { var id: Int = 0 var aplicacionId: Int = 0 var aplicacionName: String = "" var lectura: Boolean = false var escritura: Boolean = false } class Session: BaseUser() { var token: String = "" var permiso: List<Permiso> = emptyList() }
- 调整网络请求封装逻辑,将请求改为协程挂起函数,移除无意义的Error捕获(Error属于系统级错误,捕获后无法正常处理):
class Retro{ private fun getinstance(): APIService { val retrofit = Retrofit.Builder() // 注意:baseUrl必须以/结尾 .baseUrl("https://你的接口域名/") .addConverterFactory(GsonConverterFactory.create()) .build() return retrofit.create(APIService::class.java) } suspend fun token(payload: String): Result<Session> = runCatching { val requestBody = payload.toRequestBody("application/json".toMediaTypeOrNull()) val response = getinstance().requestToken(requestBody) if (response.isSuccessful) { response.body() ?: throw IllegalStateException("响应体为空") } else { throw IllegalStateException("请求失败,HTTP状态码:${response.code()}") } } }
- 修改ViewModel中的请求调用逻辑,切换到IO线程执行网络请求,避免阻塞主线程:
class LocalViewModel(private val retro: Retro = Retro()): ViewModel(){ private val _result = MutableStateFlow("") val result: StateFlow<String> = _result init { viewModelScope.launch { val jsonObject = JSONObject().apply { put("correo", "xxxxxx.com") put("password", "xxxxx") } Log.d("payload", jsonObject.toString()) // 切换到IO调度器执行网络请求 val requestResult = withContext(Dispatchers.IO) { retro.token(jsonObject.toString()) } requestResult.onSuccess { session -> _result.value = "登录成功,获取token:${session.token}" }.onFailure { error -> _result.value = "请求出错:${error.message}" } } } }
补充说明:如果不想使用协程,也可以选择把接口方法返回值改成
Call<Session>,调用时通过enqueue方法发起异步请求,不需要加suspend修饰符。
内容的提问来源于stack exchange,提问作者sofnomic cr
相关产品推荐
相关产品推荐

