Flutter 网络请求高级技巧完全指南
·
Flutter 网络请求高级技巧完全指南
引言
网络请求是移动应用开发中不可或缺的一部分。Flutter 提供了多种网络请求方案,本文将深入探讨网络请求的高级技巧,包括请求封装、错误处理、缓存策略等内容。
网络请求基础回顾
Flutter 中常用的网络请求库有:
- http - Flutter 官方提供的基础 HTTP 库
- dio - 强大的 HTTP 客户端,支持拦截器、请求取消等
- retrofit - 类型安全的 HTTP 客户端
// 使用 http 库
import 'package:http/http.dart' as http;
final response = await http.get(Uri.parse('https://api.example.com/data'));
高级技巧一:请求封装
创建网络服务类
import 'package:dio/dio.dart';
class ApiService {
static final Dio _dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
));
static Future<Response<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
try {
final response = await _dio.get<T>(
path,
queryParameters: queryParameters,
options: options,
);
return response;
} catch (e) {
throw _handleError(e);
}
}
static Future<Response<T>> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
try {
final response = await _dio.post<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
);
return response;
} catch (e) {
throw _handleError(e);
}
}
static Exception _handleError(dynamic e) {
if (e is DioException) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
return Exception('连接超时');
case DioExceptionType.sendTimeout:
return Exception('发送超时');
case DioExceptionType.receiveTimeout:
return Exception('接收超时');
case DioExceptionType.badResponse:
return Exception('服务器错误: ${e.response?.statusCode}');
case DioExceptionType.cancel:
return Exception('请求已取消');
default:
return Exception('网络错误: ${e.message}');
}
}
return Exception('未知错误');
}
}
高级技巧二:拦截器配置
请求拦截器
class AuthInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// 添加 token
final token = getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
// 添加通用参数
options.queryParameters['platform'] = 'flutter';
options.queryParameters['version'] = '1.0.0';
super.onRequest(options, handler);
}
}
class LogInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print('请求方法: ${options.method}');
print('请求地址: ${options.uri}');
print('请求参数: ${options.data}');
super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print('响应状态: ${response.statusCode}');
print('响应数据: ${response.data}');
super.onResponse(response, handler);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
print('错误信息: ${err.message}');
super.onError(err, handler);
}
}
// 注册拦截器
_dio.interceptors.add(AuthInterceptor());
_dio.interceptors.add(LogInterceptor());
高级技巧三:请求取消
单个请求取消
CancelToken cancelToken = CancelToken();
try {
final response = await _dio.get(
'/api/data',
cancelToken: cancelToken,
);
} on DioException catch (e) {
if (DioException.cancel == e.type) {
print('请求已取消');
}
}
// 取消请求
cancelToken.cancel('用户主动取消');
批量请求取消
class CancelTokenManager {
final Map<String, CancelToken> _tokens = {};
void addToken(String key, CancelToken token) {
_tokens[key] = token;
}
void cancelToken(String key) {
_tokens[key]?.cancel();
_tokens.remove(key);
}
void cancelAll() {
_tokens.forEach((key, token) => token.cancel());
_tokens.clear();
}
CancelToken getToken(String key) {
return _tokens[key] ?? CancelToken();
}
}
高级技巧四:响应数据解析
泛型解析
class ApiResponse<T> {
final int code;
final String message;
final T? data;
ApiResponse({
required this.code,
required this.message,
this.data,
});
factory ApiResponse.fromJson(Map<String, dynamic> json, T Function(dynamic) fromJson) {
return ApiResponse<T>(
code: json['code'] as int,
message: json['message'] as String,
data: json['data'] != null ? fromJson(json['data']) : null,
);
}
}
// 使用
final response = await ApiService.get<Map<String, dynamic>>('/api/user');
final apiResponse = ApiResponse<User>.fromJson(
response.data!,
(json) => User.fromJson(json as Map<String, dynamic>),
);
列表数据解析
final response = await ApiService.get<Map<String, dynamic>>('/api/users');
final apiResponse = ApiResponse<List<User>>.fromJson(
response.data!,
(json) => (json as List).map((e) => User.fromJson(e as Map<String, dynamic>)).toList(),
);
高级技巧五:缓存策略
内存缓存
class MemoryCache {
static final Map<String, dynamic> _cache = {};
static final Map<String, DateTime> _cacheTime = {};
static const Duration _expireDuration = Duration(minutes: 5);
static T? get<T>(String key) {
if (!_cache.containsKey(key)) return null;
final expireTime = _cacheTime[key];
if (expireTime != null && DateTime.now().isAfter(expireTime)) {
_cache.remove(key);
_cacheTime.remove(key);
return null;
}
return _cache[key] as T?;
}
static void set<T>(String key, T value) {
_cache[key] = value;
_cacheTime[key] = DateTime.now().add(_expireDuration);
}
static void remove(String key) {
_cache.remove(key);
_cacheTime.remove(key);
}
static void clear() {
_cache.clear();
_cacheTime.clear();
}
}
网络优先缓存策略
Future<T> fetchWithCache<T>(
String key,
Future<T> Function() fetchFunction, {
Duration expireDuration = const Duration(minutes: 5),
}) async {
// 先尝试获取缓存
final cachedData = MemoryCache.get<T>(key);
if (cachedData != null) {
return cachedData;
}
// 缓存不存在或过期,发起网络请求
final data = await fetchFunction();
// 缓存数据
MemoryCache.set<T>(key, data);
return data;
}
高级技巧六:请求重试
自动重试机制
class RetryInterceptor extends Interceptor {
final int maxRetries;
final List<int> retryStatusCodes;
RetryInterceptor({
this.maxRetries = 3,
this.retryStatusCodes = const [500, 502, 503, 504],
});
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
if (err.response != null &&
retryStatusCodes.contains(err.response!.statusCode) &&
_shouldRetry(err)) {
_retryRequest(err, handler);
return;
}
super.onError(err, handler);
}
bool _shouldRetry(DioException err) {
return err.requestOptions.extra['retryCount'] != null &&
err.requestOptions.extra['retryCount'] < maxRetries;
}
void _retryRequest(DioException err, ErrorInterceptorHandler handler) async {
final currentRetry = err.requestOptions.extra['retryCount'] ?? 0;
final nextRetry = currentRetry + 1;
// 延迟重试
await Future.delayed(Duration(seconds: nextRetry * 2));
// 更新重试次数
err.requestOptions.extra['retryCount'] = nextRetry;
try {
final response = await _dio.fetch(err.requestOptions);
handler.resolve(response);
} catch (e) {
handler.reject(e as DioException);
}
}
}
高级技巧七:进度监听
上传进度
final response = await _dio.post(
'/api/upload',
data: MultipartFile.fromFileSync(
'file_path',
filename: 'test.png',
),
onSendProgress: (int sent, int total) {
final progress = (sent / total) * 100;
print('上传进度: ${progress.toStringAsFixed(2)}%');
},
);
下载进度
await _dio.download(
'https://example.com/file.zip',
'/path/to/save/file.zip',
onReceiveProgress: (int received, int total) {
final progress = (received / total) * 100;
print('下载进度: ${progress.toStringAsFixed(2)}%');
},
);
实战案例:完整的网络请求服务
class UserService {
static const String _basePath = '/api/users';
static Future<User> getUser(int id) async {
final response = await ApiService.get<Map<String, dynamic>>('$_basePath/$id');
return User.fromJson(response.data!);
}
static Future<List<User>> getUsers({int page = 1, int limit = 10}) async {
final response = await ApiService.get<Map<String, dynamic>>(
_basePath,
queryParameters: {'page': page, 'limit': limit},
);
final data = response.data!['data'] as List;
return data.map((e) => User.fromJson(e as Map<String, dynamic>)).toList();
}
static Future<User> createUser(User user) async {
final response = await ApiService.post<Map<String, dynamic>>(
_basePath,
data: user.toJson(),
);
return User.fromJson(response.data!);
}
static Future<User> updateUser(int id, User user) async {
final response = await ApiService.put<Map<String, dynamic>>(
'$_basePath/$id',
data: user.toJson(),
);
return User.fromJson(response.data!);
}
static Future<void> deleteUser(int id) async {
await ApiService.delete('$_basePath/$id');
}
}
class User {
final int id;
final String name;
final String email;
final String avatar;
User({
required this.id,
required this.name,
required this.email,
required this.avatar,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
avatar: json['avatar'] as String,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'avatar': avatar,
};
}
}
实战案例:网络状态管理
import 'package:flutter_bloc/flutter_bloc.dart';
abstract class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final List<User> users;
UserLoaded({required this.users});
}
class UserError extends UserState {
final String message;
UserError({required this.message});
}
class UserCubit extends Cubit<UserState> {
UserCubit() : super(UserInitial());
Future<void> fetchUsers() async {
emit(UserLoading());
try {
final users = await UserService.getUsers();
emit(UserLoaded(users: users));
} catch (e) {
emit(UserError(message: e.toString()));
}
}
}
常见问题与解决方案
Q1:如何处理 SSL 证书问题?
A:在 Dio 中配置证书:
Dio dio = Dio();
dio.httpClientAdapter = Http2Adapter(
ConnectionManager(
onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
),
);
Q2:如何设置代理?
A:在 Dio 的 BaseOptions 中配置:
BaseOptions options = BaseOptions(
proxy: 'http://proxy.example.com:8080',
);
Dio dio = Dio(options);
Q3:如何处理 Cookie?
A:使用 cookie_jar 包:
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:cookie_jar/cookie_jar.dart';
final cookieJar = CookieJar();
_dio.interceptors.add(CookieManager(cookieJar));
性能优化技巧
- 使用连接池:复用 HTTP 连接
- 设置合理的超时时间:避免长时间等待
- 启用 gzip 压缩:减少数据传输量
- 缓存策略:减少重复请求
- 请求合并:合并多个相似请求
总结
网络请求是 Flutter 应用开发的核心功能之一。通过本文的学习,你应该能够:
- 创建封装良好的网络服务类
- 使用拦截器处理请求和响应
- 实现请求取消和重试机制
- 配置缓存策略
- 监听上传/下载进度
掌握这些高级技巧,能够帮助你构建更加稳定、高效的网络请求系统。
更多推荐
所有评论(0)