@TOC
一、核心网络请求库对比
1. 常用库特性对比
在 Flutter 开发中,选择一个合适的网络请求库是构建稳定应用的第一步。不同的项目规模和团队背景需要不同的技术栈支持。http、dio、retrofit 和 chopper 是目前最主流的几种选择。
http:由 Flutter 官方团队维护,轻量、简洁,适合初学者或对网络功能要求不高的小型项目。它没有内置拦截器、序列化、超时等高级功能,所有逻辑都需要手动实现。dio:功能极为强大,支持拦截器、请求/响应拦截、文件上传下载、超时配置、自动解析、取消请求等,是中大型项目的首选。其 API 设计清晰,扩展性强,社区生态成熟。retrofit/chopper:借鉴了 Android 平台的 Retrofit 框架思想,采用注解方式定义接口,代码结构优雅,适合有 Android 背景的开发者。但它们依赖代码生成器,在灵活性和调试上略逊于 dio,且生态相对较小。
总结建议:
dio是最全面、功能最强大的选择,适合中大型项目。http轻量简单,适合小型项目或学习使用。retrofit和chopper提供类似 Retrofit 的注解风格,适合习惯 Android 开发的团队,但生态和灵活性略逊于 dio。
二、Dio 深度使用指南
1. 基础配置与初始化
dart
// dio_client.dart
class DioClient {
static final DioClient _instance = DioClient._internal();
factory DioClient() => _instance;
DioClient._internal() {
_init();
}
late Dio dio;
void _init() {
dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
sendTimeout: const Duration(seconds: 30),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
),
);
// 添加拦截器
dio.interceptors.addAll([
_getLogInterceptor(),
_getAuthInterceptor(),
_getErrorInterceptor(),
_getCacheInterceptor(),
]);
}
Interceptor _getLogInterceptor() {
return LogInterceptor(
request: true,
requestBody: true,
responseBody: true,
responseHeader: false,
error: true,
logPrint: (log) => debugPrint('Dio: $log'),
);
}
Interceptor _getAuthInterceptor() {
return InterceptorsWrapper(
onRequest: (options, handler) async {
// 添加认证token
final token = await _getAuthToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
},
onResponse: (response, handler) {
// 统一处理响应
return handler.next(response);
},
onError: (DioException error, handler) async {
// Token过期处理
if (error.response?.statusCode == 401) {
final newToken = await _refreshToken();
if (newToken != null) {
// 重新发起请求
final request = error.requestOptions;
request.headers['Authorization'] = 'Bearer $newToken';
return handler.resolve(await dio.fetch(request));
}
}
return handler.next(error);
},
);
}
Interceptor _getErrorInterceptor() {
return InterceptorsWrapper(
onError: (DioException error, handler) {
// 统一错误处理
final networkError = _handleDioError(error);
return handler.reject(networkError);
},
);
}
Interceptor _getCacheInterceptor() {
return InterceptorsWrapper(
onRequest: (options, handler) async {
// 缓存处理
if (options.extra['cache'] == true) {
final cachedResponse = await _getCachedResponse(options);
if (cachedResponse != null) {
return handler.resolve(cachedResponse);
}
}
return handler.next(options);
},
onResponse: (response, handler) async {
// 缓存响应
if (response.requestOptions.extra['cache'] == true) {
await _cacheResponse(response);
}
return handler.next(response);
},
);
}
Future<String?> _getAuthToken() async {
// 从本地存储获取token
final prefs = await SharedPreferences.getInstance();
return prefs.getString('auth_token');
}
Future<String?> _refreshToken() async {
// 刷新token逻辑
try {
final response = await dio.post('/refresh-token');
final newToken = response.data['token'];
final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', newToken);
return newToken;
} catch (e) {
return null;
}
}
DioException _handleDioError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return DioException(
requestOptions: error.requestOptions,
error: '网络连接超时,请检查网络设置',
);
case DioExceptionType.badResponse:
return _handleBadResponse(error);
case DioExceptionType.cancel:
return DioException(
requestOptions: error.requestOptions,
error: '请求已取消',
);
case DioExceptionType.unknown:
return DioException(
requestOptions: error.requestOptions,
error: '网络连接失败,请检查网络设置',
);
default:
return error;
}
}
DioException _handleBadResponse(DioException error) {
final statusCode = error.response?.statusCode;
final message = error.response?.data?['message'] ?? '请求失败';
String errorMessage;
switch (statusCode) {
case 400:
errorMessage = '请求参数错误';
break;
case 401:
errorMessage = '未授权,请重新登录';
break;
case 403:
errorMessage = '访问被拒绝';
break;
case 404:
errorMessage = '请求的资源不存在';
break;
case 500:
errorMessage = '服务器内部错误';
break;
case 502:
errorMessage = '网关错误';
break;
case 503:
errorMessage = '服务不可用';
break;
default:
errorMessage = message;
}
return DioException(
requestOptions: error.requestOptions,
error: errorMessage,
response: error.response,
);
}
Future<Response?> _getCachedResponse(RequestOptions options) async {
// 实现缓存逻辑
return null;
}
Future<void> _cacheResponse(Response response) async {
// 实现缓存存储逻辑
}
}
详细说明:Dio 客户端的单例模式与拦截器体系
上述代码实现了一个全局唯一的 DioClient,采用 单例模式(Singleton Pattern),确保整个应用只存在一个 Dio 实例,避免重复创建和资源浪费。
BaseOptions 配置了基础请求参数,包括:
baseUrl:所有请求的前缀,减少重复输入。timeout:连接、发送、接收超时时间,防止请求无限等待。headers:默认请求头,如内容类型和接受格式。
拦截器(Interceptors) 是 Dio 的核心特性之一,允许你在请求/响应生命周期中插入自定义逻辑:
- 日志拦截器:打印请求和响应内容,便于调试。
- 鉴权拦截器 :在每个请求头中自动添加
Authorization,并处理 401 错误时的 token 刷新与重试机制(即"自动续签")。 - 错误拦截器:将 Dio 的原生异常转换为更友好的用户提示信息。
- 缓存拦截器:根据请求配置决定是否读取或写入缓存,提升用户体验。
该设计实现了关注点分离,让网络层具备日志、安全、容错、性能优化等企业级能力。
三、高级请求封装
1. API 服务类封装
dart
// api_service.dart
class ApiService {
final DioClient _dioClient;
ApiService(this._dioClient);
// GET 请求封装
Future<ApiResponse<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
bool cache = false,
T Function(dynamic)? fromJson,
}) async {
try {
final response = await _dioClient.dio.get(
path,
queryParameters: queryParameters,
options: Options(extra: {'cache': cache}),
);
return _handleResponse<T>(response, fromJson);
} on DioException catch (e) {
return ApiResponse.error(message: e.error?.toString() ?? '网络请求失败');
} catch (e) {
return ApiResponse.error(message: '未知错误: $e');
}
}
// POST 请求封装
Future<ApiResponse<T>> post<T>(
String path, {
dynamic data,
T Function(dynamic)? fromJson,
}) async {
try {
final response = await _dioClient.dio.post(
path,
data: data,
);
return _handleResponse<T>(response, fromJson);
} on DioException catch (e) {
return ApiResponse.error(message: e.error?.toString() ?? '网络请求失败');
} catch (e) {
return ApiResponse.error(message: '未知错误: $e');
}
}
// 文件上传
Future<ApiResponse<T>> upload<T>(
String path,
String filePath, {
T Function(dynamic)? fromJson,
ProgressCallback? onSendProgress,
}) async {
try {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(filePath),
});
final response = await _dioClient.dio.post(
path,
data: formData,
onSendProgress: onSendProgress,
);
return _handleResponse<T>(response, fromJson);
} on DioException catch (e) {
return ApiResponse.error(message: e.error?.toString() ?? '文件上传失败');
} catch (e) {
return ApiResponse.error(message: '未知错误: $e');
}
}
// 多文件上传
Future<ApiResponse<T>> uploadMultiple<T>(
String path,
List<String> filePaths, {
T Function(dynamic)? fromJson,
ProgressCallback? onSendProgress,
}) async {
try {
final formData = FormData();
for (final filePath in filePaths) {
formData.files.add(MapEntry(
'files',
await MultipartFile.fromFile(filePath),
));
}
final response = await _dioClient.dio.post(
path,
data: formData,
onSendProgress: onSendProgress,
);
return _handleResponse<T>(response, fromJson);
} on DioException catch (e) {
return ApiResponse.error(message: e.error?.toString() ?? '文件上传失败');
} catch (e) {
return ApiResponse.error(message: '未知错误: $e');
}
}
// 文件下载
Future<ApiResponse<String>> download(
String url,
String savePath, {
ProgressCallback? onReceiveProgress,
}) async {
try {
final response = await _dioClient.dio.download(
url,
savePath,
onReceiveProgress: onReceiveProgress,
);
if (response.statusCode == 200) {
return ApiResponse.success(data: savePath);
} else {
return ApiResponse.error(message: '下载失败');
}
} on DioException catch (e) {
return ApiResponse.error(message: e.error?.toString() ?? '下载失败');
} catch (e) {
return ApiResponse.error(message: '未知错误: $e');
}
}
// 取消请求
CancelToken createCancelToken() => CancelToken();
ApiResponse<T> _handleResponse<T>(Response response, T Function(dynamic)? fromJson) {
if (response.statusCode == 200) {
try {
final data = response.data;
if (fromJson != null) {
final parsedData = fromJson(data);
return ApiResponse.success(data: parsedData);
} else {
return ApiResponse.success(data: data as T);
}
} catch (e) {
return ApiResponse.error(message: '数据解析失败: $e');
}
} else {
return ApiResponse.error(
message: '请求失败: ${response.statusCode}',
code: response.statusCode,
);
}
}
}
详细说明:统一 API 服务层的设计思想
ApiService 是对 Dio 的进一步封装,屏蔽底层细节,提供更高层次的 API 接口。
- 泛型支持 :通过
<T>支持任意数据类型的返回,结合fromJson回调完成 JSON 到模型的转换。 - 统一错误处理 :捕获
DioException并封装为ApiResponse.error,避免在 UI 层处理原始异常。 - 文件操作支持:封装单/多文件上传与下载,支持进度监听,提升用户体验。
- 响应处理抽象 :
_handleResponse方法统一判断状态码、解析数据、处理异常,确保所有请求遵循相同逻辑。 - 取消请求能力 :暴露
CancelToken,可在页面销毁或用户取消时中断请求,防止内存泄漏。
这种封装方式使得业务代码只需关心"调什么接口",而不必重复编写 try-catch、解析、错误提示等模板代码。
2. 统一响应模型
dart
// 统一响应模型
class ApiResponse<T> {
final T? data;
final String? message;
final int? code;
final bool success;
ApiResponse({
this.data,
this.message,
this.code,
required this.success,
});
factory ApiResponse.success({T? data, String? message}) {
return ApiResponse(
data: data,
message: message,
success: true,
);
}
factory ApiResponse.error({String? message, int? code}) {
return ApiResponse(
message: message,
code: code,
success: false,
);
}
bool get hasData => data != null;
}
详细说明:标准化响应结构的价值
ApiResponse<T> 是一个泛型容器类,用于包装所有网络请求的结果。
它具有以下优势:
- 结构统一:无论成功还是失败,返回值都符合同一结构,便于状态管理。
- 类型安全 :通过泛型
T明确指定数据类型,避免类型转换错误。 - 语义清晰 :
success字段明确标识请求结果,hasData快速判断是否有有效数据。 - 便于 UI 显示 :
message可直接用于 Toast 或错误提示框。
该模型是连接网络层与 UI 层的桥梁,极大提升了代码的可读性和健壮性。
四、状态管理集成
1. 基于 Riverpod 的网络状态管理
dart
// network_providers.dart
final dioClientProvider = Provider<DioClient>((ref) {
return DioClient();
});
final apiServiceProvider = Provider<ApiService>((ref) {
final dioClient = ref.watch(dioClientProvider);
return ApiService(dioClient);
});
// 用户相关API Provider
final userRepositoryProvider = Provider<UserRepository>((ref) {
final apiService = ref.watch(apiServiceProvider);
return UserRepository(apiService);
});
// 用户列表状态管理
final usersProvider = StateNotifierProvider<UsersNotifier, UsersState>((ref) {
final userRepository = ref.watch(userRepositoryProvider);
return UsersNotifier(userRepository);
});
详细说明:依赖注入与 Riverpod 的优雅结合
Riverpod 提供了强大的依赖注入机制,上述代码使用 Provider 和 StateNotifierProvider 实现了分层解耦:
dioClientProvider:提供全局 Dio 实例。apiServiceProvider:注入 Dio,创建 ApiService。userRepositoryProvider:注入 ApiService,创建 UserRepository。usersProvider:注入 Repository,管理用户列表状态。
这种方式实现了 控制反转(IoC),组件之间不直接依赖具体实现,而是通过 Provider 获取依赖,便于替换、测试和复用。
2. 状态定义
dart
class UsersState {
final List<User> users;
final bool isLoading;
final String? error;
final bool hasReachedMax;
const UsersState({
this.users = const [],
this.isLoading = false,
this.error,
this.hasReachedMax = false,
});
UsersState copyWith({
List<User>? users,
bool? isLoading,
String? error,
bool? hasReachedMax,
}) {
return UsersState(
users: users ?? this.users,
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
);
}
}
详细说明:不可变状态(Immutable State)的重要性
UsersState 是一个典型的不可变状态类,所有字段都是 final,通过 copyWith 方法创建新实例。
优点包括:
- 避免副作用:状态不会被意外修改。
- 易于调试:每次状态变化都产生新对象,便于追踪。
- 兼容 Riverpod:StateNotifier 要求状态不可变。
isLoading 控制加载指示器,error 显示错误信息,hasReachedMax 用于判断是否已加载全部数据(支持分页加载)。
3. Notifier 实现
dart
class UsersNotifier extends StateNotifier<UsersState> {
final UserRepository _userRepository;
int _page = 1;
bool _isLoading = false;
UsersNotifier(this._userRepository) : super(const UsersState());
Future<void> fetchUsers({bool refresh = false}) async {
if (_isLoading) return;
if (refresh) {
_page = 1;
state = const UsersState(isLoading: true);
} else {
state = state.copyWith(isLoading: true);
}
try {
_isLoading = true;
final response = await _userRepository.getUsers(page: _page);
if (response.success && response.hasData) {
final users = response.data!;
state = state.copyWith(
users: refresh ? users : [...state.users, ...users],
isLoading: false,
error: null,
hasReachedMax: users.length < 20, // 假设每页20条
);
_page++;
} else {
state = state.copyWith(
isLoading: false,
error: response.message ?? '加载失败',
);
}
} catch (e) {
state = state.copyWith(
isLoading: false,
error: '网络请求失败: $e',
);
} finally {
_isLoading = false;
}
}
Future<void> refresh() => fetchUsers(refresh: true);
}
详细说明:状态驱动 UI 的完整流程
UsersNotifier 是业务逻辑的核心,负责协调 Repository 并更新状态。
fetchUsers方法根据refresh参数决定是刷新还是加载更多。- 请求前设置
isLoading: true,触发 UI 显示加载动画。 - 成功后合并新旧数据(分页加载),失败则更新
error字段。 - 使用
_isLoading标志防止重复请求,提升稳定性。 refresh()方法简化下拉刷新调用。
该模式实现了"请求 → 状态更新 → UI 重绘"的闭环,是现代 Flutter 应用的标准范式。
五、数据模型与序列化
1. 自动生成序列化代码
dart
// user.dart
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
final int id;
final String name;
final String email;
final String? avatar;
final DateTime createdAt;
final DateTime updatedAt;
User({
required this.id,
required this.name,
required this.email,
this.avatar,
required this.createdAt,
required this.updatedAt,
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
User copyWith({
int? id,
String? name,
String? email,
String? avatar,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return User(
id: id ?? this.id,
name: name ?? this.name,
email: email ?? this.email,
avatar: avatar ?? this.avatar,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}
详细说明:json_serializable 的高效与安全
手动编写 fromJson 和 toJson 容易出错且繁琐。json_annotation + build_runner 可以自动生成这些方法。
@JsonSerializable()注解标记类需要序列化。part 'user.g.dart'指定生成文件。- 运行
dart run build_runner build自动生成fromJson和toJson。 copyWith支持局部更新,常用于状态管理。
这种方式既保证了性能,又减少了人为错误。
2. 通用响应模型
dart
// api_response_model.dart
@JsonSerializable(genericArgumentFactories: true)
class ApiResponseModel<T> {
final int code;
final String message;
final T? data;
ApiResponseModel({
required this.code,
required this.message,
this.data,
});
factory ApiResponseModel.fromJson(
Map<String, dynamic> json,
T Function(Object? json) fromJsonT,
) =>
_$ApiResponseModelFromJson(json, fromJsonT);
Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
_$ApiResponseModelToJson(this, toJsonT);
bool get isSuccess => code == 200;
}
详细说明:泛型响应模型的通用性
许多后端 API 返回统一结构,如 { code: 200, message: "ok", data: {} }。
ApiResponseModel<T> 封装了这种结构:
- 支持泛型
T,可嵌套任意数据模型。 genericArgumentFactories: true允许传递fromJsonT函数,实现嵌套对象解析。isSuccess快速判断请求是否成功。
该模型可直接用于 dio 的响应解析,减少重复代码。
六、Repository 模式实现
dart
// user_repository.dart
class UserRepository {
final ApiService _apiService;
UserRepository(this._apiService);
Future<ApiResponse<List<User>>> getUsers({int page = 1, int limit = 20}) async {
return _apiService.get(
'/users',
queryParameters: {
'page': page,
'limit': limit,
},
fromJson: (data) {
if (data is List) {
return data.map((e) => User.fromJson(e)).toList();
}
return [];
},
);
}
Future<ApiResponse<User>> getUserById(int id) async {
return _apiService.get(
'/users/$id',
fromJson: (data) => User.fromJson(data),
);
}
Future<ApiResponse<User>> updateUser(User user) async {
return _apiService.post(
'/users/${user.id}',
data: user.toJson(),
fromJson: (data) => User.fromJson(data),
);
}
Future<ApiResponse<String>> uploadAvatar(String filePath) async {
return _apiService.upload<String>(
'/users/avatar',
filePath,
fromJson: (data) => data['url'] as String,
);
}
}
详细说明:Repository 模式的分层价值
Repository 是业务逻辑与数据源之间的抽象层,具有以下作用:
- 隔离变化:如果未来更换网络库(如从 dio 改为 http),只需修改 Repository,不影响上层。
- 统一入口 :所有用户相关请求集中在
UserRepository,便于维护。 - 数据转换:将原始 API 响应转换为 App 内部模型。
- 支持多数据源:未来可轻松扩展本地数据库、缓存等。
它是实现 Clean Architecture 的关键一环。
七、UI 集成与使用
1. 网络请求状态 Widget
dart
// network_state_widget.dart
class NetworkStateWidget<T> extends StatelessWidget {
final AsyncValue<T> asyncValue;
final Widget Function(T data) successBuilder;
final Widget Function()? loadingBuilder;
final Widget Function(Object error, StackTrace stackTrace)? errorBuilder;
final VoidCallback? onRetry;
const NetworkStateWidget({
super.key,
required this.asyncValue,
required this.successBuilder,
this.loadingBuilder,
this.errorBuilder,
this.onRetry,
});
@override
Widget build(BuildContext context) {
return asyncValue.when(
data: successBuilder,
loading: loadingBuilder ?? () => const _LoadingWidget(),
error: errorBuilder ?? (error, stack) => _ErrorWidget(
error: error,
onRetry: onRetry,
),
);
}
}
class _LoadingWidget extends StatelessWidget {
const _LoadingWidget();
@override
Widget build(BuildContext context) {
return const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('加载中...'),
],
),
),
);
}
}
class _ErrorWidget extends StatelessWidget {
final Object error;
final VoidCallback? onRetry;
const _ErrorWidget({
required this.error,
this.onRetry,
});
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 64,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(height: 16),
Text(
error.toString(),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.error,
),
),
const SizedBox(height: 16),
if (onRetry != null)
FilledButton(
onPressed: onRetry,
child: const Text('重试'),
),
],
),
),
);
}
}
详细说明:状态感知型 UI 组件的设计理念
NetworkStateWidget 是一个通用的状态渲染组件,接受 AsyncValue(Riverpod 提供)并根据其状态展示不同 UI。
when方法分别处理data、loading、error三种状态。- 支持自定义加载和错误界面。
- 提供
onRetry回调,点击"重试"按钮可重新发起请求。
这种组件可复用于任何异步操作(如网络请求、数据库查询),极大提升 UI 开发效率。
2. 页面使用示例
dart
// users_page.dart
class UsersPage extends ConsumerStatefulWidget {
const UsersPage({super.key});
@override
ConsumerState<UsersPage> createState() => _UsersPageState();
}
class _UsersPageState extends ConsumerState<UsersPage> {
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
// 初始加载数据
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(usersProvider.notifier).fetchUsers();
});
}
void _onScroll() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
// 加载更多
ref.read(usersProvider.notifier).fetchUsers();
}
}
@override
Widget build(BuildContext context) {
final usersState = ref.watch(usersProvider);
return Scaffold(
appBar: AppBar(
title: const Text('用户列表'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => ref.read(usersProvider.notifier).refresh(),
),
],
),
body: NetworkStateWidget(
asyncValue: AsyncValue.data(usersState),
onRetry: () => ref.read(usersProvider.notifier).refresh(),
successBuilder: (state) {
return RefreshIndicator(
onRefresh: () => ref.read(usersProvider.notifier).refresh(),
child: ListView.builder(
controller: _scrollController,
itemCount: state.users.length + (state.hasReachedMax ? 0 : 1),
itemBuilder: (context, index) {
if (index >= state.users.length) {
return const Padding(
padding: EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator()),
);
}
final user = state.users[index];
return UserListItem(user: user);
},
),
);
},
),
);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
详细说明:完整页面的构建逻辑
UsersPage 展示了如何将网络、状态、UI 三层无缝集成:
- 使用
ConsumerStatefulWidget访问 Riverpod 状态。 initState中监听滚动事件,实现上拉加载更多。ref.watch(usersProvider)监听状态变化,自动刷新 UI。NetworkStateWidget统一处理加载、成功、错误状态。RefreshIndicator支持下拉刷新。- 列表末尾显示加载更多指示器。
整个页面逻辑清晰,用户体验流畅,是典型的生产级实现。
八、性能优化与最佳实践
1. 请求取消与防抖
dart
class Debouncer {
final Duration delay;
Timer? _timer;
Debouncer({required this.delay});
void call(void Function() action) {
_timer?.cancel();
_timer = Timer(delay, action);
}
void dispose() {
_timer?.cancel();
}
}
class SearchService {
final ApiService _apiService;
CancelToken? _cancelToken;
SearchService(this._apiService);
Future<ApiResponse<List<User>>> searchUsers(String query) async {
// 取消之前的请求
_cancelToken?.cancel('New search request');
_cancelToken = CancelToken();
return _apiService.get(
'/users/search',
queryParameters: {'q': query},
cancelToken: _cancelToken,
fromJson: (data) {
if (data is List) {
return data.map((e) => User.fromJson(e)).toList();
}
return [];
},
);
}
}
详细说明:防抖与请求取消的必要性
- 防抖(Debouncer):在搜索框等高频输入场景中,避免每次输入都发起请求,仅在用户停止输入后延迟执行,减少服务器压力。
- 取消请求(CancelToken):当新请求发起时,取消旧请求,防止旧响应覆盖新数据(即"竞态条件")。
两者结合可显著提升性能和用户体验。
2. 缓存策略
dart
class CacheManager {
static final CacheManager _instance = CacheManager._internal();
factory CacheManager() => _instance;
CacheManager._internal();
final Map<String, dynamic> _memoryCache = {};
final Duration _defaultDuration = const Duration(minutes: 5);
Future<T?> get<T>(String key) async {
final cached = _memoryCache[key];
if (cached != null && cached is _CacheItem) {
if (cached.expiry.isAfter(DateTime.now())) {
return cached.data as T;
} else {
_memoryCache.remove(key);
}
}
return null;
}
Future<void> set<T>(String key, T data, {Duration? duration}) async {
_memoryCache[key] = _CacheItem(
data: data,
expiry: DateTime.now().add(duration ?? _defaultDuration),
);
}
Future<void> remove(String key) async {
_memoryCache.remove(key);
}
Future<void> clear() async {
_memoryCache.clear();
}
}
class _CacheItem {
final dynamic data;
final DateTime expiry;
_CacheItem({required this.data, required this.expiry});
}
详细说明:内存缓存的实现与应用场景
CacheManager 提供了一个简单的内存缓存系统,支持:
- 设置过期时间,避免数据陈旧。
- 自动清理过期项。
- 支持任意类型数据存储。
可用于缓存用户信息、配置项、搜索结果等不常变动的数据,减少网络请求,加快响应速度。
3. 网络状态监控
dart
class NetworkConnectivity {
final Connectivity _connectivity = Connectivity();
final StreamController<bool> _connectionStatusController =
StreamController<bool>.broadcast();
Stream<bool> get connectionStatus => _connectionStatusController.stream;
Future<void> initialize() async {
_connectivity.onConnectivityChanged.listen((result) {
_updateConnectionStatus(result);
});
// 初始检查
final initialResult = await _connectivity.checkConnectivity();
_updateConnectionStatus(initialResult);
}
void _updateConnectionStatus(ConnectivityResult result) {
final hasConnection = result != ConnectivityResult.none;
_connectionStatusController.add(hasConnection);
}
Future<bool> get isConnected async {
final result = await _connectivity.checkConnectivity();
return result != ConnectivityResult.none;
}
void dispose() {
_connectionStatusController.close();
}
}
详细说明:实时网络状态监控的重要性
通过 connectivity_plus 插件,可以监听设备网络状态变化。
- 应用启动时检查网络是否可用。
- 在无网络时禁用请求或提示用户。
- 网络恢复后自动重试失败的请求。
- 提升离线体验和应用健壮性。
是构建高质量 App 的必备功能。
九、配置与依赖
1. pubspec.yaml 配置
yaml
dependencies:
flutter:
sdk: flutter
# 网络请求
dio: ^5.0.0
retrofit: ^4.0.1
logger: ^1.1.0
# 状态管理
flutter_riverpod: ^2.3.0
# 序列化
json_annotation: ^4.8.1
# 网络状态监控
connectivity_plus: ^5.0.0
# 本地存储
shared_preferences: ^2.2.0
dev_dependencies:
flutter_test:
sdk: flutter
# 序列化代码生成
build_runner: ^2.4.0
retrofit_generator: ^4.0.1
json_serializable: ^6.7.1
详细说明:核心依赖的选择依据
dio:核心网络库,功能全面。flutter_riverpod:现代状态管理方案,支持 Provider 注入。json_serializable:自动化 JSON 序列化,减少样板代码。connectivity_plus:跨平台网络状态检测。shared_preferences:轻量级本地存储,用于保存 token 等。build_runner:代码生成工具,配合json_serializable使用。
这些库共同构成了一个现代化 Flutter 应用的技术底座。
十、总结
通过这种深度集成的网络请求架构,你可以构建出:
- 高性能:支持缓存、防抖、并发控制
- 可维护:清晰的分层结构(API → Repository → StateNotifier → UI)
- 易测试:依赖注入 + 抽象接口,便于单元测试和 Mock
- 健壮性高:统一错误处理、自动重试、Token 刷新机制
关键建议:
- 根据项目规模选择合适的抽象层级(小项目可简化 Repository 层)
- 合理使用拦截器进行日志、鉴权、错误处理
- 结合 Riverpod 实现状态驱动 UI 更新
- 善用
json_serializable减少模板代码- 始终考虑用户体验:加载、错误、重试、离线支持
构建一个现代化的 Flutter 应用,从设计好网络层开始!