引言

网络请求是现代应用的核心功能之一。Flutter提供了多种方式来处理网络请求,从原生的http包到功能强大的dio库。本文将深入探讨Flutter网络请求的最佳实践,帮助你构建健壮的网络层。

一、网络请求基础

1.1 选择合适的库

特点 适用场景
http 官方包,轻量级 简单请求
dio 功能强大,支持拦截器 复杂场景
retrofit 类型安全,代码生成 大型项目

1.2 基本HTTP请求

import 'package:http/http.dart' as http;

Future<void> fetchData() async {
  final response = await http.get(
    Uri.parse('https://api.example.com/data'),
    headers: {'Authorization': 'Bearer token'},
  );
  
  if (response.statusCode == 200) {
    print(response.body);
  } else {
    throw Exception('请求失败');
  }
}

1.3 POST请求

Future<void> postData() async {
  final response = await http.post(
    Uri.parse('https://api.example.com/data'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({'name': 'John', 'age': 30}),
  );
}

二、dio库详解

2.1 安装与配置

dependencies:
  dio: ^5.0.0
import 'package:dio/dio.dart';

final dio = Dio();

2.2 基本配置

final dio = Dio(
  BaseOptions(
    baseUrl: 'https://api.example.com',
    connectTimeout: const Duration(seconds: 5),
    receiveTimeout: const Duration(seconds: 3),
    headers: {
      'Content-Type': 'application/json',
    },
  ),
);

2.3 GET请求

Future<User> getUser(String id) async {
  try {
    final response = await dio.get('/users/$id');
    return User.fromJson(response.data);
  } catch (e) {
    throw Exception('获取用户失败: $e');
  }
}

2.4 POST请求

Future<User> createUser(User user) async {
  final response = await dio.post(
    '/users',
    data: user.toJson(),
  );
  return User.fromJson(response.data);
}

三、拦截器

3.1 请求拦截器

dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    options.headers['Authorization'] = 'Bearer ${AuthService.token}';
    return handler.next(options);
  },
));

3.2 响应拦截器

dio.interceptors.add(InterceptorsWrapper(
  onResponse: (response, handler) {
    if (response.statusCode == 401) {
      AuthService.refreshToken();
    }
    return handler.next(response);
  },
));

3.3 错误拦截器

dio.interceptors.add(InterceptorsWrapper(
  onError: (error, handler) {
    if (error.response?.statusCode == 500) {
      showError('服务器错误');
    }
    return handler.next(error);
  },
));

3.4 日志拦截器

dio.interceptors.add(LogInterceptor(
  requestBody: true,
  responseBody: true,
));

四、错误处理

4.1 统一错误处理

class ApiException implements Exception {
  final String message;
  final int? statusCode;
  
  ApiException(this.message, {this.statusCode});
  
  @override
  String toString() => 'ApiException: $message';
}

Future<T> safeApiCall<T>(Future<T> Function() apiCall) async {
  try {
    return await apiCall();
  } on DioError catch (e) {
    throw ApiException(
      e.response?.data['message'] ?? '网络请求失败',
      statusCode: e.response?.statusCode,
    );
  } catch (e) {
    throw ApiException(e.toString());
  }
}

4.2 重试机制

dio.interceptors.add(RetryInterceptor(
  retries: 3,
  retryDelay: const Duration(seconds: 1),
  retryEvaluator: (error) => error.response?.statusCode == 500,
));

五、请求取消

5.1 取消单个请求

CancelToken cancelToken = CancelToken();

dio.get('/data', cancelToken: cancelToken);

// 取消请求
cancelToken.cancel('请求已取消');

5.2 取消多个请求

final cancelToken = CancelToken();

// 多个请求使用同一个cancelToken
dio.get('/data1', cancelToken: cancelToken);
dio.get('/data2', cancelToken: cancelToken);

// 取消所有请求
cancelToken.cancel('全部取消');

六、文件上传下载

6.1 文件上传

Future<void> uploadFile(File file) async {
  final formData = FormData.fromMap({
    'file': await MultipartFile.fromFile(file.path),
    'name': 'test.jpg',
  });
  
  await dio.post('/upload', data: formData);
}

6.2 文件下载

Future<void> downloadFile(String url, String savePath) async {
  await dio.download(
    url,
    savePath,
    onReceiveProgress: (received, total) {
      final progress = (received / total * 100).toStringAsFixed(0);
      print('下载进度: $progress%');
    },
  );
}

七、实战案例:API服务

7.1 创建API服务类

class ApiService {
  final Dio _dio;
  
  ApiService(this._dio);
  
  Future<User> getUser(String id) async {
    final response = await _dio.get('/users/$id');
    return User.fromJson(response.data);
  }
  
  Future<List<User>> getUsers() async {
    final response = await _dio.get('/users');
    return (response.data as List).map((e) => User.fromJson(e)).toList();
  }
  
  Future<User> createUser(User user) async {
    final response = await _dio.post('/users', data: user.toJson());
    return User.fromJson(response.data);
  }
  
  Future<void> updateUser(String id, User user) async {
    await _dio.put('/users/$id', data: user.toJson());
  }
  
  Future<void> deleteUser(String id) async {
    await _dio.delete('/users/$id');
  }
}

7.2 依赖注入

// 使用GetIt
final getIt = GetIt.instance;

void setupLocator() {
  getIt.registerSingleton<Dio>(Dio(
    BaseOptions(baseUrl: 'https://api.example.com'),
  ));
  
  getIt.registerSingleton<ApiService>(ApiService(getIt()));
}

7.3 配合状态管理

class UserViewModel extends ChangeNotifier {
  final ApiService _apiService;
  
  User? _user;
  bool _isLoading = false;
  String? _error;
  
  UserViewModel(this._apiService);
  
  Future<void> loadUser(String id) async {
    _isLoading = true;
    _error = null;
    notifyListeners();
    
    try {
      _user = await _apiService.getUser(id);
    } catch (e) {
      _error = e.toString();
    }
    
    _isLoading = false;
    notifyListeners();
  }
}

八、性能优化

8.1 请求缓存

final cache = <String, dynamic>{};

Future<T> cachedRequest<T>(String key, Future<T> Function() request) async {
  if (cache.containsKey(key)) {
    return cache[key] as T;
  }
  
  final result = await request();
  cache[key] = result;
  return result;
}

8.2 请求合并

Future<List<User>> fetchUsers(List<String> ids) async {
  final futures = ids.map((id) => _apiService.getUser(id));
  return await Future.wait(futures);
}

8.3 连接池

final dio = Dio(BaseOptions(
  connectTimeout: const Duration(seconds: 5),
  receiveTimeout: const Duration(seconds: 3),
  maxConnectionsPerHost: 5,
));

九、最佳实践

9.1 环境配置

enum Environment { development, staging, production }

class Config {
  static String baseUrl(Environment env) {
    switch (env) {
      case Environment.development:
        return 'https://dev.api.example.com';
      case Environment.staging:
        return 'https://staging.api.example.com';
      case Environment.production:
        return 'https://api.example.com';
    }
  }
}

9.2 日志记录

dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    print('请求: ${options.method} ${options.path}');
    return handler.next(options);
  },
));

9.3 测试网络层

void main() {
  test('getUser returns user', () async {
    final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
    final service = ApiService(dio);
    
    final user = await service.getUser('1');
    expect(user.id, '1');
  });
}

十、总结

Flutter网络请求是应用开发的核心技能,通过合理使用dio库和良好的架构设计,可以构建健壮的网络层。

关键要点:

  • 使用dio进行网络请求
  • 使用拦截器处理请求/响应
  • 实现统一错误处理
  • 支持请求取消和重试
  • 考虑性能优化和缓存

掌握网络请求,将使你的Flutter应用更加稳定和高效。

更多推荐