Retrofit2返回结果无法创建ApiResponse.Success实例问题
问题解决:Retrofit解析密封类ApiResponse时Success状态抛出异常
问题场景
采用MVVM+Repository模式开发,使用泛型密封类ApiResponse作为客户端统一响应封装。调用Retrofit2登录接口后,创建ApiResponse.Success实例时持续抛出异常,其余状态(Loading/Error/ExceptionError)均正常工作。
异常信息:
无法创建com.example.ApiResponse类的实例。注册InstanceCreator或TypeAdapter,或者添加无参构造函数可能解决此问题。
错误原因
- 密封类与Gson解析冲突:
ApiResponse是密封类,Gson默认无法识别并正确实例化其子类。Retrofit通过Gson解析响应时,会尝试直接实例化ApiResponse父类,但父类是密封类且无无参构造函数,导致实例化失败。 - 职责混淆:将客户端状态封装类
ApiResponse直接作为Retrofit的响应类型,而ApiResponse包含客户端特有的状态(如Loading、ExceptionError),这些并非服务器返回的结构,Gson无法解析非服务器定义的字段。
解决方案
最清晰的解决方式是将服务器响应实体与客户端状态封装类分离:
- 定义专门的服务器响应实体类,对应服务器返回的字段结构;
- Retrofit返回该实体类的响应,在Repository层手动转换为
ApiResponse的子类实例。
代码修改示例
1. 创建服务器响应实体类
// 对应服务器返回的原始响应结构 data class ServerResponse<T>( val code: Int? = null, val status: String? = null, val message: String? = null, val data: T? = null, val errors: Map<String, ArrayList<String>>? = null )
2. 修改AuthService接口
将Retrofit的返回类型改为服务器响应实体:
interface AuthService { @POST("accounts/login") suspend fun getTokenPair(@Body userLoginCred: UserLoginCred): Response<ServerResponse<UserTokenPair>> }
3. 调整AuthRepository逻辑
在Repository层解析服务器响应后,手动创建ApiResponse子类实例:
if (NetworkUtil.isInternetAvailable(context)!!) { try { val response = authService.getTokenPair(userLoginCred) if (response.isSuccessful && response.body() != null) { val serverResponse = response.body()!! _userTokenPairResponse.postValue(ApiResponse.Success(serverResponse.data)) } else if (response.errorBody() != null) { // 使用TypeToken避免类型擦除问题 val errorMap = Gson().fromJson( response.errorBody()!!.string(), object : TypeToken<Map<String, ArrayList<String>>>() {}.type ) _userTokenPairResponse.postValue(ApiResponse.Error(errorMap)) } else { _userTokenPairResponse.postValue(ApiResponse.Loading()) } } catch (e: Exception) { _userTokenPairResponse.postValue(ApiResponse.ExceptionError(e.message)) } } else { _userTokenPairResponse.postValue(ApiResponse.ExceptionError("设备未连接网络。")) }
替代方案(快速修复)
若不想分离实体类,可给ApiResponse的所有子类添加无参构造函数,并确保构造参数带默认值:
sealed class ApiResponse<T> ( val code: Int? = null, val status: String? = null, val message: String? = null, val data: T? = null, val errors: Map<String, ArrayList<String>>? = null, val exceptionMessage: String? = null, ) { class Loading<T>: ApiResponse<T>() // 添加默认值,允许无参实例化 class Success<T> (data: T? = null): ApiResponse<T> (data = data) class Error<T> (errors: Map<String, ArrayList<String>>? = null): ApiResponse<T> (errors = errors) class ExceptionError<T> (exceptionMessage: String? = null): ApiResponse<T> (exceptionMessage = exceptionMessage) }
但此方法仅解决实例化问题,仍存在客户端状态与服务器响应职责混淆的问题,不推荐长期使用。
内容的提问来源于stack exchange,提问作者VikramChholak
相关产品推荐
相关产品推荐

