用于缓存 API 响应的 Flutter Dio 拦截器
I’m happy to translate the article for you, but I need the text you’d like translated. Could you please paste the content (or the portion you want translated) here? I’ll then provide the Simplified Chinese version while keeping the source link and formatting unchanged.
缓存拦截器的工作原理
| 功能 | 描述 |
|---|---|
| 持久化 GET 响应 | 仅缓存 GET 方法的响应,因为这些通常代表安全可缓存的数据检索操作。 |
| 网络优先策略 | 当网络连接可用时,拦截器会从 API 获取最新数据。 |
| 回退到缓存 | 如果发生网络错误,拦截器会自动回退到之前缓存的响应。 |
| 自动缓存更新 | 成功的响应会自动更新缓存,以供以后离线访问。 |
1️⃣ 存储抽象层
创建存储抽象层可以让您在不修改拦截器逻辑的情况下,灵活地更换底层存储机制(例如 SharedPreferences、Hive、Secure Storage)。
/// Simple key‑value storage interface for caching
abstract class CacheStorage {
Future get(String key);
Future set(String key, String value);
Future remove(String key);
}
/// Implementation using SharedPreferences (via AppPreference)
class CacheStorageImpl implements CacheStorage {
CacheStorageImpl(this._pref);
final AppPreference _pref;
@override
Future get(String key) async => _pref.getString(key);
@override
Future set(String key, String value) async =>
await _pref.setString(key, value);
@override
Future remove(String key) async => await _pref.remove(key);
}
2️⃣ 缓存拦截器实现
import 'dart:convert';
import 'package:dio/dio.dart';
class CacheInterceptor implements Interceptor {
final CacheStorage storage;
CacheInterceptor(this.storage);
// -------------------------------------------------
// 1️⃣ onRequest – let the request pass through unchanged
// -------------------------------------------------
@override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) {
handler.next(options);
}
// -------------------------------------------------
// 2️⃣ onResponse – cache successful GET responses
// -------------------------------------------------
@override
void onResponse(
Response response, ResponseInterceptorHandler handler) {
if (response.requestOptions.method.toUpperCase() == 'GET') {
_saveResponseToCache(response);
}
handler.next(response);
}
// -------------------------------------------------
// 3️⃣ onError – try to serve cached data on network errors
// -------------------------------------------------
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
// Attempt to retrieve cached data for GET requests
if (err.requestOptions.method.toUpperCase() == 'GET') {
final cached = await _getCachedResponse(err.requestOptions);
if (cached != null) {
// Return cached response instead of the error
return handler.resolve(cached);
}
}
// Show a “no internet” modal for network‑related errors
if (_isNetworkError(err)) {
await NoInternetModalWidget.show();
}
// Propagate the original error if no cache is available
handler.next(err);
}
// -------------------------------------------------
// Helper methods
// -------------------------------------------------
bool _isNetworkError(DioException err) {
return err.type == DioExceptionType.connectionTimeout ||
err.type == DioExceptionType.sendTimeout ||
err.type == DioExceptionType.receiveTimeout ||
err.type == DioExceptionType.connectionError;
}
Future _getCachedResponse(RequestOptions options) async {
try {
final cacheKey = '${options.uri}';
final cacheEntry = await storage.get(cacheKey);
if (cacheEntry == null) return null;
final Map decodedCache = jsonDecode(cacheEntry);
return Response(
requestOptions: options,
data: decodedCache,
statusCode: 200,
);
} catch (_) {
// Corrupted cache – ignore and let the error propagate
return null;
}
}
Future _saveResponseToCache(Response response) async {
final cacheKey = '${response.realUri}';
final cacheEntry = jsonEncode(response.data);
await storage.set(cacheKey, cacheEntry);
}
}
生命周期方法概述
| 方法 | 功能说明 |
|---|---|
| onRequest | 不做任何修改地传递请求。 |
| onResponse | 如果请求是 GET,则使用完整的 URI 作为键将响应存入缓存。 |
| onError | 对于失败的 GET 请求,尝试获取缓存的响应;如果找到,则用缓存数据解决错误;否则(网络错误)显示 “无网络” 模态框并向上传递错误。 |
3️⃣ 使用拦截器与 Dio
import 'package:dio/dio.dart';
class ApiClient {
late final Dio dio;
ApiClient(AppPreference appPreference) {
dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
),
);
// 1️⃣ Create the cache storage implementation
final cacheStorage = CacheStorageImpl(appPreference);
// 2️⃣ Add the cache interceptor
dio.interceptors.add(CacheInterceptor(cacheStorage));
// 3️⃣ (Optional) Add other interceptors such as logging, auth, etc.
dio.interceptors.add(
LogInterceptor(
requestBody: true,
responseBody: true,
),
);
}
}
现在,通过 ApiClient 发出的每个 GET 请求将会:
- 首先尝试网络 – 在可能的情况下获取最新数据。
- 缓存响应 – 将成功的结果存储以供离线使用。
- 回退到缓存 – 当网络失败时自动提供缓存的数据。
🎉 Wrap‑Up
- CacheInterceptor 提供了一种简洁、可复用的方式,为任何基于 Dio 的 Flutter 应用添加离线支持。
- 通过在
CacheStorage后抽象存储,你只需一行代码即可将SharedPreferences切换为 Hive、SQLite 或安全存储。 - 网络优先策略确保用户在线时始终看到最新信息,而在离线时仍能提供可用的功能体验。
使用 Dio 拦截器的透明缓存
class ProductRepository {
final ApiClient apiClient;
ProductRepository(this.apiClient);
Future> getProducts() async {
try {
final response = await apiClient.dio.get('/products');
return (response.data as List)
.map((json) => Product.fromJson(json))
.toList();
} catch (e) {
// Handle error or rethrow
rethrow;
}
}
}
当网络可用时,它会获取最新数据并进行缓存。
离线时,它会返回缓存的响应且不会抛出错误。
🔄 透明缓存
无需对现有 API 调用进行任何更改——缓存会自动生效。
📴 离线弹性
网络故障时会自动回退到缓存数据。
🎯 清晰架构
通过存储抽象实现关注点分离。
🔌 简单集成
与依赖注入模式的简单集成。
可能的扩展
添加时间戳以确定缓存新鲜度
Future _saveResponseToCache(Response response) async {
final cacheKey = '${response.realUri}';
final cacheData = {
'data': response.data,
'timestamp': DateTime.now().millisecondsSinceEpoch,
};
final cacheEntry = jsonEncode(cacheData);
await storage.set(cacheKey, cacheEntry);
}
实现 LRU(最近最少使用)缓存驱逐
class CacheManager {
final int maxCacheSize;
final CacheStorage storage;
Future evictOldestCache() async {
// Implementation for removing oldest cached entries
}
}
为不同端点配置不同的缓存行为
class CacheConfig {
final Duration? ttl;
final bool enabled;
CacheConfig({this.ttl, this.enabled = true});
}
// Usage
final cacheConfig = {
'/products': CacheConfig(ttl: Duration(hours: 1)),
'/user/profile': CacheConfig(enabled: false),
};
为 Dio 实现缓存拦截器可以为 Flutter 应用带来显著的好处。此方案提供了强大的离线优先体验,即使在没有网络连接的情况下,用户仍能继续访问之前加载的内容。
存储抽象模式确保了灵活性和可维护性,而拦截器则在不污染业务逻辑的前提下,顺畅地处理缓存管理的复杂性。网络优先策略在可能的情况下保证获取最新数据,同时在网络出现问题时仍能保持功能可用。
要点: 这个拦截器为构建能够优雅应对网络不稳定、并提供出色用户体验的 Flutter 应用奠定了坚实基础。先从上面的基础实现开始,然后根据具体需求进行扩展。
觉得这篇文章有帮助吗?快分享给你的 Flutter 开发伙伴们吧!
祝编码愉快!