Flutter 中网络请求的核心是
http包和Dio。Dio 功能更强大,支持拦截器、取消请求和文件上传,是生产级项目的首选。
一、http 包(轻量级)
yaml
dependencies:
http: ^1.2.0
1.1 基本请求
dart
import 'package:http/http.dart' as http;
import 'dart:convert';
class HttpService {
static const baseUrl = 'https://api.example.com';
// GET 请求
static Future<List<Product>> fetchProducts() async {
final response = await http.get(
Uri.parse('$baseUrl/products'),
headers: {
'Authorization': 'Bearer ${AuthService.token}',
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
final List<dynamic> json = jsonDecode(response.body);
return json.map((e) => Product.fromJson(e)).toList();
} else {
throw HttpException('Failed: ${response.statusCode}');
}
}
// POST 请求
static Future<Order> createOrder(OrderRequest request) async {
final response = await http.post(
Uri.parse('$baseUrl/orders'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(request.toJson()),
);
if (response.statusCode == 201) {
return Order.fromJson(jsonDecode(response.body));
}
throw HttpException('Create order failed');
}
}
二、Dio(生产推荐)
yaml
dependencies:
dio: ^5.4.0
2.1 基础配置
dart
import 'package:dio/dio.dart';
class DioClient {
static DioClient? _instance;
late final Dio _dio;
DioClient._() {
_dio = Dio(BaseOptions(
baseUrl: AppConfig.apiBaseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
));
}
factory DioClient() => _instance ??= DioClient._();
Dio get dio => _dio;
}
2.2 拦截器(Interceptors)
dart
class AuthInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// 请求前:自动添加 Token
final token = AuthStorage.getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options); // 继续
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
// 响应后:统一处理业务状态码
final data = response.data;
if (data is Map && data['code'] != 0) {
handler.reject(
DioException(
requestOptions: response.requestOptions,
response: response,
message: data['message'],
),
);
} else {
handler.next(response);
}
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
// 错误处理:401 自动刷新 token
if (err.response?.statusCode == 401) {
_handleTokenExpired(err, handler);
} else {
handler.next(err);
}
}
Future<void> _handleTokenExpired(
DioException err,
ErrorInterceptorHandler handler,
) async {
try {
final newToken = await AuthService.refreshToken();
AuthStorage.saveToken(newToken);
// 重试原请求
err.requestOptions.headers['Authorization'] = 'Bearer $newToken';
final response = await DioClient().dio.fetch(err.requestOptions);
handler.resolve(response);
} catch (e) {
AuthService.logout();
handler.reject(err);
}
}
}
class LogInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
debugPrint('→ ${options.method} ${options.path}');
debugPrint(' Headers: ${options.headers}');
if (options.data != null) debugPrint(' Body: ${options.data}');
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
debugPrint('← ${response.statusCode} ${response.requestOptions.path}');
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
debugPrint('✗ Error: ${err.message}');
handler.next(err);
}
}
// 注册拦截器
_dio.interceptors.addAll([
AuthInterceptor(),
LogInterceptor(),
RetryInterceptor(dio: _dio, retries: 3), // 自动重试
]);
2.3 取消请求
dart
class ProductApiService {
final _dio = DioClient().dio;
CancelToken? _searchCancelToken;
// 搜索时取消上一次请求,避免竞态
Future<List<Product>> search(String query) async {
_searchCancelToken?.cancel('New search started');
_searchCancelToken = CancelToken();
try {
final response = await _dio.get(
'/products/search',
queryParameters: {'q': query},
cancelToken: _searchCancelToken,
);
return (response.data['items'] as List)
.map((e) => Product.fromJson(e))
.toList();
} on DioException catch (e) {
if (CancelToken.isCancel(e)) {
return []; // 请求被取消,返回空列表
}
rethrow;
}
}
}
2.4 文件上传与下载
dart
// 文件上传
Future<String> uploadAvatar(File file) async {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(
file.path,
filename: 'avatar.jpg',
contentType: MediaType('image', 'jpeg'),
),
});
final response = await _dio.post(
'/upload/avatar',
data: formData,
onSendProgress: (sent, total) {
final progress = sent / total;
debugPrint('Upload: ${(progress * 100).toStringAsFixed(1)}%');
},
);
return response.data['url'] as String;
}
// 文件下载
Future<void> downloadFile(String url, String savePath) async {
await _dio.download(
url,
savePath,
onReceiveProgress: (received, total) {
if (total != -1) {
final progress = received / total;
// 更新下载进度 UI
}
},
);
}
三、全局异常处理
dart
class NetworkException implements Exception {
final String message;
final int? statusCode;
final bool isNetworkError;
const NetworkException({
required this.message,
this.statusCode,
this.isNetworkError = false,
});
factory NetworkException.fromDioException(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.receiveTimeout:
return const NetworkException(
message: '请求超时,请检查网络连接',
isNetworkError: true,
);
case DioExceptionType.badResponse:
return NetworkException(
message: e.response?.data?['message'] ?? '服务器错误',
statusCode: e.response?.statusCode,
);
case DioExceptionType.cancel:
return const NetworkException(message: '请求已取消');
default:
return const NetworkException(
message: '网络连接失败,请检查网络',
isNetworkError: true,
);
}
}
@override
String toString() => 'NetworkException: $message (status: $statusCode)';
}
小结
| 功能 | http 包 | Dio |
|---|---|---|
| 基本请求 | ✅ | ✅ |
| 拦截器 | ❌ | ✅ |
| 取消请求 | ❌ | ✅ |
| 文件上传 | 基础 | 完整(进度、FormData) |
| 超时配置 | ✅ | ✅(更精细) |
| 推荐场景 | 简单项目 | 生产项目 |
👉 下一节:5.2 JSON 与序列化