Android中Retrofit2请求异常中断问题排查与修复求助
Retrofit2 请求无回调+NPE崩溃问题排查与修复方案
一、先定位 NullPointerException 根源
优先查看 Logcat 完整堆栈,定位空指针具体触发代码:
- 常见场景1:Retrofit 接口返回的
body()为空,但代码直接调用实体类方法(如user.getName()) - 常见场景2:DI 模块注入失败,导致 ViewModel 中 Repository/ApiService 实例为 null
- 常见场景3:UiState 中数据字段未初始化,Compose 界面直接访问空值
示例修复:若堆栈显示实体类字段访问空指针,需在获取响应后先判空:
val response = apiService.getProfile() if (response.isSuccessful) { val user = response.body() ?: run { _uiState.value = _uiState.value.copy(error = "数据解析失败:返回内容为空") return@launch } // 正常处理user数据 }
二、排查请求无回调的核心原因
1. 网络与环境问题
- 验证设备/模拟器网络连通性,确认可访问 API 域名
- 检查 AndroidManifest.xml 中
INTERNET权限是否被误删 - 排查是否开启代理/VPN,导致请求被拦截
2. Retrofit/OkHttp 配置变更
- 检查近期是否修改 OkHttpClient 拦截器:若新增拦截器未正确处理异常(如拦截器中抛出未捕获异常),会导致请求中断无回调
- 确认 ConverterFactory(Gson/Moshi 等)配置:若实体类字段与 API 返回不匹配(如缺失
@SerializedName注解),会导致解析失败,Retrofit 默认不会触发onFailure,需改用Response<T>包装返回值来捕获错误 - 强制开启请求日志:在 OkHttpClient 中添加
HttpLoggingInterceptor,设置Level.BODY,查看完整请求/响应内容,确认请求是否到达服务器、服务器返回状态码与响应体
// 示例:添加日志拦截器 OkHttpClient.Builder() .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .build()
3. 协程生命周期问题
- 确认 ViewModel 中发起请求使用
viewModelScope:若误用GlobalScope,会导致 ViewModel 销毁后请求仍在运行,无法触发回调更新 UI - 检查协程中是否调用阻塞方法:如
Thread.sleep()会挂起线程,导致回调无法及时执行
三、代码层面逐一校验
1. API 接口定义
改用 Response<T> 包装返回值,避免直接抛出异常:
interface ApiService { @GET("user/profile") suspend fun getProfile(): Response<User> }
2. DI 模块配置
以 Hilt 为例,确认注入逻辑无遗漏:
@Module @InstallIn(SingletonComponent::class) object NetworkModule { @Provides fun provideOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .build() } @Provides fun provideRetrofit(client: OkHttpClient): Retrofit { return Retrofit.Builder() .baseUrl("https://your-api-domain.com/") .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() } @Provides fun provideApiService(retrofit: Retrofit): ApiService { return retrofit.create(ApiService::class.java) } }
检查是否遗漏 @Inject/@Provides 注解,导致实例注入失败。
3. ViewModel 与 UiState 处理
确保状态更新逻辑覆盖所有分支,且处理空值:
data class ProfileUiState( val isLoading: Boolean = false, val profile: User? = null, val error: String? = null ) class ProfileViewModel @HiltViewModel constructor( private val apiService: ApiService ) : ViewModel() { private val _uiState = MutableStateFlow(ProfileUiState()) val uiState = _uiState.asStateFlow() fun fetchProfile() { viewModelScope.launch { _uiState.value = _uiState.value.copy(isLoading = true) try { val response = apiService.getProfile() _uiState.value = if (response.isSuccessful) { val user = response.body() if (user != null) { _uiState.value.copy(isLoading = false, profile = user) } else { _uiState.value.copy(isLoading = false, error = "数据为空") } } else { _uiState.value.copy(isLoading = false, error = "请求失败:${response.code()}") } } catch (e: Exception) { _uiState.value = _uiState.value.copy(isLoading = false, error = e.message ?: "未知错误") } } } }
4. Compose 界面空值处理
必须用 ?.let 安全访问 UiState 中的可选字段,避免 NPE:
@Composable fun ProfileScreen(viewModel: ProfileViewModel = viewModel()) { val uiState by viewModel.uiState.collectAsState() Column(modifier = Modifier.fillMaxSize().padding(16.dp)) { when { uiState.isLoading -> CircularProgressIndicator() uiState.error != null -> Text(text = uiState.error!!, color = Color.Red) uiState.profile != null -> Text(text = "用户名:${uiState.profile!!.name}") else -> Text(text = "点击按钮获取资料") } Button( onClick = { viewModel.fetchProfile() }, modifier = Modifier.topPadding(16.dp) ) { Text("获取用户资料") } } }
内容的提问来源于stack exchange,提问作者Mael Fosso
相关产品推荐
相关产品推荐

