You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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必须以/结尾,否则初始化阶段会抛出参数非法异常。
修复方案

按以下步骤修改代码即可解决问题:

  1. 修改APIService接口定义,给请求方法添加suspend修饰符,适配协程调用模式:
interface APIService {
    @POST("api/Auth/SignIn")
    suspend fun requestToken(@Body requestBody: RequestBody): Response<Session>
}
  1. 修改数据类定义,将可能返回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()
}
  1. 调整网络请求封装逻辑,将请求改为协程挂起函数,移除无意义的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()}")
        }
    }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 13:09:26