在这里插入图片描述
在这里插入图片描述

概述

在Flutter应用开发中,网络请求是不可或缺的一部分。为了统一处理HTTP请求的认证、日志、错误处理等逻辑,使用拦截器是一个很好的实践。Dio库提供了强大的拦截器机制,可以在请求发送前、响应返回后、错误发生时进行统一处理。

本文将详细介绍网络拦截器的核心概念、使用方法、高级特性以及在鸿蒙平台上的实现细节。

核心概念

什么是网络拦截器

网络拦截器是一种中间件机制,允许在HTTP请求的各个阶段插入自定义逻辑:

阶段 拦截器类型 作用
请求发送前 请求拦截器 添加认证信息、日志记录、参数加密
响应返回后 响应拦截器 数据解析、日志记录、错误处理
错误发生时 错误拦截器 错误重试、错误转换、错误上报

拦截器的优势

  • 代码复用:将通用逻辑抽离到拦截器中
  • 统一处理:集中处理认证、日志、错误等逻辑
  • 易于维护:修改逻辑只需改动拦截器
  • 解耦合:业务代码与网络层解耦

拦截器链

多个拦截器可以组成一个拦截器链,按照顺序执行:

请求 → 拦截器1 → 拦截器2 → ... → 服务器
响应 → 拦截器n → 拦截器n-1 → ... → 业务代码

基本使用

安装依赖

dependencies:
  dio: ^5.4.0

创建请求拦截器

import 'package:dio/dio.dart';

class AuthInterceptor extends Interceptor {
  
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    options.headers['Authorization'] = 'Bearer token';
    super.onRequest(options, handler);
  }
}

创建响应拦截器

class LoggingInterceptor extends Interceptor {
  
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print('RESPONSE: ${response.data}');
    super.onResponse(response, handler);
  }
}

添加拦截器到Dio

final dio = Dio();
dio.interceptors.add(AuthInterceptor());
dio.interceptors.add(LoggingInterceptor());

核心代码示例

代码示例1:认证拦截器

import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';

class AuthInterceptor extends Interceptor {
  
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    final prefs = await SharedPreferences.getInstance();
    final token = prefs.getString('access_token');
    if (token != null) {
      options.headers['Authorization'] = 'Bearer $token';
    }
    options.headers['Content-Type'] = 'application/json';
    super.onRequest(options, handler);
  }

  
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    if (response.statusCode == 401) {
    }
    super.onResponse(response, handler);
  }

  
  void onError(DioException err, ErrorInterceptorHandler handler) {
    if (err.response?.statusCode == 401) {
    }
    super.onError(err, handler);
  }
}

代码说明

  1. 请求拦截:在请求发送前添加认证token和Content-Type
  2. 响应拦截:处理401状态码(token过期)
  3. 错误拦截:处理请求错误时的401状态码
  4. 异步操作:使用SharedPreferences获取token

代码示例2:日志拦截器

class LoggingInterceptor extends Interceptor {
  
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    print('REQUEST[${options.method}] => PATH: ${options.path}');
    print('HEADERS: ${options.headers}');
    if (options.data != null) {
      print('BODY: ${options.data}');
    }
    super.onRequest(options, handler);
  }

  
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print('RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
    print('DATA: ${response.data}');
    super.onResponse(response, handler);
  }

  
  void onError(DioException err, ErrorInterceptorHandler handler) {
    print('ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}');
    print('ERROR: $err');
    super.onError(err, handler);
  }
}

代码说明

  1. 请求日志:记录请求方法、路径、头信息和请求体
  2. 响应日志:记录响应状态码、路径和响应数据
  3. 错误日志:记录错误状态码、路径和错误信息
  4. 调试方便:帮助开发者调试网络请求

代码示例3:API客户端封装

import 'package:dio/dio.dart';

class ApiClient {
  static late Dio _dio;

  static void init() {
    _dio = Dio(BaseOptions(
      baseUrl: 'https://api.example.com',
      connectTimeout: const Duration(seconds: 10),
      receiveTimeout: const Duration(seconds: 10),
    ));

    _dio.interceptors.add(AuthInterceptor());
    _dio.interceptors.add(LoggingInterceptor());
    _dio.interceptors.add(RetryInterceptor(
      dio: _dio,
      options: const RetryOptions(
        retries: 3,
        retryInterval: Duration(seconds: 1),
        retryEvaluator: _defaultRetryEvaluator,
      ),
    ));
  }

  static bool _defaultRetryEvaluator(DioException error) {
    return error.type == DioExceptionType.connectionTimeout ||
        error.type == DioExceptionType.receiveTimeout ||
        error.type == DioExceptionType.sendTimeout;
  }

  static Dio get instance => _dio;

  static Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) async {
    return await _dio.get(path, queryParameters: queryParameters);
  }

  static Future<Response> post(String path, {dynamic data}) async {
    return await _dio.post(path, data: data);
  }

  static Future<Response> put(String path, {dynamic data}) async {
    return await _dio.put(path, data: data);
  }

  static Future<Response> delete(String path) async {
    return await _dio.delete(path);
  }
}

代码说明

  1. 单例模式:使用静态实例避免重复创建
  2. 基础配置:设置baseUrl、超时时间
  3. 拦截器链:添加认证、日志、重试拦截器
  4. 请求方法:封装GET、POST、PUT、DELETE方法
  5. 重试逻辑:对超时错误自动重试3次

代码示例4:错误处理拦截器

class ErrorInterceptor extends Interceptor {
  
  void onError(DioException err, ErrorInterceptorHandler handler) {
    final errorMessage = _getErrorMessage(err);
    print('Error: $errorMessage');
    
    final customError = ApiError(
      code: err.response?.statusCode ?? -1,
      message: errorMessage,
      originalError: err,
    );
    
    handler.reject(DioException(
      requestOptions: err.requestOptions,
      error: customError,
      type: err.type,
      response: err.response,
    ));
  }

  String _getErrorMessage(DioException err) {
    switch (err.type) {
      case DioExceptionType.connectionTimeout:
        return '连接超时';
      case DioExceptionType.sendTimeout:
        return '发送超时';
      case DioExceptionType.receiveTimeout:
        return '接收超时';
      case DioExceptionType.badResponse:
        return '服务器错误 ${err.response?.statusCode}';
      case DioExceptionType.cancel:
        return '请求已取消';
      case DioExceptionType.connectionError:
        return '网络连接错误';
      default:
        return '未知错误';
    }
  }
}

class ApiError {
  final int code;
  final String message;
  final DioException originalError;

  ApiError({
    required this.code,
    required this.message,
    required this.originalError,
  });
}

代码说明

  1. 错误转换:将DioException转换为自定义ApiError
  2. 错误消息:根据错误类型提供友好的错误消息
  3. 错误信息:保存原始错误信息便于调试
  4. 错误码:提取HTTP状态码

高级特性

1. 请求重试

dio.interceptors.add(RetryInterceptor(
  dio: dio,
  options: const RetryOptions(
    retries: 3,
    retryInterval: Duration(seconds: 1),
    retryEvaluator: (error) => error.type == DioExceptionType.connectionTimeout,
  ),
));

2. 请求缓存

class CacheInterceptor extends Interceptor {
  final Map<String, Response> _cache = {};

  
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    if (options.method == 'GET') {
      final cachedResponse = _cache[options.path];
      if (cachedResponse != null) {
        handler.resolve(cachedResponse);
        return;
      }
    }
    handler.next(options);
  }

  
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    if (response.requestOptions.method == 'GET') {
      _cache[response.requestOptions.path] = response;
    }
    handler.next(response);
  }
}

3. 请求限流

class RateLimitInterceptor extends Interceptor {
  int _requestCount = 0;
  DateTime? _lastResetTime;

  
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    final now = DateTime.now();
    if (_lastResetTime == null || now.difference(_lastResetTime!) > const Duration(seconds: 1)) {
      _requestCount = 0;
      _lastResetTime = now;
    }

    if (_requestCount >= 10) {
      handler.reject(DioException(
        requestOptions: options,
        error: 'Rate limit exceeded',
      ));
    } else {
      _requestCount++;
      handler.next(options);
    }
  }
}

4. 参数加密

class EncryptionInterceptor extends Interceptor {
  
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    if (options.data != null) {
      options.data = _encrypt(options.data);
    }
    handler.next(options);
  }

  dynamic _encrypt(dynamic data) {
    return data;
  }
}

在鸿蒙平台的实现

鸿蒙平台适配

鸿蒙平台对Flutter网络请求的支持与Android类似,因为鸿蒙兼容Android应用。需要注意以下几点:

  1. 网络权限:需要在module.json5中声明网络权限
  2. HTTPS支持:鸿蒙平台支持HTTPS请求
  3. 网络代理:可以配置网络代理进行调试

鸿蒙平台权限配置

module.json5中配置网络权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "需要网络权限发送HTTP请求",
        "usedScene": {
          "ability": [
            {
              "name": "MainAbility",
              "when": "inuse"
            }
          ],
          "permissionFlags": [
            "grantMode:system_grant"
          ]
        }
      }
    ]
  }
}

鸿蒙平台注意事项

  1. 网络安全:鸿蒙平台要求使用HTTPS连接
  2. 证书验证:需要正确配置SSL证书
  3. 代理配置:调试时可以配置网络代理

性能对比

操作 无拦截器 有拦截器
请求时间 正常 略有增加(毫秒级)
代码复杂度 高(重复逻辑) 低(统一处理)
可维护性

最佳实践

1. 封装API客户端

将所有网络请求封装在API客户端中:

class ApiClient {
  static Future<User> getUser(int id) async {
    final response = await _dio.get('/users/$id');
    return User.fromJson(response.data);
  }

  static Future<List<User>> getUsers() async {
    final response = await _dio.get('/users');
    return (response.data as List).map((e) => User.fromJson(e)).toList();
  }
}

2. 统一错误处理

在错误拦截器中统一处理错误:

class ErrorInterceptor extends Interceptor {
  
  void onError(DioException err, ErrorInterceptorHandler handler) {
    final error = _handleError(err);
    handler.reject(error);
  }

  DioException _handleError(DioException err) {
    // 统一错误处理逻辑
    return err;
  }
}

3. 合理使用拦截器

根据需求选择合适的拦截器:

dio.interceptors.add(AuthInterceptor());
dio.interceptors.add(LoggingInterceptor());
dio.interceptors.add(ErrorInterceptor());

4. 避免重复拦截

不要在多个地方添加相同的拦截器:

// 错误示例
dio.interceptors.add(AuthInterceptor());
dio.interceptors.add(AuthInterceptor()); // 重复添加

5. 测试拦截器

编写测试用例验证拦截器功能:

void main() {
  test('AuthInterceptor adds token', () async {
    final dio = Dio();
    dio.interceptors.add(AuthInterceptor());
    
    // 测试拦截器是否正确添加token
  });
}

常见问题

Q1: 拦截器不生效怎么办?

A:检查以下几点:

  • 是否正确添加了拦截器到Dio实例
  • 拦截器的顺序是否正确
  • 是否调用了handler.next()或handler.resolve()

Q2: 如何在拦截器中获取上下文?

A:可以通过RequestOptions的extra参数传递上下文:

dio.get('/users', options: Options(extra: {'context': context}));

Q3: 拦截器可以异步操作吗?

A:可以,请求拦截器支持异步操作:


void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
  final token = await getToken();
  options.headers['Authorization'] = token;
  handler.next(options);
}

Q4: 如何取消请求?

A:使用CancelToken取消请求:

final cancelToken = CancelToken();
dio.get('/users', cancelToken: cancelToken);
cancelToken.cancel('Request cancelled');

Q5: 如何处理HTTPS证书问题?

A:可以通过配置Dio的HttpClientAdapter来处理:

dio.httpClientAdapter = Http2Adapter(
  ConnectionManager(
    idleTimeout: const Duration(seconds: 10),
  ),
);

总结

网络拦截器是Flutter应用中处理HTTP请求的重要机制,通过Dio库的拦截器可以实现认证、日志、错误处理等统一逻辑。在鸿蒙平台上,网络拦截器的实现与Android类似,但需要注意网络权限和HTTPS配置。

选择合适的拦截器需要根据应用需求来决定:

拦截器 适用场景 特点
认证拦截器 需要登录的应用 自动添加认证信息
日志拦截器 开发调试 记录请求和响应信息
错误拦截器 所有应用 统一错误处理
重试拦截器 不稳定网络 自动重试失败请求

希望本文能帮助你更好地理解和使用网络拦截器在Flutter应用中处理HTTP请求。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐