在移动应用开发中,网络请求是连接客户端与服务器的核心环节。作为Flutter开发者,官方提供的http包是我们必须掌握的基础工具。这个轻量级、稳定且由官方维护的库,特别适合初学者上手和中小型项目使用。
http包本质上是对Dart原生HttpClient的一层封装,提供了简洁的HTTP方法API。虽然功能上不如一些第三方库丰富,但它能帮助我们理清网络请求的基本脉络,是掌握Flutter网络通信的绝佳起点。
http包的设计遵循了清晰的分层思想,这让它在不同平台上都能保持一致性:
传输层:在原生平台(Android/iOS)使用dart:io库的HttpClient,在Web平台则使用dart:html的HttpRequest。这层处理了最底层的网络通信。
适配层:通过BaseClient这个抽象类,统一了不同平台底层API的差异,为上层的调用提供了稳定接口。
应用层:也就是我们直接使用的部分,提供了像get()、post()这样的顶级函数,以及可以实例化、支持更多配置的Client类。
这种分层架构可以简单理解为:dart:io/dart:html → HttpClient → BaseClient → http包API。
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();
}
Request:包含要发起请求的所有信息(URL、方法、头部、体)Response:服务器返回的响应,包含状态码、头部和响应体StreamedResponse:用于处理流式的响应数据,比如下载大文件http包并非唯一选择,了解它能帮你更好地决策:
| 特性 | http包 | dio | http_client |
|---|---|---|---|
| 官方维护 | ✅ | ❌ | ✅ |
| API简洁度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 拦截器 | 需手动 | ✅ | 需手动 |
| 文件上传 | 基础 | ✅ | 基础 |
| 学习曲线 | 平缓 | 中等 | 陡峭 |
简单来说,http包适合学习、轻量级应用或作为理解底层原理的起点。如果需要复杂的拦截、取消请求、文件上传/下载进度等高级功能,dio会是更高效的选择。
首先在pubspec.yaml中添加依赖:
yaml复制dependencies:
flutter:
sdk: flutter
http: ^1.1.0
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();
Uri _buildUrl(String endpoint, {Map<String, dynamic>? queryParameters}) {
return Uri.parse('$_baseUrl/$endpoint').replace(
queryParameters: queryParameters,
);
}
Map<String, String> _buildHeaders({Map<String, String>? extraHeaders, String? contentType}) {
final headers = <String, String>{
'Accept': 'application/json',
'Content-Type': contentType ?? 'application/json',
};
final token = _getAuthToken();
if (token != null) {
headers['Authorization'] = 'Bearer $token';
}
if (extraHeaders != null) {
headers.addAll(extraHeaders);
}
return headers;
}
String? _getAuthToken() => null;
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);
}
}
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);
}
}
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;
}
}
void dispose() {
_client.close();
}
}
基于上面的基类,我们可以创建针对特定业务(比如用户管理)的服务类:
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;
}
}
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() {
return 'your_auth_token_here';
}
}
在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();
}
}
http.Client内部有连接池机制。我们应该在应用级别共享一个Client实例,并在应用退出时关闭它:
dart复制class HttpServiceManager {
static final HttpServiceManager _instance = HttpServiceManager._internal();
late http.Client _sharedClient;
factory HttpServiceManager() => _instance;
HttpServiceManager._internal() {
_sharedClient = http.Client();
}
http.Client get client => _sharedClient;
void dispose() => _sharedClient.close();
}
// 使用:UserService(client: HttpServiceManager().client)
网络不稳定时,自动重试能提升用户体验。下面是一个简单的指数退避重试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();
}
对于不常变的数据,缓存可以极大提升加载速度并节省流量:
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://开头flutter_secure_storage等库存储AndroidManifest.xml和Info.plist中添加网络权限在实际项目中,我发现很多开发者容易忽视Client的生命周期管理。记得在应用退出时调用dispose()方法,否则可能导致资源泄漏。另外,对于频繁变动的数据,缓存策略需要谨慎设计,避免显示过期数据。