Flutter网络请求完全指南
Flutter网络请求完全指南
引言
在现代移动应用开发中,网络请求是不可或缺的一部分,它允许应用与服务器进行数据交互,实现各种功能。Flutter提供了多种网络请求方案,从基础的http包到功能强大的dio库,都可以帮助你实现各种网络交互需求。本文将深入探讨Flutter网络请求的核心概念、实现方法和最佳实践,帮助你构建更加可靠、高效的网络应用。
网络请求库
1. http 包
Flutter官方推荐的基础网络请求库:
// 安装依赖:http: ^0.13.0 import 'package:http/http.dart' as http; import 'dart:convert'; // GET请求 Future<void> fetchData() async { final response = await http.get(Uri.parse('https://api.example.com/data')); if (response.statusCode == 200) { // 成功 final data = jsonDecode(response.body); print(data); } else { // 失败 print('请求失败: ${response.statusCode}'); } } // 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 Doe', 'email': 'john@example.com', }), ); if (response.statusCode == 201) { // 成功 final data = jsonDecode(response.body); print(data); } else { // 失败 print('请求失败: ${response.statusCode}'); } }2. dio 包
功能更强大的网络请求库,支持拦截器、取消请求等高级功能:
// 安装依赖:dio: ^4.0.0 import 'package:dio/dio.dart'; // 创建dio实例 final dio = Dio(); // GET请求 Future<void> fetchData() async { try { final response = await dio.get('https://api.example.com/data'); print(response.data); } catch (e) { print('请求失败: $e'); } } // POST请求 Future<void> postData() async { try { final response = await dio.post('https://api.example.com/data', data: { 'name': 'John Doe', 'email': 'john@example.com', }, ); print(response.data); } catch (e) { print('请求失败: $e'); } }高级网络请求技巧
1. 拦截器
使用拦截器处理请求和响应:
// 添加拦截器 dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { // 在发送请求之前做一些处理 print('请求: ${options.uri}'); // 添加认证头 options.headers['Authorization'] = 'Bearer token'; return handler.next(options); }, onResponse: (response, handler) { // 在收到响应后做一些处理 print('响应: ${response.statusCode}'); return handler.next(response); }, onError: (DioError e, handler) { // 处理错误 print('错误: ${e.message}'); return handler.next(e); }, ));2. 请求取消
取消正在进行的请求:
// 创建取消令牌 final cancelToken = CancelToken(); // 发起请求 Future<void> fetchData() async { try { final response = await dio.get('https://api.example.com/data', cancelToken: cancelToken, ); print(response.data); } catch (e) { if (CancelToken.isCancel(e)) { print('请求被取消'); } else { print('请求失败: $e'); } } } // 取消请求 void cancelRequest() { cancelToken.cancel('主动取消请求'); }3. 超时设置
设置请求超时:
// 全局设置 dio.options.connectTimeout = Duration(seconds: 5); dio.options.receiveTimeout = Duration(seconds: 3); // 单次请求设置 final response = await dio.get('https://api.example.com/data', options: Options( connectTimeout: Duration(seconds: 5), receiveTimeout: Duration(seconds: 3), ), );4. 重试机制
实现请求重试:
// 添加重试拦截器 dio.interceptors.add(RetryInterceptor( dio: dio, retries: 3, // 重试次数 retryDelays: [ // 重试间隔 Duration(seconds: 1), Duration(seconds: 2), Duration(seconds: 3), ], retryEvaluator: (error) { // 只有特定错误才重试 return error.type == DioErrorType.connectTimeout || error.type == DioErrorType.receiveTimeout; }, )); // RetryInterceptor实现 class RetryInterceptor extends Interceptor { final Dio dio; final int retries; final List<Duration> retryDelays; final RetryEvaluator retryEvaluator; RetryInterceptor({ required this.dio, this.retries = 3, required this.retryDelays, required this.retryEvaluator, }); @override void onError(DioError err, ErrorInterceptorHandler handler) async { if (err.requestOptions.disableRetry ?? false) { return handler.next(err); } final retryCount = err.requestOptions.extra['retryCount'] ?? 0; if (retryCount < retries && retryEvaluator(err)) { err.requestOptions.extra['retryCount'] = retryCount + 1; await Future.delayed(retryDelays[min(retryCount, retryDelays.length - 1)]); try { final response = await dio.fetch(err.requestOptions); return handler.resolve(response); } catch (e) { return handler.next(err); } } return handler.next(err); } } typedef RetryEvaluator = bool Function(DioError error);网络请求状态管理
1. 基础状态管理
使用FutureBuilder处理网络请求状态:
class DataScreen extends StatelessWidget { Future<dynamic> fetchData() async { final response = await http.get(Uri.parse('https://api.example.com/data')); if (response.statusCode == 200) { return jsonDecode(response.body); } else { throw Exception('Failed to load data'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('数据展示')), body: FutureBuilder( future: fetchData(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return Center(child: Text('错误: ${snapshot.error}')); } else if (snapshot.hasData) { final data = snapshot.data; return ListView.builder( itemCount: data.length, itemBuilder: (context, index) { return ListTile( title: Text(data[index]['name']), subtitle: Text(data[index]['email']), ); }, ); } else { return Center(child: Text('无数据')); } }, ), ); } }2. 使用Provider管理网络状态
// 创建数据模型 class DataModel extends ChangeNotifier { List<dynamic> _data = []; bool _isLoading = false; String? _error; List<dynamic> get data => _data; bool get isLoading => _isLoading; String? get error => _error; Future<void> fetchData() async { _isLoading = true; _error = null; notifyListeners(); try { final response = await http.get(Uri.parse('https://api.example.com/data')); if (response.statusCode == 200) { _data = jsonDecode(response.body); } else { _error = '请求失败: ${response.statusCode}'; } } catch (e) { _error = '请求失败: $e'; } finally { _isLoading = false; notifyListeners(); } } } // 使用 class DataScreen extends StatelessWidget { @override Widget build(BuildContext context) { final dataModel = Provider.of<DataModel>(context); return Scaffold( appBar: AppBar(title: Text('数据展示')), body: Column( children: [ ElevatedButton( onPressed: dataModel.fetchData, child: Text('获取数据'), ), if (dataModel.isLoading) Center(child: CircularProgressIndicator()), if (dataModel.error != null) Center(child: Text('错误: ${dataModel.error}')), if (!dataModel.isLoading && dataModel.error == null) Expanded( child: ListView.builder( itemCount: dataModel.data.length, itemBuilder: (context, index) { return ListTile( title: Text(dataModel.data[index]['name']), subtitle: Text(dataModel.data[index]['email']), ); }, ), ), ], ), ); } }实战应用
1. 用户认证
class AuthService { final dio = Dio(); AuthService() { // 配置基础URL dio.options.baseUrl = 'https://api.example.com'; // 添加拦截器 dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { // 从本地存储获取token final token = LocalStorage.getToken(); if (token != null) { options.headers['Authorization'] = 'Bearer $token'; } return handler.next(options); }, )); } // 登录 Future<Map<String, dynamic>> login(String email, String password) async { final response = await dio.post('/auth/login', data: { 'email': email, 'password': password, }, ); // 存储token LocalStorage.saveToken(response.data['token']); return response.data; } // 注册 Future<Map<String, dynamic>> register(String name, String email, String password) async { final response = await dio.post('/auth/register', data: { 'name': name, 'email': email, 'password': password, }, ); return response.data; } // 获取用户信息 Future<Map<String, dynamic>> getUserInfo() async { final response = await dio.get('/user/profile'); return response.data; } } // 本地存储 class LocalStorage { static const _tokenKey = 'auth_token'; static Future<void> saveToken(String token) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_tokenKey, token); } static Future<String?> getToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString(_tokenKey); } static Future<void> removeToken() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove(_tokenKey); } }2. 商品列表
class ProductService { final dio = Dio(); ProductService() { dio.options.baseUrl = 'https://api.example.com'; } // 获取商品列表 Future<List<Product>> getProducts({int page = 1, int limit = 10}) async { final response = await dio.get('/products', queryParameters: { 'page': page, 'limit': limit, }, ); return (response.data['data'] as List) .map((item) => Product.fromJson(item)) .toList(); } // 获取商品详情 Future<Product> getProductById(int id) async { final response = await dio.get('/products/$id'); return Product.fromJson(response.data); } // 搜索商品 Future<List<Product>> searchProducts(String keyword) async { final response = await dio.get('/products/search', queryParameters: { 'keyword': keyword, }, ); return (response.data['data'] as List) .map((item) => Product.fromJson(item)) .toList(); } } class Product { final int id; final String name; final double price; final String description; final String imageUrl; Product({ required this.id, required this.name, required this.price, required this.description, required this.imageUrl, }); factory Product.fromJson(Map<String, dynamic> json) { return Product( id: json['id'], name: json['name'], price: json['price'].toDouble(), description: json['description'], imageUrl: json['image_url'], ); } }3. 文件上传
Future<void> uploadFile(File file) async { final formData = FormData.fromMap({ 'file': await MultipartFile.fromFile(file.path, filename: 'upload.jpg'), 'description': '上传的文件', }); try { final response = await dio.post('https://api.example.com/upload', data: formData, onSendProgress: (int sent, int total) { print('上传进度: ${(sent / total * 100).toStringAsFixed(0)}%'); }, ); print('上传成功: ${response.data}'); } catch (e) { print('上传失败: $e'); } }4. 网络状态监测
import 'package:connectivity_plus/connectivity_plus.dart'; class NetworkService { final Connectivity _connectivity = Connectivity(); Stream<ConnectivityResult> get connectivityStream => _connectivity.onConnectivityChanged; Future<ConnectivityResult> checkConnectivity() async { return await _connectivity.checkConnectivity(); } Future<bool> isConnected() async { final result = await checkConnectivity(); return result != ConnectivityResult.none; } } // 使用 class HomeScreen extends StatefulWidget { @override _HomeScreenState createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { late StreamSubscription<ConnectivityResult> _subscription; ConnectivityResult _connectivityResult = ConnectivityResult.none; @override void initState() { super.initState(); _subscription = NetworkService().connectivityStream.listen((result) { setState(() { _connectivityResult = result; }); }); } @override void dispose() { _subscription.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('网络状态监测')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('当前网络状态: ${_connectivityResult.toString()}'), SizedBox(height: 20), ElevatedButton( onPressed: () async { final isConnected = await NetworkService().isConnected(); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(isConnected ? '网络连接正常' : '无网络连接')), ); }, child: Text('检查网络连接'), ), ], ), ), ); } }性能优化
- 使用缓存:缓存频繁访问的数据,减少网络请求
- 压缩数据:使用gzip压缩减少数据传输量
- 批量请求:合并多个请求,减少网络连接次数
- 图片优化:使用适当尺寸的图片,考虑使用WebP格式
- 后台请求:将非关键请求放在后台执行
- 网络状态监测:根据网络状态调整请求策略
- 重试机制:对临时网络错误进行重试
- 取消无用请求:如页面切换时取消未完成的请求
最佳实践
- 封装网络服务:创建专门的网络服务类,集中管理网络请求
- 统一错误处理:使用拦截器统一处理网络错误
- 使用模型类:将JSON数据转换为强类型的模型类
- 添加超时设置:避免请求无限等待
- 实现重试机制:提高请求成功率
- 使用安全连接:优先使用HTTPS
- 处理认证:安全存储和使用认证令牌
- 监控网络请求:记录和分析网络请求性能
- 测试网络请求:为网络请求编写单元测试
- 考虑离线模式:实现离线缓存和同步机制
总结
Flutter提供了多种网络请求方案,从基础的http包到功能强大的dio库,都可以帮助你实现各种网络交互需求。通过本文的介绍,你应该已经掌握了:
- 基本的网络请求实现方法
- 高级网络请求技巧,如拦截器、取消请求和重试机制
- 网络请求状态管理
- 实战应用,如用户认证、商品列表和文件上传
- 网络状态监测
- 性能优化和最佳实践
通过合理使用这些技术,你可以构建出更加可靠、高效的网络应用,为用户提供更好的体验。在实际项目中,要根据具体需求选择合适的网络请求方案,并结合状态管理库来管理网络请求状态,确保应用的稳定性和性能。
