Flutter 网络请求基础:用好官方 http 包
引言
在移动应用开发中,网络请求是连接客户端与服务器的核心环节。对于 Flutter 开发者而言,官方提供的 http 包是一个绕不开的基础工具。它轻量、稳定且由官方维护,非常适合初学者上手以及用于中小型项目。
http 包本质上是对 Dart 原生 HttpClient 的一层封装,提供了诸如 GET、POST 等 HTTP 方法的简洁 API。虽然功能上不如一些第三方库丰富,但它胜在简单可靠,能帮你理清网络请求的基本脉络,是掌握 Flutter 网络通信的扎实起点。
技术分析
http 包是如何工作的?
http 包的设计遵循了清晰的分层思想,这让它在不同平台上都能有一致的行为:
- 传输层 :在原生平台(Android/iOS)使用
dart:io库的HttpClient,在 Web 平台则使用dart:html的HttpRequest。这层处理了最底层的网络通信。 - 适配层 :通过
BaseClient这个抽象类,它统一了不同平台底层 API 的差异,为上层的调用提供了一个稳定的接口。 - 应用层 :也就是我们直接打交道的部分,提供了像
get()、post()这样的顶级函数,以及可以实例化、支持更多配置的Client类。
你可以这样理解它的架构:dart:io/dart:html → HttpClient → BaseClient → 我们使用的 http 包 API。
核心类与接口一览
1. Client 类
Client 类是 http 包的核心,它实现了 BaseClient 接口。直接使用 http.get() 这样的静态函数很方便,但在需要复用连接、管理资源或添加统一拦截逻辑时,创建 Client 实例会是更好的选择。
BaseClient 接口定义了我们熟悉的所有 HTTP 方法:
dart
abstract class BaseClient {
Future<Response> head(Uri url, {Map<String, String>? headers});
Future<Response> get(Uri url, {Map<String, String>? headers});
// ... 其他 post, put, patch, delete 方法
Future<StreamedResponse> send(BaseRequest request);
void close();
}
2. Request 与 Response 对象
Request:包含了要发起请求的所有信息(URL、方法、头部、体)。Response:服务器返回的响应,包含状态码、头部和响应体。StreamedResponse:用于处理流式的响应数据,比如下载大文件。
聊聊其他网络库:如何选择?
http 包并非唯一选择,了解它能帮你更好地决策:
| 特性 | http 包 | dio | http_client |
|---|---|---|---|
| 官方维护 | ✅ 是 | ❌ 社区 | ✅ 是 |
| API 简洁度 | ⭐⭐⭐⭐⭐ 极简 | ⭐⭐⭐⭐ 丰富 | ⭐⭐⭐ 偏底层 |
| 拦截器 | 需手动实现 | ✅ 内置强大支持 | 需手动实现 |
| 文件上传 | 基础支持(MultipartRequest) |
✅ 高级易用支持 | 基础支持 |
| 学习曲线 | 平缓,极易上手 | 中等,功能多 | 陡峭,更接近原生 |
简单来说,http 包适合学习、轻量级应用或作为理解底层原理的起点 。如果你的项目需要复杂的拦截、取消请求、文件上传/下载进度等高级功能,那么 dio 会是更高效的选择。
动手实现
第一步:配置环境
在 pubspec.yaml 文件中添加依赖:
yaml
dependencies:
flutter:
sdk: flutter
http: ^1.1.0 # 核心网络库
# 如果需要处理复杂 JSON,可以加上序列化支持
json_annotation: ^4.8.1
dev_dependencies:
build_runner: ^2.4.4
json_serializable: ^6.7.0
第二步:搭建网络层基类
一个好的习惯是将网络请求逻辑封装起来。下面是一个基础的网络服务类,它处理了 URL 拼接、头部管理、错误处理和基本的 GET/POST 请求:
dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
/// 自定义的网络异常,方便区分处理
class NetworkException implements Exception {
final String message;
final int statusCode;
NetworkException(this.message, this.statusCode);
@override
String toString() => 'NetworkException: $message (状态码: $statusCode)';
}
/// 网络服务基类
abstract class BaseHttpService {
static const String _baseUrl = 'https://api.example.com';
static const Duration _defaultTimeout = Duration(seconds: 30);
final http.Client _client;
BaseHttpService({http.Client? client}) : _client = client ?? http.Client();
/// 构建完整的请求 URL
Uri _buildUrl(String endpoint, {Map<String, dynamic>? queryParameters}) {
return Uri.parse('$_baseUrl/$endpoint').replace(
queryParameters: queryParameters,
);
}
/// 构建公共请求头,例如添加认证 Token
Map<String, String> _buildHeaders({Map<String, String>? extraHeaders, String? contentType}) {
final headers = <String, String>{
'Accept': 'application/json',
'Content-Type': contentType ?? 'application/json',
};
// 示例:添加认证 Token
final token = _getAuthToken();
if (token != null) {
headers['Authorization'] = 'Bearer $token';
}
if (extraHeaders != null) {
headers.addAll(extraHeaders);
}
return headers;
}
/// 获取认证 Token,子类可重写此方法
String? _getAuthToken() => null;
/// 统一处理响应:成功则解析 JSON,失败则抛出异常
Future<dynamic> _handleResponse(http.Response response) async {
if (response.statusCode >= 200 && response.statusCode < 300) {
return response.body.isEmpty ? null : jsonDecode(response.body);
} else {
throw NetworkException('请求失败: ${response.reasonPhrase}', response.statusCode);
}
}
/// GET 请求
Future<dynamic> get(String endpoint, {Map<String, dynamic>? queryParams, Map<String, String>? headers}) async {
try {
final url = _buildUrl(endpoint, queryParameters: queryParams);
final response = await _client
.get(url, headers: _buildHeaders(extraHeaders: headers))
.timeout(_defaultTimeout);
return await _handleResponse(response);
} on http.ClientException catch (e) {
throw NetworkException('网络连接错误: ${e.message}', 0);
} on TimeoutException catch (_) {
throw NetworkException('请求超时', 408);
}
}
/// POST 请求
Future<dynamic> post(String endpoint,
{Map<String, dynamic>? body, Map<String, String>? headers, String? contentType}) async {
try {
final url = _buildUrl(endpoint);
final response = await _client
.post(url,
headers: _buildHeaders(extraHeaders: headers, contentType: contentType),
body: body != null ? jsonEncode(body) : null)
.timeout(_defaultTimeout);
return await _handleResponse(response);
} on FormatException catch (e) {
throw NetworkException('数据格式错误: ${e.message}', 400);
} catch (e) {
rethrow;
}
}
/// 记得在应用退出时关闭 Client,释放资源
void dispose() {
_client.close();
}
}
第三步:实现具体的业务 API
基于上面的基类,我们可以创建针对特定业务(比如用户管理)的服务类:
dart
/// 用户数据模型
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(id: json['id'] as int, name: json['name'] as String, email: json['email'] as String);
}
Map<String, dynamic> toJson() => {'id': id, 'name': name, 'email': email};
}
/// 用户相关的网络请求
class UserService extends BaseHttpService {
static const String _usersEndpoint = 'users';
/// 获取用户列表
Future<List<User>> getUsers({int page = 1, int limit = 10}) async {
try {
final response = await get(_usersEndpoint,
queryParams: {'page': page.toString(), 'limit': limit.toString()});
if (response is List) {
return response.map((json) => User.fromJson(json)).toList();
}
return [];
} catch (e) {
debugPrint('获取用户列表失败: $e');
rethrow; // 将异常抛给 UI 层处理
}
}
/// 创建新用户
Future<User> createUser(User user) async {
try {
final response = await post(_usersEndpoint, body: user.toJson());
return User.fromJson(response);
} catch (e) {
debugPrint('创建用户失败: $e');
rethrow;
}
}
@override
String? _getAuthToken() {
// 实际情况中,从安全存储(如 flutter_secure_storage)中获取
// return storage.get('auth_token');
return 'your_auth_token_here';
}
}
第四步:在 UI 中调用并展示
最后,在 Flutter 页面中,我们使用 UserService 来获取数据并更新界面:
dart
import 'package:flutter/material.dart';
import 'services/user_service.dart';
class UserListScreen extends StatefulWidget {
const UserListScreen({super.key});
@override
State<UserListScreen> createState() => _UserListScreenState();
}
class _UserListScreenState extends State<UserListScreen> {
final UserService _userService = UserService();
List<User> _users = [];
bool _isLoading = false;
String? _errorMessage;
@override
void initState() {
super.initState();
_loadUsers();
}
Future<void> _loadUsers() async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
final users = await _userService.getUsers();
setState(() => _users = users);
} on NetworkException catch (e) {
setState(() => _errorMessage = e.message);
_showErrorSnackbar(e.message);
} catch (e) {
setState(() => _errorMessage = '未知错误');
} finally {
setState(() => _isLoading = false);
}
}
void _showErrorSnackbar(String message) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message), backgroundColor: Colors.red));
}
Future<void> _refreshData() async => _loadUsers();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('用户列表'), actions: [
IconButton(icon: const Icon(Icons.refresh), onPressed: _refreshData),
]),
body: _buildBody(),
);
}
Widget _buildBody() {
if (_isLoading && _users.isEmpty) return const Center(child: CircularProgressIndicator());
if (_errorMessage != null && _users.isEmpty) {
return Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Text(_errorMessage!),
const SizedBox(height: 16),
ElevatedButton(onPressed: _loadUsers, child: const Text('重试')),
]),
);
}
return RefreshIndicator(
onRefresh: _refreshData,
child: ListView.builder(
itemCount: _users.length,
itemBuilder: (context, index) {
final user = _users[index];
return ListTile(
leading: CircleAvatar(child: Text(user.name[0])),
title: Text(user.name),
subtitle: Text(user.email),
onTap: () {/* 跳转详情 */},
);
},
),
);
}
@override
void dispose() {
_userService.dispose(); // 重要:释放网络客户端
super.dispose();
}
}
如何优化得更专业?
基础功能跑通后,我们可以考虑一些优化策略,让网络层更健壮、高效。
1. 管理 Client 的生命周期:连接池
http.Client 内部有连接池机制。我们应该在应用级别共享一个 Client 实例,并在应用退出时关闭它,而不是每个请求都新建一个。
dart
class HttpServiceManager {
static final HttpServiceManager _instance = HttpServiceManager._internal();
late http.Client _sharedClient;
factory HttpServiceManager() => _instance;
HttpServiceManager._internal() {
_sharedClient = http.Client(); // 全局唯一的 Client
}
http.Client get client => _sharedClient;
void dispose() => _sharedClient.close();
}
// 使用:UserService(client: HttpServiceManager().client)
2. 实现请求重试机制
网络不稳定时,自动重试能提升用户体验。下面是一个简单的指数退避重试 Client:
dart
class RetryClient extends http.BaseClient {
final http.Client _innerClient;
final int _maxRetries;
final Duration _initialDelay;
RetryClient({required http.Client innerClient, int maxRetries = 3, Duration initialDelay = const Duration(milliseconds: 500)})
: _innerClient = innerClient,
_maxRetries = maxRetries,
_initialDelay = initialDelay;
@override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
int attempt = 0;
Duration delay = _initialDelay;
while (true) {
attempt++;
try {
return await _innerClient.send(request);
} catch (error) {
// 只在网络错误、超时等情况下重试
bool shouldRetry = (error is SocketException || error is TimeoutException || error is http.ClientException) && attempt < _maxRetries;
if (!shouldRetry) rethrow;
debugPrint('请求失败,进行第$attempt次重试(延迟 ${delay.inMilliseconds}ms)');
await Future.delayed(delay);
delay *= 2; // 指数退避
}
}
}
@override
void close() => _innerClient.close();
}
3. 添加简单的内存缓存
对于不常变的数据,缓存可以极大提升加载速度并节省流量:
dart
class CachedHttpClient extends http.BaseClient {
final http.Client _innerClient;
final Map<String, (DateTime, dynamic)> _cache = {};
final Duration _defaultCacheDuration;
CachedHttpClient({required http.Client innerClient, Duration defaultCacheDuration = const Duration(minutes: 5)})
: _innerClient = innerClient,
_defaultCacheDuration = defaultCacheDuration;
@override
Future<http.Response> get(Uri url, {Map<String, String>? headers}) async {
final cacheKey = 'GET:${url.toString()}';
// 检查缓存是否存在且未过期
if (_cache.containsKey(cacheKey)) {
final (expiryTime, cachedResponse) = _cache[cacheKey]!;
if (expiryTime.isAfter(DateTime.now())) {
return cachedResponse as http.Response;
} else {
_cache.remove(cacheKey);
}
}
// 执行请求并缓存成功的结果
final response = await _innerClient.get(url, headers: headers);
if (response.statusCode == 200) {
_cache[cacheKey] = (DateTime.now().add(_defaultCacheDuration), response);
}
return response;
}
// ... 需要实现其他 send 等方法
}
一些实用的开发建议
调试技巧:记录所有网络请求
在开发阶段,一个能打印详细日志的 Client 非常有用:
dart
class LoggingClient extends http.BaseClient {
final http.Client _innerClient;
LoggingClient(this._innerClient);
@override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
final startTime = DateTime.now();
debugPrint('🚀 [HTTP 请求] ${request.method} ${request.url}');
debugPrint(' 头信息: ${request.headers}');
try {
final response = await _innerClient.send(request);
final duration = DateTime.now().difference(startTime);
debugPrint('✅ [HTTP 响应] 状态码: ${response.statusCode}, 耗时: ${duration.inMilliseconds}ms');
return response;
} catch (e) {
debugPrint('❌ [HTTP 错误]: $e');
rethrow;
}
}
@override
void close() => _innerClient.close();
}
安全与最佳实践
- 务必使用 HTTPS :生产环境的 API 地址必须是
https://开头。 - 妥善管理敏感信息 :如 API Token,应使用
flutter_secure_storage等库存储,切勿硬编码或打印到日志。 - 提供友好的用户错误提示:将网络异常状态码(如 401、500)转换为用户能理解的消息。
- 配置平台权限 :别忘了在
AndroidManifest.xml和Info.plist中添加网络权限。
总结
通过本文,我们从原理到实践,完整地梳理了如何在 Flutter 中使用官方的 http 包。你应该已经掌握了如何:
- 使用
http包进行基本的 GET/POST 请求。 - 设计一个分层、易维护的网络服务层。
- 实现健壮的错误处理和用户体验优化。
- 通过连接池、重试、缓存等策略提升网络性能。
http 包是你 Flutter 网络编程之旅的一个坚实起点。当你和你的项目一起成长,遇到需要更复杂功能(如拦截器、请求取消、更便捷的文件操作)时,你会自然地去探索像 dio 这样更强大的第三方库。但在此之前,充分理解并用好 http 包,将为你的开发打下坚实的基础。