You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter使用sqflite时出现database_closed异常的问题求助

解决sqflite操作时的DatabaseException(error database_closed)问题

从你的错误日志和代码来看,核心问题是在执行数据库查询/插入操作时,数据库连接已经被提前关闭。下面分析具体原因并给出针对性的解决方案:

问题根源分析

你当前的实现是每次数据库操作都新建MovieProvider实例、打开连接、执行操作、关闭连接。这种模式看似合理,但存在两个潜在风险:

  1. 频繁的打开/关闭数据库连接可能触发sqflite内部的状态异常,尤其是在异步操作未完全完成时就执行关闭;
  2. 如果存在并发操作(比如快速触发两次查询),多个独立的数据库实例可能互相干扰,导致某个连接被意外标记为已关闭。

另外,你的paginate方法还存在一个小问题:使用maps.map()转换Movie对象时,因为Iterable的map是懒加载的,没有调用.toList()的话,实际不会执行转换逻辑(不过这不是导致数据库关闭错误的直接原因)。

解决方案:改用单例模式管理数据库连接

我们可以将MovieProvider改为单例,确保整个App生命周期内只有一个数据库实例,避免频繁打开/关闭连接,同时优化批量插入的效率。

1. 修改MoviesBloc为单例并优化连接管理

import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

class MoviesBloc implements BaseBloc {
  // 单例实现
  static final MoviesBloc _instance = MoviesBloc._internal();
  factory MoviesBloc() => _instance;
  MoviesBloc._internal();

  final _moviesController = StreamController<List<Movie>>();
  Database? _moviesDB; // 改为可空类型,避免空指针
  String? _moviesPath;
  Stream<List<Movie>> get moviesStream => _moviesController.stream;
  List<Movie> _movies = [];
  List<Movie> get movies => _movies;

  int getLengthMovieList() => _movies.length;

  void clearMovieList() => _movies.clear();

  // 确保数据库只被打开一次
  Future<void> openMovieDB({String dbName = 'movies.db'}) async {
    if (_moviesDB != null && _moviesDB!.isOpen) return;
    var databasesPath = await getDatabasesPath();
    _moviesPath = join(databasesPath, dbName);
    _moviesDB = await openDatabase(
      _moviesPath!,
      version: 1,
      onCreate: (Database db, int version) async {
        await db.execute(
          'CREATE TABLE movies (Id INTEGER PRIMARY KEY AUTOINCREMENT, Title TEXT NOT NULL, imdbRating TEXT, Poster TEXT, Plot TEXT, Saved INTEGER DEFAULT 0)'
        );
      },
    );
  }

  // 安全关闭数据库
  Future<void> closeMovieDB() async {
    if (_moviesDB != null && _moviesDB!.isOpen) {
      await _moviesDB!.close();
      _moviesDB = null;
    }
  }

  @override
  void dispose() {
    _moviesController.close();
    closeMovieDB(); // 页面销毁时统一关闭数据库
  }

  Database get moviesDB {
    if (_moviesDB == null || !_moviesDB!.isOpen) {
      throw StateError('数据库未打开,请先调用openMovieDB');
    }
    return _moviesDB!;
  }
}

2. 修改MovieProvider为单例并优化操作

class MovieProvider extends MoviesBloc {
  static final MovieProvider _instance = MovieProvider._internal();
  factory MovieProvider() => _instance;
  MovieProvider._internal();

  final String _tableName = 'movies';

  Future<Movie> insert(Movie movie, {ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.ignore}) async {
    await moviesDB.insert(_tableName, movie.toMap(), conflictAlgorithm: conflictAlgorithm);
    return movie;
  }

  // 改用事务批量插入,比并行插入更高效稳定
  Future<bool> insertAll(List<Movie> movies) async {
    await moviesDB.transaction((txn) async {
      for (final movie in movies) {
        await txn.insert(_tableName, movie.toMap(), conflictAlgorithm: ConflictAlgorithm.ignore);
      }
    });
    return true;
  }

  Future<List<Movie>> paginate(int page, {int limit = 15}) async {
    print('in Paginate to SQLite');
    final maps = await moviesDB.query(
      _tableName,
      columns: ['Id', 'Title', 'imdbRating', 'Poster', 'Plot', 'Saved'],
      limit: limit,
      offset: page == 1 ? 0 : ((page - 1) * limit),
    );
    // 直接转换为List,解决懒加载问题
    return maps.map((movieMap) => Movie.fromJson(movieMap)).toList();
  }
}

3. 调整数据保存/加载方法

不再每次操作都关闭数据库,而是在App退出时统一关闭:

// 保存数据
static Future<bool> saveAllMoviesIntoSqlite(List<Movie> movies) async {
  final db = MovieProvider();
  await db.openMovieDB();
  await db.insertAll(movies);
  return true;
}

// 加载数据
static Future<Map> getAllMoviesFromSqlite(int page) async {
  final db = MovieProvider();
  await db.openMovieDB();
  final movies = await db.paginate(page);
  return {"currentPage": page, "movies": movies};
}

4. 在App根Widget中统一销毁数据库

比如在MaterialApp的State类中:

@override
void dispose() {
  MovieProvider().dispose();
  super.dispose();
}

额外优化点

  • 使用事务批量插入代替Future.wait并行插入,减少数据库IO开销,提升稳定性;
  • 增加数据库实例的非空校验,避免空指针异常;
  • 修复paginate方法中Movie对象转换的懒加载问题,确保数据正确转换。

内容的提问来源于stack exchange,提问作者Mohammad Shamsi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 06:57:47