由于 Flutter 仅接管了渲染层,真正涉及到存储等操作系统底层行为时,还需要依托于原生 Android、iOS,因此与原生开发类似的,根据需要持久化数据的大小和方式不同,Flutter 提供了三种数据持久化方法,即文件 、SharedPreferences 与数据库。
文件
文件存储适合保存大文本数据、二进制文件(如图片、PDF)、JSON 或者小型数据库等。Flutter 提供了 dart:io 库来支持文件读写操作
Flutter 提供了两种文件存储的目录,即临时(Temporary)目录与文档(Documents) 目录:
临时目录是操作系统可以随时清除的目录,通常被用来存放一些不重要的临时缓存数据。 这个目录在 iOS 上对应着 NSTemporaryDirectory 返回的值,而在 Android 上则对应 着 getCacheDir 返回的值。
文档目录则是只有在删除应用程序时才会被清除的目录,通常被用来存放应用产生的重要 数据文件。在 iOS 上,这个目录对应着 NSDocumentDirectory ,而在 Android 上则对 应着 AppData 目录
dart
Future<File> _localFile(String filename) async {
final directory = await getApplicationDocumentsDirectory();
return File('${directory.path}/$filename');
}
Future<void> writeToFile(String filename, String content) async {
final file = await _localFile(filename);
await file.writeAsString(content);
}
Future<String> readFromFile(String filename) async {
try {
final file = await _localFile(filename);
return await file.readAsString();
} catch (e) {
return 'Error: $e';
}
}
void test() {
writeToFile("1.txt", "FloatingActionButton")
.then((onValue) {
readFromFile("1.txt").then(
(value) => print("value= $value"), // Output: FloatingActionButton
);
});
}
优化技巧
- 避免频繁 IO 操作:文件系统操作较慢,适合较低频率的数据读写。频繁访问的数据应使用内存缓存。
- 异步操作:使用 await 异步执行文件操作,避免阻塞主线程,保证流畅的用户体验。
- 文件缓存策略:对于缓存文件,设置自动清理机制,如限定文件数量或定期清理,避免磁盘空间占满。
SharedPreferences
SharedPreferences(Android)和 NSUserDefaults(iOS)在 Flutter 中通过 shared_preferences 插件实现,支持键值对存储,适合存储用户偏好和简单配置信息。
使用示例
dart
import 'package:shared_preferences/shared_preferences.dart';
Future<void> savePreference() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('username', 'flutter_user');
}
Future<String?> loadPreference() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('username');
}
优化技巧
- 避免频繁写操作,SharedPreferences 更适合少量、低频的数据存储。
- 尽量将数据转为字符串格式(如 JSON),便于管理和读取
SQLite数据库
一、项目初始化与配置
- 在 pubspec.yaml 中添加依赖:
yaml
dependencies:
sqflite:
path:
path_provider: # 用于获取应用的存储目录
- 导入依赖: 在需要使用 SQLite 的文件中导入 sqflite 和 path_provider。
dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
二、数据库初始化与创建表
在 Flutter 中,初始化数据库的常见步骤包括指定数据库路径、创建表、初始化数据库和返回数据库实例。
- 数据库路径与打开数据库
dart
Future<Database> openAppDatabase() async {
final dbPath = await getDatabasesPath();
return openDatabase(
join(dbPath, 'app_database.db'),
version: 1,
onCreate: _onCreateDatabase,
onUpgrade: _onUpgradeDatabase,
);
}
- 创建表
在 onCreate 中定义表结构,以 CREATE TABLE 语句创建表。
dart
void _onCreateDatabase(Database db, int version) async {
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER
)
''');
}
三、数据库的 CRUD 操作
在 Flutter 中,我们可以通过简单的函数来实现增、删、查、改等操作。
- 插入数据
dart
Future<int> insertUser(Database db, Map<String, dynamic> user) async {
return await db.insert('users', user);
}
- 查询数据
dart
Future<List<Map<String, dynamic>>> getUsers(Database db) async {
return await db.query('users');
}
- 更新数据
dart
Future<int> updateUser(Database db, int id, Map<String, dynamic> updatedUser) async {
return await db.update('users', updatedUser, where: 'id = ?', whereArgs: [id]);
}
- 删除数据
dart
Future<int> deleteUser(Database db, int id) async {
return await db.delete('users', where: 'id = ?', whereArgs: [id]);
}
四、数据库升级与迁移
在应用程序更新时,可能需要对数据库表进行结构变更或数据迁移。在 SQLite 中,我们可以通过增加 version 值并在 onUpgrade 回调中执行迁移代码来完成数据库升级。
- 增加数据库版本
在升级数据库时,需要提升 version 值,例如从 version: 1 增加到 version: 2。
- 编写迁移逻辑
在 onUpgrade 回调中,根据旧版本和新版本执行相应的迁移操作。
dart
void _onUpgradeDatabase(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
await db.execute('ALTER TABLE users ADD COLUMN email TEXT');
}
if (oldVersion < 3) {
await db.execute('ALTER TABLE users ADD COLUMN phone TEXT');
}
}
在该示例中:
- 若 oldVersion 小于 2,将 email 列添加到 users 表。
- 若 oldVersion 小于 3,则在用户表中添加 phone 列。
注意:确保 onUpgrade 逻辑的顺序和条件,使得数据库版本升级过程可以适应任何历史版本的迁移。
五、数据迁移的常见问题与解决方案
在实际开发中,数据迁移可能遇到一些常见问题:
- 备份数据
为了在升级失败时不丢失数据,可以在升级前先备份数据库文件。
dart
Future<void> backupDatabase(Database db) async {
final dbPath = await getDatabasesPath();
final originalPath = join(dbPath, 'app_database.db');
final backupPath = join(dbPath, 'app_database_backup.db');
final originalFile = File(originalPath);
if (await originalFile.exists()) {
await originalFile.copy(backupPath);
}
}
- 迁移数据内容
有时不仅需要更改表结构,还需要迁移表内数据。例如,重命名字段或迁移表内的某些数据。
dart
void _onUpgradeDatabase(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 3) {
await db.execute('ALTER TABLE users RENAME TO temp_users');
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER,
email TEXT
)
''');
await db.execute('''
INSERT INTO users (id, name, age)
SELECT id, name, age FROM temp_users
''');
await db.execute('DROP TABLE temp_users');
}
}
在该示例中,通过创建临时表并将数据复制到新结构表来实现复杂的数据迁移。
六、优化数据库操作
- 批量插入操作:对于大量数据插入,建议使用 Batch 操作来提升性能。
dart
Future<void> batchInsertUsers(Database db, List<Map<String, dynamic>> users) async {
Batch batch = db.batch();
for (var user in users) {
batch.insert('users', user);
}
await batch.commit(noResult: true);
}
- 索引优化:对经常查询的字段建立索引,提高查询速度。
await db.execute('CREATE INDEX idx_users_name ON users (name)');
- 合理控制数据库大小:定期清理过期数据,避免数据库文件过大影响性能。
- 保持数据库连接:避免频繁打开和关闭数据库连接,尽量保持一个全局的 Database 实例,提高访问效率。
七、完整示例:数据库操作类
将上述操作封装到一个数据库操作类中,使得操作数据库更加便捷。
dart
class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
factory DatabaseHelper() => _instance;
static Database? _database;
static const int _dbVersion = 3;
DatabaseHelper._internal();
Future<Database> get database async {
if (_database != null) return _database!;
_database = await openAppDatabase();
return _database!;
}
Future<Database> openAppDatabase() async {
final dbPath = await getDatabasesPath();
return openDatabase(
join(dbPath, 'app_database.db'),
version: _dbVersion,
onCreate: _onCreateDatabase,
onUpgrade: _onUpgradeDatabase,
);
}
void _onCreateDatabase(Database db, int version) async {
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER,
email TEXT
)
''');
}
void _onUpgradeDatabase(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
await db.execute('ALTER TABLE users ADD COLUMN email TEXT');
}
if (oldVersion < 3) {
await db.execute('ALTER TABLE users ADD COLUMN phone TEXT');
}
}
// 插入用户信息,并获取自增的id
Future<int> insertUser(Map<String, dynamic> user) async {
final db = await database;
// 插入数据,并返回新插入记录的自增id
return await db.insert('users', user);
}
// 查询所有用户信息
Future<List<Map<String, dynamic>>> getUsers() async {
final db = await database;
// 获取所有的用户记录
return await db.query('users');
}
Future<int> updateUser(int id, Map<String, dynamic> updatedUser) async {
final db = await database;
return await db.update('users', updatedUser, where: 'id = ?', whereArgs: [id]);
}
Future<int> deleteUser(int id) async {
final db = await database;
return await db.delete('users', where: 'id = ?', whereArgs: [id]);
}
}
dart
void test() async {
// 新增用户
final newUser = {'name': 'junmo', 'age': 25, 'email': 'junmo@gmail.com'};
final userId = await dbHelper.insertUser(newUser);
print('Inserted user with id: $userId');
// Inserted user with id: 1
// 查询所有用户
var users = await dbHelper.getUsers();
print('All users: $users');
// All users: [{id: 1, name: junmo, age: 25, email: junmo@gmail.com}]
dbHelper.updateUser(userId, {'email': 'xx'});
users = await dbHelper.getUsers();
print('All users: $users');
// flutter: All users: [{id: 1, name: junmo, age: 25, email: xx}]
}