Flutter 本地存储与数据库的使用和优化

由于 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数据库

一、项目初始化与配置
  1. 在 pubspec.yaml 中添加依赖:
yaml 复制代码
dependencies:
  sqflite: 
  path: 
  path_provider: # 用于获取应用的存储目录
  1. 导入依赖: 在需要使用 SQLite 的文件中导入 sqflite 和 path_provider。
dart 复制代码
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
二、数据库初始化与创建表

在 Flutter 中,初始化数据库的常见步骤包括指定数据库路径、创建表、初始化数据库和返回数据库实例。

  1. 数据库路径与打开数据库
dart 复制代码
Future<Database> openAppDatabase() async {
  final dbPath = await getDatabasesPath();
  return openDatabase(
    join(dbPath, 'app_database.db'), 
    version: 1, 
    onCreate: _onCreateDatabase,
    onUpgrade: _onUpgradeDatabase,
  );
}
  1. 创建表

在 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 中,我们可以通过简单的函数来实现增、删、查、改等操作。

  1. 插入数据
dart 复制代码
Future<int> insertUser(Database db, Map<String, dynamic> user) async {
  return await db.insert('users', user);
}
  1. 查询数据
dart 复制代码
Future<List<Map<String, dynamic>>> getUsers(Database db) async {
  return await db.query('users');
}
  1. 更新数据
dart 复制代码
Future<int> updateUser(Database db, int id, Map<String, dynamic> updatedUser) async {
  return await db.update('users', updatedUser, where: 'id = ?', whereArgs: [id]);
}
  1. 删除数据
dart 复制代码
Future<int> deleteUser(Database db, int id) async {
  return await db.delete('users', where: 'id = ?', whereArgs: [id]);
}
四、数据库升级与迁移

在应用程序更新时,可能需要对数据库表进行结构变更或数据迁移。在 SQLite 中,我们可以通过增加 version 值并在 onUpgrade 回调中执行迁移代码来完成数据库升级。

  1. 增加数据库版本

在升级数据库时,需要提升 version 值,例如从 version: 1 增加到 version: 2。

  1. 编写迁移逻辑

在 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 逻辑的顺序和条件,使得数据库版本升级过程可以适应任何历史版本的迁移。

五、数据迁移的常见问题与解决方案

在实际开发中,数据迁移可能遇到一些常见问题:

  1. 备份数据

为了在升级失败时不丢失数据,可以在升级前先备份数据库文件。

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);
  }
}
  1. 迁移数据内容

有时不仅需要更改表结构,还需要迁移表内数据。例如,重命名字段或迁移表内的某些数据。

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');
  }
}

在该示例中,通过创建临时表并将数据复制到新结构表来实现复杂的数据迁移。

六、优化数据库操作

  1. 批量插入操作:对于大量数据插入,建议使用 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);
}
  1. 索引优化:对经常查询的字段建立索引,提高查询速度。

await db.execute('CREATE INDEX idx_users_name ON users (name)');

  1. 合理控制数据库大小:定期清理过期数据,避免数据库文件过大影响性能。
  2. 保持数据库连接:避免频繁打开和关闭数据库连接,尽量保持一个全局的 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': '[email protected]'};
    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: [email protected]}]

    dbHelper.updateUser(userId, {'email': 'xx'});

    users = await dbHelper.getUsers();
    print('All users: $users');
    // flutter: All users: [{id: 1, name: junmo, age: 25, email: xx}]
}

file

sqlite

相关推荐
zonda的地盘8 小时前
Flutter PlatformViewLink vs Texture
flutter
zonda的地盘8 小时前
‌AndroidView 配置 TLHC(Texture Layer Hybrid Composition)模式指南
flutter
zonda的地盘8 小时前
‌PlatformInterface 的双向通信能力解析
flutter
90后的晨仔12 小时前
Dart 中的聚合类型与容器类型详解
前端·flutter
90后的晨仔12 小时前
dart 中的位置参数和命名参数的区别?
前端·flutter
没有机器猫的大雄12 小时前
Flutter版本的PopupWindow,可自行调整显示位置
flutter·app
90后的晨仔12 小时前
Flutter 开发遇到的一些问题
前端·flutter
恋猫de小郭14 小时前
JetBrains Terminal 又发布新架构,Android Studio 将再次迎来新终端
android·前端·flutter
Flutter社区15 小时前
Flutter 2025 年产品路线图发布.md
flutter
Flutter社区15 小时前
Flutter 2025 年产品路线图发布
flutter