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': '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}]
}

file

sqlite

相关推荐
旭日猎鹰1 小时前
Flutter踩坑记录(三)-- 更改入口执行文件
flutter
旭日猎鹰1 小时前
Flutter踩坑记录(一)debug运行生成的项目,不能手动点击运行
flutter
️ 邪神1 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】自定义View
flutter·ios·鸿蒙·reactnative·anroid
比格丽巴格丽抱13 小时前
flutter项目苹果编译运行打包上线
flutter·ios
SoaringHeart13 小时前
Flutter进阶:基于 MLKit 的 OCR 文字识别
前端·flutter
AiFlutter17 小时前
Flutter通过 Coap发送组播
flutter
嘟嘟叽2 天前
初学 flutter 环境变量配置
flutter
iFlyCai2 天前
深入理解Flutter生命周期函数之StatefulWidget(一)
flutter·生命周期·dart·statefulwidget
sunly_2 天前
Flutter:photo_view图片预览功能
android·javascript·flutter
Summer不秃2 天前
Flutter中sqflite的使用案例
flutter