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

Flutter+Retrofit架构下FormatException无法定位问题,如何捕获原始响应排查?

Flutter+Retrofit架构下FormatException无法定位问题,如何捕获原始响应排查?

我之前也踩过一模一样的坑——Retrofit解析JSON时炸FormatException,但完全找不到是哪个API出的问题,结合你的MVVM场景,给你几个亲测好用的解决办法:

一、给Dio加全局拦截器,抓所有请求的原始响应

Retrofit本质是基于Dio封装的,所以给Dio实例加个拦截器,就能在Retrofit解析JSON前,拿到每一个请求的原始响应数据,这是定位问题最直接的方式。

直接抄这个拦截器代码就行:

class ApiLoggingInterceptor extends InterceptorsWrapper {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    print('--> ${options.method} ${options.uri}');
    super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print('<-- ${response.statusCode} ${response.requestOptions.uri}');
    print('原始响应体: ${response.data}'); // 重点!这里是Retrofit解析前的原始数据
    super.onResponse(response, handler);
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) {
    print('<-- 错误请求: ${err.requestOptions.uri}');
    print('错误原因: ${err.message}');
    if (err.response != null) {
      print('响应状态码: ${err.response?.statusCode}');
      print('错误响应体: ${err.response?.data}');
      // 专门揪出FormatException
      if (err.error is FormatException) {
        print('🔥 找到问题了!这个API返回了无效JSON: ${err.requestOptions.uri}');
        print('无效JSON内容: ${err.response?.data}');
      }
    }
    super.onError(err, handler);
  }
}

然后把这个拦截器挂到你的Dio实例上:

final dio = Dio();
dio.interceptors.add(ApiLoggingInterceptor());
// 再把这个dio传给Retrofit的DataSource工厂
final notificationDatasource = NotificationRemoteDatasource(dio);
final scheduleDatasource = ScheduleRemoteDatasource(dio);

这样不管是启动时自动调用的API,还是用户操作触发的,所有请求的原始响应都会打印在控制台,一旦炸FormatException,拦截器会直接给你标红哪个API出的问题,连无效JSON的内容都能看到。

二、优化全局错误处理,精准捕获Retrofit的解析错误

你已经加了FlutterError.onError和PlatformDispatcher的错误处理,但还要专门处理DioError——因为Retrofit的JSON解析错误会被包装成DioError,类型是DioErrorType.response,里面的error字段就是原始的FormatException。

修改你的ErrorUtils,加上对Dio错误的判断:

class ErrorUtils {
  static void onErrorsAndExceptions(Object error, StackTrace? stackTrace) {
    if (error is DioError) {
      final dioError = error;
      print('来自API的Dio错误: ${dioError.requestOptions.uri}');
      if (dioError.response != null) {
        print('响应状态: ${dioError.response?.statusCode}');
        print('原始响应内容: ${dioError.response?.data}');
      }
      // 精准定位解析错误
      if (dioError.error is FormatException) {
        print('⚠️ JSON解析失败:');
        print('出问题的API: ${dioError.requestOptions.uri}');
        print('无效JSON: ${dioError.response?.data}');
        print('调用栈: $stackTrace');
      }
    } else {
      // 处理其他类型的错误
      print('错误内容: $error');
      print('调用栈: $stackTrace');
    }
  }
}

这样即使全局错误触发,也能直接定位到出问题的API。

三、针对void返回的API,避免不必要的JSON解析

你有几个返回Future<void>的API(比如createMeal、reschedule),如果后端返回空字符串或非JSON内容,Retrofit还是会硬着头皮尝试解析成void对应的JSON,这也会炸FormatException。

解决办法是把这些API的返回类型改成Future<Response<dynamic>>,跳过Retrofit的自动解析:

// 修改NotificationRemoteDatasource的reschedule方法
@POST(NotificationApiConstants.reschedule)
Future<Response<dynamic>> reschedule({
  @Path('notificationID') required String notificationID,
  @Body() required RescheduleModel body
});

// 修改ScheduleRemoteDatasource的createMeal方法
@POST(ScheduleApiConstants.createMeal)
@MultiPart()
Future<Response<dynamic>> createMeal({
  @Part(name: 'meal_type') required String mealType,
  @Part(name: 'description') required String description,
  @Part(name: 'image') required File image,
});

然后在ViewModel里调用时,直接判断状态码就行:

final response = await _notificationDatasource.reschedule(notificationID: id, body: body);
if (response.statusCode == 200 || response.statusCode == 204) {
  // 处理成功逻辑
} else {
  // 处理错误
}

这样就避免了Retrofit对空响应/非JSON响应的强制解析,减少无意义的FormatException。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 13:34:30