flutter-caching
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseflutter-caching-and-performance
Flutter缓存与性能
Goal
目标
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.
在Flutter应用中实现高级缓存、离线优先的数据持久化以及性能优化策略。评估应用需求以选择并集成合适的本地缓存机制(内存缓存、持久化缓存、文件系统或设备端数据库)。配置Android专属的FlutterEngine缓存以最小化初始化延迟。在遵循当前Flutter API标准、避免高开销渲染操作的同时,优化Widget渲染、图片缓存和滚动性能。
Instructions
操作指南
1. Evaluate and Select Caching Strategy (Decision Logic)
1. 评估并选择缓存策略(决策逻辑)
Analyze the user's data retention requirements using the following decision tree to select the appropriate caching mechanism:
- Is the data temporary and only needed for the current session?
- Yes: Use In-memory caching.
- Is the data small, simple key-value pairs (e.g., user preferences)?
- Yes: Use .
shared_preferences
- Yes: Use
- Is the data large, relational, or requires complex querying?
- Yes: Use On-device databases (e.g., ). Proceed to Step 3.
sqflite
- Yes: Use On-device databases (e.g.,
- Is the data large binary files, custom documents, or JSON blobs?
- Yes: Use File system caching (). Proceed to Step 2.
path_provider
- Yes: Use File system caching (
- Is the data network images?
- Yes: Use Image caching (or custom
cached_network_image). Proceed to Step 6.ImageCache
- Yes: Use Image caching (
- Is the goal to reduce Android Flutter UI warm-up time?
- Yes: Use FlutterEngine caching. Proceed to Step 5.
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?"
使用以下决策树分析用户的数据保留需求,以选择合适的缓存机制:
- 数据是否为临时数据,仅当前会话需要使用?
- 是: 使用内存缓存。
- 数据是否为小型、简单的键值对(例如用户偏好设置)?
- 是: 使用****。
shared_preferences
- 是: 使用**
- 数据是否体量较大、为关系型数据,或需要复杂查询?
- 是: 使用设备端数据库(例如),前往步骤3。
sqflite
- 是: 使用设备端数据库(例如
- 数据是否为大型二进制文件、自定义文档或JSON blob?
- 是: 使用文件系统缓存(),前往步骤2。
path_provider
- 是: 使用文件系统缓存(
- 数据是否为网络图片?
- 是: 使用图片缓存(或自定义
cached_network_image),前往步骤6。ImageCache
- 是: 使用图片缓存(
- 目标是否为减少Android端Flutter UI预热时间?
- 是: 使用FlutterEngine缓存,前往步骤5。
请停止并询问用户: "根据您的需求,我们要处理的数据类型和大小是什么?我应该实现SQLite、文件系统缓存还是其他策略?"
2. Implement File System Caching
2. 实现文件系统缓存
When is insufficient for larger data, use and to persist data to the device's hard drive.
shared_preferencespath_providerdart:iodart
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
}
}
}当不足以存储更大体量的数据时,使用和将数据持久化到设备硬盘中。
shared_preferencespath_providerdart:iodart
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
}
}
}3. Implement SQLite Persistence
3. 实现SQLite持久化
For large datasets requiring improved performance over simple files, implement an on-device database using .
sqflitedart
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;
}
}对于相比简单文件需要更高性能的大型数据集,使用实现设备端数据库。
sqflitedart
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;
}
}4. Implement Offline-First Repository (Stream-based)
4. 实现离线优先仓库(基于Stream)
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.
dart
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
}
}结合本地缓存和远程拉取。首先返回缓存数据(缓存命中),然后从网络拉取数据,更新缓存后返回最新数据。
dart
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
}
}5. Implement FlutterEngine Caching (Android)
5. 实现FlutterEngine缓存(Android端)
To minimize Flutter's initialization time when adding Flutter screens to an Android app, pre-warm and cache the .
FlutterEnginePre-warm in Application class (Kotlin):
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):
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()为了在Android应用中添加Flutter页面时最小化Flutter的初始化时间,可预热并缓存。
FlutterEngine在Application类中预热(Kotlin):
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)
}
}在Activity/Fragment中使用(Kotlin):
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()6. Optimize Image and Scroll Caching
6. 优化图片与滚动缓存
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 .
containsKeydart
class CustomImageCache extends ImageCache {
bool containsKey(Object key) {
// Check if cache is tracking this key
return super.containsKey(key);
}
}ScrollCacheExtent Implementation:
Use the strongly-typed object for scrolling widgets (replaces deprecated and ).
ScrollCacheExtentcacheExtentcacheExtentStyledart
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: [ ... ],
)对图片缓存和滚动行为施加严格约束,避免GPU内存膨胀和多余布局遍历。
ImageCache校验:
使用验证缓存命中,无需触发加载。
containsKeydart
class CustomImageCache extends ImageCache {
bool containsKey(Object key) {
// Check if cache is tracking this key
return super.containsKey(key);
}
}ScrollCacheExtent实现:
为滚动Widget使用强类型对象(替代已废弃的和)。
ScrollCacheExtentcacheExtentcacheExtentStyledart
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: [ ... ],
)7. Validate-and-Fix Performance
7. 校验并修复性能问题
Review the generated UI code for performance pitfalls.
- Check for triggers: Ensure
saveLayer(),Opacity,ShaderMask, andColorFilterare only used when absolutely necessary. ReplaceClip.antiAliasWithSaveLayerwith semitransparent colors orOpacitywhere possible.FadeInImage - Check overrides: Ensure
operator ==is NOT overridden onoperator ==objects unless they are leaf widgets whose properties rarely change.Widget - Check Intrinsic Passes: Ensure and
ListViewuse lazy builder methods (GridView) and avoid intrinsic layout passes by setting fixed sizes where possible.ListView.builder
检查生成的UI代码是否存在性能陷阱:
- 检查触发场景: 确保仅在绝对必要时使用
saveLayer()、Opacity、ShaderMask和ColorFilter。尽可能用半透明颜色或Clip.antiAliasWithSaveLayer替代FadeInImage。Opacity - 检查重写: 确保不要在
operator ==对象上重写Widget,除非它们是属性极少变化的叶子节点Widget。operator == - 检查内联布局遍历: 确保和
ListView使用懒加载构建方法(GridView),尽可能设置固定尺寸避免内联布局遍历。ListView.builder
Constraints
约束
- SQL Injection Prevention: ALWAYS use in
whereArgsqueries. NEVER use string interpolation for SQLsqfliteclauses.where - Engine Lifecycle: When using a cached in Android, remember it outlives the
FlutterEngine/Activity. Explicitly callFragmentwhen it is no longer needed to clear resources.FlutterEngine.destroy() - Image Caching Limits: Raster cache entries are expensive to construct and use significant GPU memory. Only cache images when absolutely necessary. Do not artificially inflate .
ImageCache.maxByteSize - Widget Equality: Do not override on widgets to force caching, as this degrades performance to O(N²). Rely on
operator ==constructors instead.const - Scroll Extents: NEVER use the deprecated (double) or
cacheExtent. ALWAYS use thecacheExtentStyleobject.ScrollCacheExtent - Web Workers: If targeting Flutter Web, remember that Dart are not supported. Do not generate isolate-based background parsing for web targets.
isolates
- SQL注入预防: 在查询中始终使用
sqflite,绝不要在SQLwhereArgs子句中使用字符串插值。where - 引擎生命周期: 在Android中使用缓存的时,需注意它的生命周期长于
FlutterEngine/Activity。当不再需要时需显式调用Fragment释放资源。FlutterEngine.destroy() - 图片缓存限制: 栅格缓存条目构建成本高,会占用大量GPU内存。仅在绝对必要时缓存图片,不要人为调高。
ImageCache.maxByteSize - Widget相等性判断: 不要通过重写Widget的强制缓存,这会将性能降低到O(N²),应优先使用
operator ==构造函数。const - 滚动缓存范围: 绝不要使用已废弃的(double类型)或
cacheExtent,始终使用cacheExtentStyle对象。ScrollCacheExtent - Web Workers: 如果目标是Flutter Web,需注意Dart 不受支持,不要为Web端生成基于isolate的后台解析逻辑。
isolates