npx skills add https://github.com/flutter/skills --skill flutter-caching在 Flutter 应用程序中实现高级缓存、离线优先的数据持久化和性能优化策略。评估应用程序需求,以选择和集成适当的本地缓存机制(内存、持久化、文件系统或设备端数据库)。配置 Android 特定的 FlutterEngine 缓存,以最小化初始化延迟。优化小部件渲染、图像缓存和滚动性能,同时遵循当前的 Flutter API 标准并避免昂贵的渲染操作。
使用以下决策树分析用户的数据保留需求,以选择合适的缓存机制:
shared_preferences。sqflite)。继续执行步骤 3。path_provider)。继续执行步骤 2。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
cached_network_image 或自定义 ImageCache)。继续执行步骤 6。暂停并询问用户: "根据您的需求,我们正在处理哪种数据类型和大小?我应该实现 SQLite、文件系统缓存还是其他策略?"
当 shared_preferences 不足以处理较大数据时,使用 path_provider 和 dart:io 将数据持久化到设备的硬盘驱动器。
import 'dart:io';
import 'package:path_provider/path_provider.dart';
class FileCacheService {
Future<String> get _localPath async {
// 使用 getTemporaryDirectory() 获取系统可清理的缓存目录
// 使用 getApplicationDocumentsDirectory() 获取持久化数据目录
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/cached_data.json');
}
Future<File> writeData(String data) async {
final file = await _localFile;
return file.writeAsString(data);
}
Future<String?> readData() async {
try {
final file = await _localFile;
return await file.readAsString();
} catch (e) {
return null; // 缓存未命中
}
}
}
对于需要比简单文件更优性能的大型数据集,使用 sqflite 实现设备端数据库。
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
class DatabaseService {
late Database _database;
Future<void> initDB() async {
_database = await openDatabase(
join(await getDatabasesPath(), 'app_cache.db'),
onCreate: (db, version) {
return db.execute(
'CREATE TABLE cache_data(id INTEGER PRIMARY KEY, key TEXT, payload TEXT)',
);
},
version: 1,
);
}
Future<void> insertCache(String key, String payload) async {
await _database.insert(
'cache_data',
{'key': key, 'payload': payload},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<String?> getCache(String key) async {
final List<Map<String, Object?>> maps = await _database.query(
'cache_data',
where: 'key = ?',
whereArgs: [key], // 必须使用 whereArgs 来防止 SQL 注入
);
if (maps.isNotEmpty) {
return maps.first['payload'] as String;
}
return null;
}
}
结合本地缓存和远程获取。首先返回缓存数据(缓存命中),然后从网络获取,更新缓存,并返回新数据。
Stream<UserProfile> getUserProfile() async* {
// 1. 检查本地缓存
final localData = await _databaseService.getCache('user_profile');
if (localData != null) {
yield UserProfile.fromJson(localData);
}
// 2. 获取远程数据
try {
final remoteData = await _apiClient.fetchUserProfile();
// 3. 更新缓存
await _databaseService.insertCache('user_profile', remoteData.toJson());
// 4. 返回新数据
yield remoteData;
} catch (e) {
// 处理网络故障;本地数据已经返回
}
}
为了在向 Android 应用添加 Flutter 屏幕时最小化 Flutter 的初始化时间,预热并缓存 FlutterEngine。
在 Application 类中预热(Kotlin):
class MyApplication : Application() {
lateinit var flutterEngine : FlutterEngine
override fun onCreate() {
super.onCreate()
flutterEngine = FlutterEngine(this)
// 可选:在执行入口点之前配置初始路由
flutterEngine.navigationChannel.setInitialRoute("/cached_route");
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)
}
}
在 Activity/Fragment 中使用(Kotlin):
// 对于 Activity
startActivity(
FlutterActivity
.withCachedEngine("my_engine_id")
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
.build(this)
)
// 对于 Fragment
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.renderMode(FlutterView.RenderMode.texture)
.shouldAttachEngineToActivity(false)
.build()
对图片缓存和滚动应用严格的约束,以防止 GPU 内存膨胀和布局传递。
ImageCache 验证: 使用 containsKey 验证缓存命中而不触发加载。
class CustomImageCache extends ImageCache {
@override
bool containsKey(Object key) {
// 检查缓存是否正在跟踪此键
return super.containsKey(key);
}
}
ScrollCacheExtent 实现: 对滚动小部件使用强类型的 ScrollCacheExtent 对象(取代已弃用的 cacheExtent 和 cacheExtentStyle)。
ListView(
// 使用 ScrollCacheExtent.pixels 进行基于像素的缓存
scrollCacheExtent: const ScrollCacheExtent.pixels(500.0),
children: [ ... ],
)
Viewport(
// 使用 ScrollCacheExtent.viewport 进行基于比例的缓存
scrollCacheExtent: const ScrollCacheExtent.viewport(0.5),
slivers: [ ... ],
)
检查生成的 UI 代码是否存在性能隐患。
saveLayer() 触发器: 确保仅在绝对必要时使用 Opacity、ShaderMask、ColorFilter 和 Clip.antiAliasWithSaveLayer。尽可能用半透明颜色或 FadeInImage 替换 Opacity。operator == 重写: 确保不要在 Widget 对象上重写 operator ==,除非它们是属性很少变化的叶子小部件。ListView 和 GridView 使用惰性构建方法(ListView.builder),并尽可能通过设置固定大小来避免内部布局传递。sqflite 查询中始终使用 whereArgs。绝不在 SQL where 子句中使用字符串插值。FlutterEngine 时,请记住它的生命周期比 Activity/Fragment 长。当不再需要时,必须显式调用 FlutterEngine.destroy() 来清理资源。ImageCache.maxByteSize。operator == 来强制缓存,因为这会将性能降低到 O(N²)。应依赖 const 构造函数。cacheExtent(双精度)或 cacheExtentStyle。始终使用 ScrollCacheExtent 对象。isolates 不受支持。不要为 Web 目标生成基于 isolate 的后台解析。每周安装量
1.0K
代码仓库
GitHub 星标数
808
首次出现
Mar 4, 2026
安全审计
安装于
codex988
github-copilot971
opencode971
cursor971
gemini-cli970
kimi-cli969
Implements advanced caching, offline-first data persistence, and performance optimization strategies in Flutter applications. Evaluates application requirements to select and integrate the appropriate local caching mechanism (in-memory, persistent, file system, or on-device databases). Configures Android-specific FlutterEngine caching to minimize initialization latency. Optimizes widget rendering, image caching, and scrolling performance while adhering to current Flutter API standards and avoiding expensive rendering operations.
Analyze the user's data retention requirements using the following decision tree to select the appropriate caching mechanism:
shared_preferences.sqflite). Proceed to Step 3.path_provider). Proceed to Step 2.cached_network_image or custom ImageCache). Proceed to Step 6.STOP AND ASK THE USER: "Based on your requirements, which data type and size are we handling? Should I implement SQLite, File System caching, or a different strategy?"
When shared_preferences is insufficient for larger data, use path_provider and dart:io to persist data to the device's hard drive.
import 'dart:io';
import 'package:path_provider/path_provider.dart';
class FileCacheService {
Future<String> get _localPath async {
// Use getTemporaryDirectory() for system-cleared cache
// Use getApplicationDocumentsDirectory() for persistent data
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/cached_data.json');
}
Future<File> writeData(String data) async {
final file = await _localFile;
return file.writeAsString(data);
}
Future<String?> readData() async {
try {
final file = await _localFile;
return await file.readAsString();
} catch (e) {
return null; // Cache miss
}
}
}
For large datasets requiring improved performance over simple files, implement an on-device database using sqflite.
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
class DatabaseService {
late Database _database;
Future<void> initDB() async {
_database = await openDatabase(
join(await getDatabasesPath(), 'app_cache.db'),
onCreate: (db, version) {
return db.execute(
'CREATE TABLE cache_data(id INTEGER PRIMARY KEY, key TEXT, payload TEXT)',
);
},
version: 1,
);
}
Future<void> insertCache(String key, String payload) async {
await _database.insert(
'cache_data',
{'key': key, 'payload': payload},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<String?> getCache(String key) async {
final List<Map<String, Object?>> maps = await _database.query(
'cache_data',
where: 'key = ?',
whereArgs: [key], // MUST use whereArgs to prevent SQL injection
);
if (maps.isNotEmpty) {
return maps.first['payload'] as String;
}
return null;
}
}
Combine local caching and remote fetching. Yield the cached data first (cache hit), then fetch from the network, update the cache, and yield the fresh data.
Stream<UserProfile> getUserProfile() async* {
// 1. Check local cache
final localData = await _databaseService.getCache('user_profile');
if (localData != null) {
yield UserProfile.fromJson(localData);
}
// 2. Fetch remote data
try {
final remoteData = await _apiClient.fetchUserProfile();
// 3. Update cache
await _databaseService.insertCache('user_profile', remoteData.toJson());
// 4. Yield fresh data
yield remoteData;
} catch (e) {
// Handle network failure; local data has already been yielded
}
}
To minimize Flutter's initialization time when adding Flutter screens to an Android app, pre-warm and cache the FlutterEngine.
Pre-warm in Application class (Kotlin):
class MyApplication : Application() {
lateinit var flutterEngine : FlutterEngine
override fun onCreate() {
super.onCreate()
flutterEngine = FlutterEngine(this)
// Optional: Configure initial route before executing entrypoint
flutterEngine.navigationChannel.setInitialRoute("/cached_route");
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)
}
}
Consume in Activity/Fragment (Kotlin):
// For Activity
startActivity(
FlutterActivity
.withCachedEngine("my_engine_id")
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
.build(this)
)
// For Fragment
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.renderMode(FlutterView.RenderMode.texture)
.shouldAttachEngineToActivity(false)
.build()
Apply strict constraints to image caching and scrolling to prevent GPU memory bloat and layout passes.
ImageCache Validation: Verify cache hits without triggering loads using containsKey.
class CustomImageCache extends ImageCache {
@override
bool containsKey(Object key) {
// Check if cache is tracking this key
return super.containsKey(key);
}
}
ScrollCacheExtent Implementation: Use the strongly-typed ScrollCacheExtent object for scrolling widgets (replaces deprecated cacheExtent and cacheExtentStyle).
ListView(
// Use ScrollCacheExtent.pixels for pixel-based caching
scrollCacheExtent: const ScrollCacheExtent.pixels(500.0),
children: [ ... ],
)
Viewport(
// Use ScrollCacheExtent.viewport for fraction-based caching
scrollCacheExtent: const ScrollCacheExtent.viewport(0.5),
slivers: [ ... ],
)
Review the generated UI code for performance pitfalls.
saveLayer() triggers: Ensure Opacity, ShaderMask, ColorFilter, and Clip.antiAliasWithSaveLayer are only used when absolutely necessary. Replace Opacity with semitransparent colors or FadeInImage where possible.operator == overrides: Ensure operator == is NOT overridden on Widget objects unless they are leaf widgets whose properties rarely change.whereArgs in sqflite queries. NEVER use string interpolation for SQL where clauses.FlutterEngine in Android, remember it outlives the Activity/Fragment. Explicitly call FlutterEngine.destroy() when it is no longer needed to clear resources.ImageCache.maxByteSize.operator == on widgets to force caching, as this degrades performance to O(N²). Rely on constructors instead.Weekly Installs
1.0K
Repository
GitHub Stars
808
First Seen
Mar 4, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex988
github-copilot971
opencode971
cursor971
gemini-cli970
kimi-cli969
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
SRE工程师指南:核心工作流程、SLO定义、监控告警与自动化实践
981 周安装
Playwright 测试最佳实践指南 - 50+ 实战模式与 TypeScript/JavaScript 示例
981 周安装
Swift协议依赖注入测试:基于协议的DI模式实现可测试代码
982 周安装
maishou 买手技能:淘宝京东拼多多抖音快手全网比价,获取商品价格优惠券
983 周安装
GSAP Utils 工具函数详解:数学运算、数组处理与动画值映射 | GSAP 开发指南
983 周安装
App Store Connect 订阅批量本地化工具 - 自动化设置多语言显示名称
983 周安装
ListView and GridView use lazy builder methods (ListView.builder) and avoid intrinsic layout passes by setting fixed sizes where possible.constcacheExtent (double) or cacheExtentStyle. ALWAYS use the ScrollCacheExtent object.isolates are not supported. Do not generate isolate-based background parsing for web targets.