请求超时处理 - 鸿蒙Flutter避免应用卡死策略


概述
在网络请求中,超时处理是一个非常重要的环节。当网络不稳定或服务器响应缓慢时,如果没有超时机制,应用程序可能会一直等待,导致用户界面卡死,严重影响用户体验。
Flutter 提供了多种方式来实现请求超时处理,本文将详细介绍这些方法,并提供完整的代码示例。
为什么需要超时处理
在实际开发中,网络请求可能会因为以下原因无法及时完成:
- 网络连接不稳定
- 服务器负载过高
- 网络延迟过大
- 请求的数据量过大
如果没有超时处理,这些情况会导致:
- 用户界面卡死,无法进行任何操作
- 应用程序消耗过多资源
- 用户体验严重下降
- 可能导致应用程序崩溃
因此,合理的超时处理是构建健壮网络请求的必要条件。
使用 Future.timeout() 实现超时
Dart 的 Future 类提供了 timeout() 方法,可以为异步操作设置超时时间。
基本用法
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<dynamic> fetchWithTimeout(String url, {Duration timeout = const Duration(seconds: 10)}) async {
try {
final response = await http.get(Uri.parse(url)).timeout(timeout);
return jsonDecode(response.body);
} on TimeoutException catch (e) {
throw Exception('请求超时: $e');
} catch (e) {
throw Exception('请求失败: $e');
}
}
代码说明
timeout()方法接收一个Duration参数,表示超时时间- 如果在指定时间内
Future没有完成,会抛出TimeoutException - 需要使用
on TimeoutException catch来捕获超时异常 - 其他异常仍然由普通的
catch块处理
实际示例
try {
var data = await fetchWithTimeout(
'https://api.example.com/users',
timeout: const Duration(seconds: 5),
);
print('请求成功: $data');
} on TimeoutException {
print('请求超时,请稍后重试');
} catch (e) {
print('请求失败: $e');
}
带超时配置的 API 客户端
将超时处理封装到 API 客户端中,可以统一管理超时配置。
实现代码
class TimeoutApiClient {
final String baseUrl;
final Duration defaultTimeout;
TimeoutApiClient({
required this.baseUrl,
this.defaultTimeout = const Duration(seconds: 15),
});
Map<String, String> _buildHeaders() {
return {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
}
Future<dynamic> get(String path, {Duration? timeout, Map<String, String>? query}) async {
Uri uri = Uri.parse('$baseUrl$path');
if (query != null) {
uri = uri.replace(queryParameters: query);
}
try {
final response = await http
.get(uri, headers: _buildHeaders())
.timeout(timeout ?? defaultTimeout);
return _handleResponse(response);
} on TimeoutException {
throw RequestTimeoutException('GET请求超时');
}
}
Future<dynamic> post(String path, Map<String, dynamic> body, {Duration? timeout}) async {
try {
final response = await http
.post(
Uri.parse('$baseUrl$path'),
headers: _buildHeaders(),
body: jsonEncode(body),
)
.timeout(timeout ?? defaultTimeout);
return _handleResponse(response);
} on TimeoutException {
throw RequestTimeoutException('POST请求超时');
}
}
dynamic _handleResponse(http.Response response) {
if (response.statusCode >= 200 && response.statusCode < 300) {
return jsonDecode(response.body);
} else {
throw Exception('HTTP Error: ${response.statusCode}');
}
}
}
使用示例
final apiClient = TimeoutApiClient(baseUrl: 'https://api.example.com');
// 使用默认超时
var user = await apiClient.get('/users/1');
// 使用自定义超时
var data = await apiClient.get('/large-data', timeout: const Duration(seconds: 30));
// POST请求
var result = await apiClient.post('/users', {'name': 'John'});
自定义超时异常
创建自定义异常类可以更好地区分不同类型的错误。
实现代码
class RequestTimeoutException implements Exception {
final String message;
final DateTime timestamp;
final String? endpoint;
RequestTimeoutException(this.message, {this.endpoint})
: timestamp = DateTime.now();
String toString() {
return 'RequestTimeoutException: $message'
'${endpoint != null ? ' (端点: $endpoint)' : ''}'
' (时间: ${timestamp.toIso8601String()})';
}
}
代码说明
message:错误消息timestamp:超时发生的时间endpoint:可选的请求端点信息- 重写
toString()方法提供详细的错误信息
使用示例
try {
var data = await apiClient.get('/users');
} on RequestTimeoutException catch (e) {
print('超时发生在: ${e.timestamp}');
print('请求端点: ${e.endpoint}');
// 可以根据端点信息进行不同的处理
} catch (e) {
print('其他错误: $e');
}
不同请求类型的超时配置
不同类型的请求需要不同的超时时间,创建统一的配置类可以方便管理。
配置类实现
class TimeoutConfig {
static const Duration short = Duration(seconds: 5);
static const Duration normal = Duration(seconds: 10);
static const Duration long = Duration(seconds: 30);
static const Duration upload = Duration(seconds: 60);
static const Duration download = Duration(seconds: 120);
}
适用场景
| 超时配置 | 时间 | 适用场景 |
|---|---|---|
| short | 5秒 | 快速查询、健康检查、简单接口 |
| normal | 10秒 | 普通API请求、数据列表查询 |
| long | 30秒 | 大数据查询、复杂计算、报表生成 |
| upload | 60秒 | 文件上传、表单提交 |
| download | 120秒 | 大文件下载 |
使用示例
final client = TimeoutApiClient(baseUrl: 'https://api.example.com');
// 健康检查 - 使用短超时
var health = await client.get('/health', timeout: TimeoutConfig.short);
// 用户列表 - 使用正常超时
var users = await client.get('/users', timeout: TimeoutConfig.normal);
// 生成报表 - 使用长时间超时
var report = await client.get('/report/generate', timeout: TimeoutConfig.long);
// 上传文件 - 使用上传超时
var result = await client.post('/upload', data, timeout: TimeoutConfig.upload);
超时处理的高级技巧
1. 指数退避重试
在超时后进行重试时,可以使用指数退避策略,避免频繁重试。
Future<dynamic> fetchWithRetry(String url, {
int maxRetries = 3,
Duration initialDelay = const Duration(seconds: 1),
}) async {
int retryCount = 0;
Duration delay = initialDelay;
while (retryCount < maxRetries) {
try {
final response = await http.get(Uri.parse(url)).timeout(const Duration(seconds: 10));
return jsonDecode(response.body);
} on TimeoutException {
retryCount++;
if (retryCount >= maxRetries) {
throw RequestTimeoutException('请求超时,已重试$maxRetries次');
}
await Future.delayed(delay);
delay = delay * 2;
}
}
throw Exception('重试次数已用尽');
}
2. 动态超时调整
根据网络状态动态调整超时时间。
Future<dynamic> fetchWithDynamicTimeout(String url) async {
Duration timeout = const Duration(seconds: 10);
// 检查网络状态,如果是移动网络,使用更短的超时
if (await _isMobileNetwork()) {
timeout = const Duration(seconds: 5);
}
final response = await http.get(Uri.parse(url)).timeout(timeout);
return jsonDecode(response.body);
}
3. 超时回调
提供超时回调函数,方便在超时时执行特定操作。
Future<dynamic> fetchWithTimeoutCallback(
String url, {
Duration timeout = const Duration(seconds: 10),
void Function()? onTimeout,
}) async {
try {
final response = await http.get(Uri.parse(url)).timeout(timeout);
return jsonDecode(response.body);
} on TimeoutException {
onTimeout?.call();
throw RequestTimeoutException('请求超时');
}
}
UI 层的超时处理
在 UI 层需要为用户提供友好的超时提示。
示例代码
class TimeoutDemoPage extends StatefulWidget {
const TimeoutDemoPage({super.key});
State<TimeoutDemoPage> createState() => _TimeoutDemoPageState();
}
class _TimeoutDemoPageState extends State<TimeoutDemoPage> {
String _status = '准备就绪';
bool _loading = false;
Future<void> _fetchData() async {
setState(() {
_loading = true;
_status = '请求中...';
});
try {
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/users/1'))
.timeout(const Duration(seconds: 5));
var user = jsonDecode(response.body);
setState(() {
_status = '请求成功!\n用户名: ${user['name']}';
});
} on TimeoutException {
setState(() {
_status = '请求超时! 请检查网络连接或稍后重试';
});
_showTimeoutDialog();
} catch (e) {
setState(() {
_status = '请求失败: $e';
});
} finally {
setState(() => _loading = false);
}
}
void _showTimeoutDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('请求超时'),
content: const Text('网络请求超时,请检查网络连接后重试'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
],
),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('超时处理示例')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _loading ? null : _fetchData,
child: _loading
? const CircularProgressIndicator()
: const Text('发起请求'),
),
const SizedBox(height: 20),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(_status),
),
),
],
),
),
);
}
}
超时处理最佳实践
1. 根据请求类型设置不同超时时间
不同类型的请求需要不同的超时配置,不能一刀切。
2. 使用自定义异常类型区分超时错误
自定义异常可以携带更多信息,方便错误处理和日志记录。
3. 在 UI 层显示友好的超时提示
用户需要知道发生了什么,以及应该怎么做。
4. 为关键请求提供重试机制
对于重要的请求,可以在超时后自动重试。
5. 监控超时率优化超时配置
通过监控超时率,不断优化超时配置,找到最佳平衡点。
6. 避免设置过短的超时导致正常请求失败
超时时间设置过短会导致正常请求也失败,需要根据实际情况调整。
7. 考虑网络状态
在移动网络下,超时时间可以适当缩短;在 Wi-Fi 下,可以适当延长。
总结
超时处理是网络请求中不可或缺的一部分,合理的超时配置可以:
- 避免应用程序卡死
- 提升用户体验
- 减少资源浪费
- 提高系统稳定性
通过本文介绍的方法,你可以构建一个健壮的超时处理机制,确保你的应用在各种网络环境下都能正常运行。
完整示例代码
以下是一个完整的超时处理示例:
import 'package:http/http.dart' as http;
import 'dart:convert';
class TimeoutApiClient {
final String baseUrl;
final Duration defaultTimeout;
TimeoutApiClient({
required this.baseUrl,
this.defaultTimeout = const Duration(seconds: 10),
});
Future<dynamic> get(String path, {Duration? timeout}) async {
try {
final response = await http
.get(Uri.parse('$baseUrl$path'))
.timeout(timeout ?? defaultTimeout);
return jsonDecode(response.body);
} on TimeoutException {
throw RequestTimeoutException('GET请求超时');
}
}
Future<dynamic> post(String path, Map<String, dynamic> body, {Duration? timeout}) async {
try {
final response = await http
.post(
Uri.parse('$baseUrl$path'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(body),
)
.timeout(timeout ?? defaultTimeout);
return jsonDecode(response.body);
} on TimeoutException {
throw RequestTimeoutException('POST请求超时');
}
}
}
class RequestTimeoutException implements Exception {
final String message;
final DateTime timestamp;
RequestTimeoutException(this.message) : timestamp = DateTime.now();
String toString() {
return 'RequestTimeoutException: $message (${timestamp.toIso8601String()})';
}
}
class TimeoutConfig {
static const Duration short = Duration(seconds: 5);
static const Duration normal = Duration(seconds: 10);
static const Duration long = Duration(seconds: 30);
}
通过这个示例,你可以快速实现一个带有超时处理的 API 客户端,为你的网络请求提供可靠的超时保护。
更多推荐



所有评论(0)