美文网首页
flutter 数据库频繁操作 'cannot open fil

flutter 数据库频繁操作 'cannot open fil

作者: 李小轰 | 来源:发表于2022-08-24 10:22 被阅读0次

引言

本篇记录在 flutter 项目中频繁操作数据库(增删改)遇到的问题以及处理方案。

  • 场景描述
    小编在业务中有这样一个场景,监听数据流,对上万条数据对同一张表进行插库、改库操作。
    按照常规方案,小编使用了 future 对数据库操作进行包裹异步封装。

  • 异常 cannot open fileToo many open filesOOM
    当数据库操作次数达到一定量级,我们观察到控制台输出异常,甚至直接OOM ,具体日志如下:

E/SQLiteLog(15803): (14) cannot open file at line 32460 of [69906880ce]
Large object allocation failed: ashmem_create_region failed for 'large object space allocation': Too many open files

I/art     (14225): Starting a blocking GC Alloc

[log] DatabaseException(Cursor window allocation of 2048 kb failed. ) sql 'SELECT * FROM ...
DatabaseException(Cursor window allocation of 2048 kb failed

Could not allocate CursorWindow '/data/user/0/cn.rex.com/databases/rex_record.db' of size 2097152 due to error -24.

Alloc sticky concurrent mark sweep GC freed 22126(2MB) AllocSpace objects, 1(20KB) LOS objects, 39% free, 9MB/15MB, paused 696us total 17.869ms

Large object allocation failed: ashmem_create_region failed for 'large object space allocation': Too many open files

分析

这是由于大量的 future 同时对数据库进行操作,造成数据库文件 lock,加剧了死锁等待,最终等待的数据量级太大,作为压死骆驼的最后一根稻草。我们的数据库直接崩溃了。

处理方案:对 future 操作使用队列管理

像这种大量频繁的对同一个文件进行操作,我们需要使用 队列 的方式对 future 任务进行限流管理。

  • 第一步: 使用 Stream 实现 future 队列管理
    封装一个 future 队列工具, 大致代码如下,可直接 copy 使用
import 'dart:async';

/// future 操作队列工具
typedef FutureQueueFunc = Future<dynamic> Function();

class FutureQueueUtil {
  final StreamController<_FutureQueueItem> _futureQueue =
      StreamController<_FutureQueueItem>(sync: true);

  StreamSubscription? _subscription;

  void init() {
    _onListen();
  }

  void dispose() {
    _cancel();
  }

  void add(
    FutureQueueFunc func, {
    Function(dynamic result)? callback,
  }) {
    _futureQueue.sink.add(
      _FutureQueueItem(func, callback: callback),
    );
  }

  void _onListen() {
    _cancel();
    _subscription = _futureQueue.stream.asyncMap(
      (event) async {
        final result = await event.func();
        if (event.callback != null) {
          event.callback!(result);
        }
        return true;
      },
    ).listen(null);
  }

  void _cancel() {
    _subscription?.cancel();
    _subscription = null;
  }
}

class _FutureQueueItem {
  final FutureQueueFunc func;
  final Function(dynamic result)? callback;

  _FutureQueueItem(this.func, {this.callback});
}
  • 第二步: 在数据库操作的管理类中进行使用,大致结构如下
///打印记录管理
class DatabaseManager {
  static DatabaseManager? _instance;

  //数据库增删改队列操作 (增删改加入队列)
  final FutureQueueUtil _dataOptQueue = FutureQueueUtil();

  static DatabaseManager get instance {
    _instance ??= DatabaseManager._();
    return _instance!;
  }

  DatabaseManager._() {
    //初始化队列监听
    _dataOptQueue.init();
  }

  //新增或修改操作
  Future<bool> insertOrUpdate(Map data) async {
    Completer<bool> completer = Completer();
    _dataOptQueue.add(
      () async {
        //具体的数据库操作,例如:
        // await db.insert;
        // return true;
      },
      callback: (result) {
        completer.complete(result);
      },
    );
    return completer.future;
  }

  //删除操作
  Future<bool> remove() async {
    Completer<bool> completer = Completer();
    _dataOptQueue.add(
      () async {
        //具体的数据库操作,例如:
        // await db.delete;
        // return true;
      },
      callback: (result) {
        completer.complete(result);
      },
    );
    return completer.future;
  }
}

将每个增删改操作,加入队列进行管理,上一个操作完成后再进行下一个操作,避免文件的死锁等待,同时这种方案也能减轻 flutter cpu 的瞬时消耗,弱化对 UI 线程的影响。

相关文章

网友评论

      本文标题:flutter 数据库频繁操作 'cannot open fil

      本文链接:https://www.haomeiwen.com/subject/wfpagrtx.html