网络与存储:dio 拦截器、Hive 与安全存储
mediumflutterdiohttpinterceptorhivesqliteshared-preferencesflutter-secure-storagejson-serializable
dio —— Flutter 最流行的 HTTP 客户端
flutter pub add dio retrofit retrofit_generator json_annotation
flutter pub add --dev build_runner json_serializable retrofit_generator
基础配置
class ApiClient {
late final Dio _dio;
ApiClient() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com/v1',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
headers: {'Content-Type': 'application/json'},
));
// 添加拦截器
_dio.interceptors.addAll([
AuthInterceptor(),
RetryInterceptor(dio: _dio),
LogInterceptor(requestBody: true, responseBody: true), // 调试用
]);
}
}
拦截器(Interceptor)
// 认证拦截器:自动添加 token,处理 401 刷新
class AuthInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
final token = TokenStorage.read();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options); // 放行
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401) {
try {
// 尝试刷新 token
final newToken = await refreshToken();
TokenStorage.write(newToken);
// 重试原请求
final retryResponse = await _dio.fetch(
err.requestOptions..headers['Authorization'] = 'Bearer $newToken',
);
handler.resolve(retryResponse);
} catch (_) {
// 刷新失败,跳转登录
Get.offAllNamed('/login');
handler.reject(err);
}
} else {
handler.next(err); // 其他错误继续传递
}
}
}
// 重试拦截器
class RetryInterceptor extends Interceptor {
final Dio dio;
RetryInterceptor({required this.dio});
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
final extra = err.requestOptions.extra;
final retries = extra['retries'] as int? ?? 0;
if (retries < 3 && _shouldRetry(err)) {
await Future.delayed(Duration(seconds: retries + 1));
try {
final response = await dio.fetch(
err.requestOptions..extra['retries'] = retries + 1,
);
handler.resolve(response);
return;
} catch (_) {}
}
handler.next(err);
}
bool _shouldRetry(DioException err) =>
err.type == DioExceptionType.connectionTimeout ||
err.type == DioExceptionType.receiveTimeout;
}
JSON 序列化(json_serializable)
// user.dart
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
final int id;
final String name;
@JsonKey(name: 'avatar_url') // 字段名映射
final String avatarUrl;
@JsonKey(fromJson: _dateFromJson) // 自定义转换
final DateTime createdAt;
const User({required this.id, required this.name, required this.avatarUrl, required this.createdAt});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
static DateTime _dateFromJson(String s) => DateTime.parse(s);
}
dart run build_runner build
本地存储方案对比
| 方案 | 适用场景 | 类型安全 | 性能 |
|---|---|---|---|
SharedPreferences |
少量配置(bool/int/String) | ❌ | ⭐⭐⭐ |
Hive |
大量 Dart 对象,高性能 | ✅(TypeAdapter) | ⭐⭐⭐⭐⭐ |
sqflite |
关系型数据,复杂查询 | ❌(原始 SQL) | ⭐⭐⭐⭐ |
drift |
sqflite 的类型安全封装 | ✅ | ⭐⭐⭐⭐ |
flutter_secure_storage |
敏感数据(token、密码) | ❌ | ⭐⭐ |
Hive —— 高性能 NoSQL 存储
flutter pub add hive_flutter hive
flutter pub add --dev hive_generator build_runner
// 定义数据模型
@HiveType(typeId: 0)
class UserCache extends HiveObject {
@HiveField(0)
late String id;
@HiveField(1)
late String name;
@HiveField(2)
late String avatarUrl;
}
// 生成 TypeAdapter
// dart run build_runner build
// 初始化
await Hive.initFlutter();
Hive.registerAdapter(UserCacheAdapter());
// 打开 Box
final box = await Hive.openBox<UserCache>('users');
// CRUD
box.put('user_42', UserCache()..id = '42'..name = 'Alice'..avatarUrl = '...');
final user = box.get('user_42');
box.delete('user_42');
// 监听变化(响应式)
ValueListenableBuilder<Box<UserCache>>(
valueListenable: box.listenable(),
builder: (_, box, __) {
final users = box.values.toList();
return ListView.builder(/* ... */);
},
)
flutter_secure_storage —— 敏感数据
const storage = FlutterSecureStorage();
// 写入(iOS 用 Keychain,Android 用 Keystore + EncryptedSharedPreferences)
await storage.write(key: 'access_token', value: token);
// 读取
final token = await storage.read(key: 'access_token');
// 删除
await storage.delete(key: 'access_token');
// 全部清除(退出登录时)
await storage.deleteAll();
核心考点
Q:dio 拦截器执行顺序?
interceptors.add 的顺序:请求时正序执行 onRequest;响应时逆序执行 onResponse;错误时逆序执行 onError。
Q:Hive 为什么比 SQLite 快?
Hive 使用二进制格式存储,直接内存映射文件(LazyBox 延迟加载),不需要 SQL 解析器,访问速度比 SQLite 快约 10 倍(官方测试)。但不支持复杂查询,关系型数据仍推荐 SQLite/drift。