news 2026/5/9 4:28:48

Flutter 网络请求最佳实践:构建可靠的异步应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter 网络请求最佳实践:构建可靠的异步应用

Flutter 网络请求最佳实践:构建可靠的异步应用

引言

在现代移动应用开发中,网络请求是不可或缺的一部分。Flutter 提供了多种方式来处理网络请求,从原生的http包到强大的第三方库如dio。本文将深入探讨 Flutter 网络请求的最佳实践,帮助你构建高效、可靠的异步应用。

一、选择合适的网络库

1.1 原生 http 包

import 'package:http/http.dart' as http; Future<void> fetchUserData() async { final response = await http.get( Uri.parse('https://api.example.com/users'), headers: {'Authorization': 'Bearer token'}, ); if (response.statusCode == 200) { // 处理响应 print(response.body); } else { throw Exception('请求失败: ${response.statusCode}'); } }

1.2 Dio 库 - 推荐选择

import 'package:dio/dio.dart'; final dio = Dio(BaseOptions( baseUrl: 'https://api.example.com', connectTimeout: const Duration(seconds: 5), receiveTimeout: const Duration(seconds: 3), )); Future<User> fetchUser(String userId) async { try { final response = await dio.get('/users/$userId'); return User.fromJson(response.data); } catch (e) { // 统一错误处理 throw handleError(e); } }

1.3 库对比分析

特性http 包DioRetrofit
易用性基础优秀中等
拦截器支持有限完善依赖 Dio
取消请求支持支持支持
缓存支持需实现内置需实现
文件上传基础优秀依赖 Dio
类型安全

二、构建网络层架构

2.1 分层架构设计

┌─────────────────────────────────────────────┐ │ UI Layer │ │ (Widgets, State) │ └────────────────────┬────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ Repository Layer │ │ (数据获取、缓存、业务逻辑) │ └────────────────────┬────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ Service Layer │ │ (网络请求、API 调用) │ └────────────────────┬────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────┐ │ Data Source │ │ (API、本地数据库、缓存) │ └─────────────────────────────────────────────┘

2.2 创建 ApiService 单例

class ApiService { static final ApiService _instance = ApiService._internal(); late Dio _dio; factory ApiService() => _instance; ApiService._internal() { _dio = Dio(BaseOptions( baseUrl: 'https://api.example.com', connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 10), headers: { 'Content-Type': 'application/json', }, )); // 添加拦截器 _setupInterceptors(); } void _setupInterceptors() { // 请求拦截器 _dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { // 添加 token final token = TokenManager.getToken(); if (token != null) { options.headers['Authorization'] = 'Bearer $token'; } return handler.next(options); }, onResponse: (response, handler) { // 统一处理响应 return handler.next(response); }, onError: (error, handler) { // 统一错误处理 return handler.reject(error); }, )); } Dio get dio => _dio; }

2.3 创建 Repository 层

class UserRepository { final ApiService _apiService; UserRepository({ApiService? apiService}) : _apiService = apiService ?? ApiService(); Future<User> getUser(String userId) async { try { final response = await _apiService.dio.get('/users/$userId'); return User.fromJson(response.data); } catch (e) { throw RepositoryException('获取用户失败', cause: e); } } Future<List<User>> getUsers({int page = 1, int limit = 10}) async { try { final response = await _apiService.dio.get( '/users', queryParameters: {'page': page, 'limit': limit}, ); final List<dynamic> data = response.data['data']; return data.map((json) => User.fromJson(json)).toList(); } catch (e) { throw RepositoryException('获取用户列表失败', cause: e); } } }

三、错误处理策略

3.1 自定义异常类

abstract class AppException implements Exception { final String message; final Exception? cause; AppException(this.message, {this.cause}); @override String toString() => '$message${cause != null ? ': $cause' : ''}'; } class NetworkException extends AppException { NetworkException({String message = '网络连接失败', Exception? cause}) : super(message, cause: cause); } class ServerException extends AppException { final int statusCode; ServerException({ required this.statusCode, String message = '服务器错误', Exception? cause, }) : super(message, cause: cause); } class AuthException extends AppException { AuthException({String message = '认证失败', Exception? cause}) : super(message, cause: cause); } class RepositoryException extends AppException { RepositoryException({required String message, Exception? cause}) : super(message, cause: cause); }

3.2 统一错误处理

class ErrorHandler { static AppException handle(dynamic error) { if (error is DioException) { switch (error.type) { case DioExceptionType.connectionTimeout: case DioExceptionType.receiveTimeout: case DioExceptionType.sendTimeout: return NetworkException(message: '请求超时'); case DioExceptionType.connectionError: return NetworkException(message: '网络连接失败'); case DioExceptionType.badResponse: final statusCode = error.response?.statusCode ?? 500; return _handleStatusCode(statusCode, error); case DioExceptionType.cancel: return AppException('请求已取消'); default: return AppException('未知错误'); } } if (error is SocketException) { return NetworkException(message: '网络连接异常'); } if (error is AppException) { return error; } return AppException('未知错误: $error'); } static ServerException _handleStatusCode(int statusCode, DioException error) { final message = error.response?.data?['message'] ?? ''; switch (statusCode) { case 400: return ServerException( statusCode: 400, message: message.isNotEmpty ? message : '请求参数错误', ); case 401: return AuthException(message: message.isNotEmpty ? message : '未授权'); case 403: return ServerException( statusCode: 403, message: message.isNotEmpty ? message : '禁止访问', ); case 404: return ServerException( statusCode: 404, message: message.isNotEmpty ? message : '资源未找到', ); case 500: return ServerException( statusCode: 500, message: message.isNotEmpty ? message : '服务器内部错误', ); default: return ServerException( statusCode: statusCode, message: '请求失败 ($statusCode)', ); } } }

四、请求取消机制

4.1 使用 CancelToken

class DataService { CancelToken? _cancelToken; Future<void> fetchData() async { // 取消之前的请求 _cancelToken?.cancel('请求已取消'); _cancelToken = CancelToken(); try { final response = await ApiService().dio.get( '/data', cancelToken: _cancelToken, ); // 处理响应 } on DioException catch (e) { if (e.type == DioExceptionType.cancel) { // 请求被取消,不处理错误 return; } throw ErrorHandler.handle(e); } } void dispose() { _cancelToken?.cancel('组件已销毁'); } }

4.2 在 Widget 生命周期中使用

class DataWidget extends StatefulWidget { @override _DataWidgetState createState() => _DataWidgetState(); } class _DataWidgetState extends State<DataWidget> { late DataService _dataService; @override void initState() { super.initState(); _dataService = DataService(); _loadData(); } Future<void> _loadData() async { try { await _dataService.fetchData(); } catch (e) { // 处理错误 } } @override void dispose() { _dataService.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container(); } }

五、请求缓存策略

5.1 使用 Hive 作为缓存

import 'package:hive/hive.dart'; class CacheService { static const String _cacheBox = 'api_cache'; Future<void> save(String key, dynamic data, {Duration? expiresIn}) async { final box = await Hive.openBox(_cacheBox); final cacheData = { 'data': data, 'timestamp': DateTime.now().millisecondsSinceEpoch, 'expiresIn': expiresIn?.inMilliseconds, }; await box.put(key, cacheData); } Future<dynamic?> get(String key) async { final box = await Hive.openBox(_cacheBox); final cacheData = box.get(key); if (cacheData == null) return null; final timestamp = cacheData['timestamp'] as int; final expiresIn = cacheData['expiresIn'] as int?; if (expiresIn != null) { final now = DateTime.now().millisecondsSinceEpoch; if (now - timestamp > expiresIn) { await box.delete(key); return null; } } return cacheData['data']; } Future<void> delete(String key) async { final box = await Hive.openBox(_cacheBox); await box.delete(key); } Future<void> clear() async { final box = await Hive.openBox(_cacheBox); await box.clear(); } }

5.2 实现缓存优先策略

class UserRepository { final CacheService _cacheService; Future<User> getUser(String userId) async { // 先尝试从缓存获取 final cachedData = await _cacheService.get('user_$userId'); if (cachedData != null) { return User.fromJson(cachedData); } // 缓存不存在,从网络获取 final response = await ApiService().dio.get('/users/$userId'); final user = User.fromJson(response.data); // 缓存结果,5分钟过期 await _cacheService.save( 'user_$userId', response.data, expiresIn: const Duration(minutes: 5), ); return user; } }

六、请求重试机制

6.1 实现指数退避策略

class RetryInterceptor extends Interceptor { final int maxRetries; final Duration initialDelay; RetryInterceptor({ this.maxRetries = 3, this.initialDelay = const Duration(seconds: 1), }); @override void onError(DioException err, ErrorInterceptorHandler handler) { final requestOptions = err.requestOptions; // 检查是否需要重试 if (_shouldRetry(err) && requestOptions.extra['retryCount'] != maxRetries) { final retryCount = requestOptions.extra['retryCount'] ?? 0; requestOptions.extra['retryCount'] = retryCount + 1; // 指数退避延迟 final delay = initialDelay * math.pow(2, retryCount); Future.delayed(delay, () { _retryRequest(requestOptions, handler); }); return; } handler.reject(err); } bool _shouldRetry(DioException err) { // 只对网络错误和服务器错误重试 return err.type == DioExceptionType.connectionError || err.type == DioExceptionType.connectionTimeout || (err.type == DioExceptionType.badResponse && err.response?.statusCode != null && err.response!.statusCode! >= 500); } void _retryRequest( RequestOptions options, ErrorInterceptorHandler handler, ) async { try { final response = await ApiService().dio.fetch(options); handler.resolve(response); } catch (e) { handler.reject(e as DioException); } } }

七、实战案例:完整的用户列表页面

7.1 ViewModel 实现

class UserListViewModel extends ChangeNotifier { final UserRepository _repository; List<User> _users = []; bool _isLoading = false; AppException? _error; int _page = 1; bool _hasMore = true; List<User> get users => _users; bool get isLoading => _isLoading; AppException? get error => _error; bool get hasMore => _hasMore; UserListViewModel({UserRepository? repository}) : _repository = repository ?? UserRepository(); Future<void> fetchUsers({bool refresh = false}) async { if (_isLoading) return; if (refresh) { _page = 1; _users.clear(); _hasMore = true; } if (!_hasMore) return; _isLoading = true; _error = null; notifyListeners(); try { final newUsers = await _repository.getUsers(page: _page); if (newUsers.isEmpty) { _hasMore = false; } else { _users.addAll(newUsers); _page++; } } catch (e) { _error = ErrorHandler.handle(e); } finally { _isLoading = false; notifyListeners(); } } Future<void> refreshUsers() => fetchUsers(refresh: true); }

7.2 Widget 实现

class UserListPage extends StatefulWidget { @override _UserListPageState createState() => _UserListPageState(); } class _UserListPageState extends State<UserListPage> { late UserListViewModel _viewModel; final _scrollController = ScrollController(); @override void initState() { super.initState(); _viewModel = UserListViewModel(); _viewModel.fetchUsers(); _scrollController.addListener(_onScroll); } void _onScroll() { if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) { _viewModel.fetchUsers(); } } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('用户列表')), body: RefreshIndicator( onRefresh: _viewModel.refreshUsers, child: _buildContent(), ), ); } Widget _buildContent() { if (_viewModel.error != null) { return _buildError(); } if (_viewModel.users.isEmpty && !_viewModel.isLoading) { return _buildEmptyState(); } return ListView.builder( controller: _scrollController, itemCount: _viewModel.users.length + (_viewModel.hasMore ? 1 : 0), itemBuilder: (context, index) { if (index == _viewModel.users.length) { return _buildLoadingIndicator(); } return _buildUserItem(_viewModel.users[index]); }, ); } Widget _buildError() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(_viewModel.error!.message), const SizedBox(height: 16), ElevatedButton( onPressed: _viewModel.refreshUsers, child: const Text('重试'), ), ], ), ); } Widget _buildEmptyState() { return const Center( child: Text('暂无数据'), ); } Widget _buildLoadingIndicator() { return const Center( child: Padding( padding: EdgeInsets.all(16), child: CircularProgressIndicator(), ), ); } Widget _buildUserItem(User user) { return ListTile( leading: CircleAvatar(child: Text(user.name[0])), title: Text(user.name), subtitle: Text(user.email), ); } }

八、性能优化建议

8.1 避免重复请求

// 使用 debounce 避免频繁请求 class Debounce { final Duration delay; Timer? _timer; Debounce({this.delay = const Duration(milliseconds: 300)}); void run(VoidCallback action) { _timer?.cancel(); _timer = Timer(delay, action); } void dispose() { _timer?.cancel(); } } // 使用示例 final debounce = Debounce(); void onSearch(String query) { debounce.run(() { fetchSearchResults(query); }); }

8.2 使用 HTTP/2 和连接池

final dio = Dio(BaseOptions( baseUrl: 'https://api.example.com', // 启用 HTTP/2 httpClientAdapter: Http2Adapter( ConnectionManager( idleTimeout: const Duration(seconds: 10), // 连接池配置 connectionPool: const ConnectionPoolConfiguration( maxConnections: 5, idleTimeout: Duration(seconds: 30), ), ), ), ));

8.3 图片懒加载

class LazyLoadImage extends StatelessWidget { final String imageUrl; final Widget placeholder; const LazyLoadImage({ required this.imageUrl, this.placeholder = const CircularProgressIndicator(), }); @override Widget build(BuildContext context) { return FutureBuilder<Uint8List>( future: _loadImage(), builder: (context, snapshot) { if (snapshot.hasData) { return Image.memory(snapshot.data!); } return placeholder; }, ); } Future<Uint8List> _loadImage() async { final response = await ApiService().dio.get( imageUrl, options: Options(responseType: ResponseType.bytes), ); return response.data; } }

九、安全注意事项

9.1 HTTPS 强制使用

final dio = Dio(BaseOptions( baseUrl: 'https://api.example.com', validateStatus: (status) { // 只接受 HTTPS 请求 return status! >= 200 && status < 300; }, ));

9.2 Token 安全存储

import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class TokenManager { static const FlutterSecureStorage _storage = FlutterSecureStorage(); static const String _tokenKey = 'auth_token'; static Future<void> saveToken(String token) async { await _storage.write(key: _tokenKey, value: token); } static Future<String?> getToken() async { return _storage.read(key: _tokenKey); } static Future<void> deleteToken() async { await _storage.delete(key: _tokenKey); } }

十、总结与展望

10.1 最佳实践总结

  1. 选择合适的网络库:根据项目需求选择 http、Dio 或 Retrofit
  2. 分层架构:Repository 层 + Service 层 + Data Source 层
  3. 统一错误处理:定义自定义异常类,统一处理各类错误
  4. 请求取消:使用 CancelToken 避免无用请求
  5. 缓存策略:实现缓存优先,提升用户体验
  6. 请求重试:实现指数退避策略,提高请求成功率
  7. 性能优化:使用 debounce、连接池等技术

10.2 未来发展趋势

  • HTTP/3 支持:更快的连接建立和多路复用
  • GraphQL:更高效的数据获取方式
  • WebSocket:实时通信场景的最佳选择
  • 离线优先:更好的离线体验

参考资料

  • Dio Documentation
  • Retrofit for Dart
  • Flutter Secure Storage
  • Hive
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/9 4:28:39

开源技能学习平台Skillfoundry:架构解析与自部署实践

1. 项目概述&#xff1a;一个面向技能学习的开源平台最近在GitHub上看到一个挺有意思的项目&#xff0c;叫“skillfoundry”&#xff0c;作者是sami。这个项目乍一看名字&#xff0c;可能很多人会联想到一个技能锻造厂或者学习工坊。没错&#xff0c;它的核心定位就是一个开源、…

作者头像 李华
网站建设 2026/5/9 4:28:36

扩散模型在视频编辑中的应用与DualityForge框架解析

1. 项目概述&#xff1a;当扩散模型遇上视频编辑去年在帮一个影视工作室处理后期时&#xff0c;他们需要把拍摄场景中的现代路灯统一替换成复古煤气灯。传统逐帧修图的方式让团队苦不堪言&#xff0c;直到我们尝试用扩散模型进行视频连贯编辑——结果发现生成的路灯时大时小&am…

作者头像 李华
网站建设 2026/5/9 4:28:26

OpenClawBrain:为AI Agent构建非侵入式记忆与学习层的实践指南

1. 项目概述&#xff1a;OpenClawBrain 是什么&#xff1f; 如果你正在使用 OpenClaw 这类基于 AI Agent 的自动化工具&#xff0c;可能会遇到一个瓶颈&#xff1a;Agent 的“记忆”是短暂的、静态的&#xff0c;或者完全依赖于你手动注入的上下文。每次对话或任务执行后&#…

作者头像 李华
网站建设 2026/5/9 4:28:13

LLSA:高效稀疏注意力机制在长序列处理中的应用

1. 从密集到稀疏&#xff1a;注意力机制的计算效率革命在自然语言处理和计算机视觉领域&#xff0c;注意力机制已经成为现代深度学习架构的核心组件。传统注意力机制&#xff08;如Transformer中的自注意力&#xff09;虽然功能强大&#xff0c;但其计算复杂度随着序列长度呈二…

作者头像 李华
网站建设 2026/5/9 4:28:11

多智能体系统性能优化:架构设计与实践指南

1. 多智能体系统性能优化概述在工业自动化和分布式计算领域&#xff0c;多智能体系统(MAS)已经成为解决复杂任务的关键技术。这类系统由多个自主或半自主的智能体组成&#xff0c;通过相互协作完成单个智能体难以处理的复杂问题。典型的应用场景包括无人机编队控制、分布式传感…

作者头像 李华
网站建设 2026/5/9 4:27:58

AI代码守卫:eslint-plugin-ai-guard实战指南与异步错误处理

1. 项目概述&#xff1a;为什么我们需要一个专为AI代码设计的“守卫”&#xff1f; 如果你和我一样&#xff0c;在日常开发中重度依赖 GitHub Copilot、Cursor 或者 Claude Code 这类 AI 编程助手&#xff0c;那你肯定经历过那种“哭笑不得”的时刻&#xff1a;AI 生成的代码看…

作者头像 李华