Retrofit2调用API偶现Expected BEGIN_OBJECT but was STRING错误排查求助
排查Retrofit2 "Expected BEGIN_OBJECT but was STRING" 偶发错误的方案
遇到的错误:
error: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $
这个错误偶发,API调用成功后不再出现,且请求URL一致,错误触发时直接进入onFailure回调,无法查看响应内容。以下是具体排查和解决步骤:
1. 用OkHttp拦截器捕获所有响应内容
不管请求成功还是失败,都能拿到原始响应体,这是定位问题的关键。在构建Retrofit前添加OkHttpClient拦截器:
val gson = GsonBuilder().setLenient().create() // 构建带拦截器的OkHttpClient val okHttpClient = OkHttpClient.Builder() .addInterceptor { chain -> val request = chain.request() val response = chain.proceed(request) // 读取并打印响应内容 val responseBodyStr = response.body?.string() ?: "无响应内容" Log.d("API_Debug", "请求URL: ${request.url}\n响应状态码: ${response.code}\n响应内容: $responseBodyStr") // 重新构建响应体(因为string()会消耗原始body) val newResponseBody = responseBodyStr.toResponseBody(response.body?.contentType()) response.newBuilder().body(newResponseBody).build() } .build() // 构建Retrofit时关联该客户端 val retrofit = Retrofit.Builder() .baseUrl("BASE_URL") .client(okHttpClient) // 添加这一行 .addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson)) .build()
运行后,当错误再次出现时,查看API_Debug日志,就能看到服务端返回的具体字符串内容(可能是错误提示、HTML页面或其他非JSON格式)。
2. 临时修改接口返回类型,手动解析响应
把接口的返回类型从Call<AInfo>改为Call<ResponseBody>,这样可以在onResponse中直接获取原始响应,不受Gson转换失败的影响:
修改AService接口:
interface AService { @GET("end_point") fun getInfo( @Query("serviceKey") serviceKey : String, @Query("pageNo") pageNo : String, @Query("numOfRows") numOfRows : String, @Query("itemName") itemName : String, @Query("efcyQesitm") efcyQesitm : String, @Query("type") type: String = "json" ): Call<ResponseBody> // 改为ResponseBody }
修改调用逻辑:
val aCall = aService.getInfo(apiKey, "1","50",aName,"") aCall.enqueue(object : Callback<ResponseBody>{ override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) { val responseStr = response.body?.string() ?: "" Log.d("API_Response", "响应内容: $responseStr") // 尝试手动解析为AInfo try { val aInfo = gson.fromJson(responseStr, AInfo::class.java) val aList = aInfo.body?.items if (!aList.isNullOrEmpty()) { dataList.clear() dataList.addAll(aList.filterNotNull()) adapter.notifyDataSetChanged() } } catch (e: JsonSyntaxException) { // 解析失败,处理异常场景 Log.e("Parse_Error", "解析响应失败: $responseStr", e) // 比如提示用户重试 } } override fun onFailure(call: Call<ResponseBody>, t: Throwable) { Log.e("API_Request_Failure", "请求失败: ${t.message}", t) } })
这种方式能确保你拿到所有响应内容,即使服务端返回非JSON格式,也能在onResponse中处理。
3. 排查服务端偶发异常的可能
从错误特征来看,大概率是服务端偶尔返回非JSON响应,常见场景包括:
- 服务端限流/超时,返回错误提示字符串(如"请求过于频繁")
- 临时维护,返回HTML页面或维护提示
- 权限验证偶发失败,返回非JSON格式的错误信息
- 接口内部异常,返回堆栈信息或其他文本
通过第一步的拦截器日志,就能明确服务端返回的内容,进而针对性解决(比如联系后端修复,或在客户端添加异常处理逻辑)。
4. 优化客户端异常处理逻辑
在确认问题原因后,可以添加全局的异常处理:
- 如果服务端偶尔返回错误字符串,可在拦截器中判断响应内容,提前处理后再交给Gson解析
- 给
AInfo添加兼容逻辑,或者在解析时捕获JsonSyntaxException,并重试请求
内容的提问来源于stack exchange,提问作者soo
相关产品推荐
相关产品推荐

