Flutter 对象框性能问题

Flutter Objectbox performance issue

如果有人有解决办法,请帮助我。

现在我遇到了 ObjectBox 在 Flutter 中的性能问题。 我有 Event Recurrence EventPlace 的模型,每个模型都有关系。

问题是数据插入花费的时间太长,这会导致应用程序崩溃,因为操作在主线程中堆栈,直到完成它。

完成 1 个循环大约需要 300 毫秒...

我不知道该如何解决这个问题。我不应该使用模型之间的关系吗? 还是我使用 Objectbox 的方法不对?

[✓] Flutter (Channel stable, 1.22.5, on Mac OS X 10.15.7 19H524 darwin-x64, locale
    en-GB)
 
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 12.4)
[✓] Android Studio (version 4.1)
[✓] VS Code (version 1.54.3)
[✓] Connected device (1 available)
@Entity(uid: EVENT_UID)
class ObEvent {
  @Id(assignable: true)
  int id;

  String deviceEventId;
  String eventTitle;
  String eventDesc;
  String eventType;
  List<String> eventLabels;
  String timezone;
  bool isAllDay;
  String link;
  int recurrId;
  int originalRecurrId;
  bool toDateUnfixed;
  bool isOwned;
  bool editable;

  @Property(type: PropertyType.byteVector)
  List<int> remainders;

  @Index()
  int calendarId;

  @Index()
  int parentId;

  @Index()
  String usersId;

  @Index()
  @Property(type: PropertyType.date)
  DateTime eventPublishDate;

  @Index()
  @Property(type: PropertyType.date)
  DateTime eventStart;

  @Index()
  @Property(type: PropertyType.date)
  DateTime eventEnd;

  @Index()
  @Property(type: PropertyType.date)
  DateTime createdAt;

  @Index()
  @Property(type: PropertyType.date)
  DateTime updatedAt;

  @Index()
  bool deleted;

  @Index()
  bool notSynced;

  final recurrence = ToOne<ObRecurrence>();
  final place = ToOne<ObEventPlace>();

  ObEvent({
    this.id,
    this.usersId,
    this.calendarId,
    this.eventTitle,
    this.eventStart,
    this.eventEnd,
    this.deviceEventId,
    this.editable,
    this.eventType,
    this.isAllDay,
    this.toDateUnfixed,
    this.eventDesc,
    this.parentId,
    this.eventLabels,
    this.eventPublishDate,
    this.link,
    this.remainders,
    this.recurrId,
    this.originalRecurrId,
    this.createdAt,
    this.updatedAt,
    this.isOwned = true,
    this.deleted = false,
    this.notSynced = false,
    timezone,
  }) : timezone = getIt<Vars>().defaultTimezone;

  ObEvent.fromEvent(Event event)
      : this.id = event.id,
        this.usersId = event.userId,
        this.calendarId = event.calendar.calId,
        this.eventTitle = event.eventTitle,
        this.eventStart = event.eventStart,
        this.eventEnd = event.eventEnd,
        this.deviceEventId = event.deviceEventId,
        this.editable = event.editable,
        this.eventType = EnumHandler.enumToString(event.eventType),
        this.isAllDay = event.isAllDay,
        this.toDateUnfixed = event.toDateUnfixed,
        this.eventDesc = event.eventDesc,
        this.parentId = event.parentId,
        this.eventLabels = event.eventLabels,
        this.eventPublishDate = event.eventPublishDate,
        this.link = event.link,
        this.remainders = event.remainders?.toList(),
        this.recurrId = event.recurrId,
        this.originalRecurrId = event.originalRecurrId,
        this.isOwned = event.isOwned,
        this.createdAt = event.createdAt,
        this.updatedAt = event.updatedAt,
        this.timezone = event.timezone;

  ObEvent merge(ObEvent event) {
    return ObEvent(
      id: event.id ?? this.id,
      usersId: event.usersId ?? this.usersId,
      calendarId: event.calendarId ?? this.calendarId,
      eventTitle: event.eventTitle ?? this.eventTitle,
      eventStart: event.eventStart ?? this.eventStart,
      eventEnd: event.eventEnd ?? this.eventEnd,
      deviceEventId: event.deviceEventId ?? this.deviceEventId,
      editable: event.editable ?? this.editable,
      eventType: event.eventType ?? this.eventType,
      isAllDay: event.isAllDay ?? this.isAllDay,
      toDateUnfixed: event.toDateUnfixed ?? this.toDateUnfixed,
      eventDesc: event.eventDesc ?? this.eventDesc,
      parentId: event.parentId ?? this.parentId,
      eventLabels: event.eventLabels ?? this.eventLabels,
      eventPublishDate: event.eventPublishDate ?? this.eventPublishDate,
      link: event.link ?? this.link,
      remainders: event.remainders ?? this.remainders,
      recurrId: event.recurrId ?? this.recurrId,
      originalRecurrId: event.originalRecurrId ?? this.originalRecurrId,
      isOwned: event.isOwned ?? this.isOwned,
      createdAt: event.createdAt ?? this.createdAt,
      updatedAt: event.updatedAt ?? this.updatedAt,
      timezone: event.timezone ?? this.timezone,
      deleted: event.deleted ?? this.deleted,
      notSynced: event.notSynced ?? this.notSynced,
    );
  }

  // For event type
  EventType get type => eventType.toEnum(EventType.values);
  set type(EventType value) => eventType = EnumHandler.enumToString(value);
}
@Entity(uid: EVENT_PLACE_UID)
class ObEventPlace {
  @Id()
  int id;

  String address;
  double lat;
  double lon;

  ObEventPlace({
    this.id,
    this.address,
    this.lat,
    this.lon,
  });

  ObEventPlace.fromEventPlace(EventPlace place)
      : this.address = place.address,
        this.lat = place.lat,
        this.lon = place.lon;

  ObEventPlace.fromMap(Map data)
      : this.address = data['address'],
        this.lat = data['lat'],
        this.lon = data['lon'];

  ObEventPlace merge(ObEventPlace ob) {
    return ObEventPlace(
      id: ob.id ?? this.id,
      address: ob.address ?? this.address,
      lat: ob.lat ?? this.lat,
      lon: ob.lon ?? this.lon,
    );
  }
}
@Entity(uid: RECURRENCE_UID)
class ObRecurrence {
  @Id(assignable: true)
  int id;

  String freq;
  int intval;
  List<String> byDay;
  String timezone;

  @Property(type: PropertyType.date)
  DateTime dtStart;
  @Property(type: PropertyType.date)
  DateTime dtEnd;
  @Property(type: PropertyType.byteVector)
  List<int> byMonth;
  @Property(type: PropertyType.byteVector)
  List<int> byMonthDay;
  @Property(type: PropertyType.byteVector)
  List<int> setPositions;
  @Property(type: PropertyType.byteVector)
  List<int> exDates;

  ObRecurrence({
    this.id,
    this.freq,
    this.intval,
    this.dtStart,
    this.dtEnd,
    this.byDay,
    this.byMonth,
    this.byMonthDay,
    this.setPositions,
    this.timezone,
    this.exDates,
  });

  ObRecurrence.fromRecurr(Recurrence recurr)
      : this.id = recurr.id,
        this.freq = EnumHandler.enumToString(recurr.freq),
        this.intval = recurr.intval,
        this.dtStart = recurr.dtStart,
        this.dtEnd = recurr.dtEnd,
        this.byDay = recurr.byDay?.map((day) => EnumHandler.enumToString(day))?.toList(),
        this.byMonth = recurr.byMonth?.toList(),
        this.byMonthDay = recurr.byMonthDay?.toList(),
        this.setPositions = recurr.setPositions?.toList(),
        this.timezone = recurr.timezone,
        this.exDates = recurr.exDates?.map((date) => date.millisecondsSinceEpoch)?.toList();

  ObRecurrence merge(ObRecurrence rec) {
    return ObRecurrence(
      id: rec.id ?? this.id,
      freq: rec.freq ?? this.freq,
      intval: rec.intval ?? this.intval,
      dtStart: rec.dtStart ?? this.dtStart,
      dtEnd: rec.dtEnd ?? this.dtEnd,
      byDay: rec.byDay ?? this.byDay,
      byMonth: rec.byMonth ?? this.byMonth,
      byMonthDay: rec.byMonthDay ?? this.byMonthDay,
      setPositions: rec.setPositions ?? this.setPositions,
      timezone: rec.timezone ?? this.timezone,
      exDates: rec.exDates ?? this.exDates,
    );
  }

  // For freq
  Freq get frequency => freq.toEnum(Freq.values);
  set frequency(Freq value) => EnumHandler.enumToString(value);

  // For byDay field
  Set<Day> get bydayList => byDay?.map((day) => day.toEnum(Day.values))?.toSet() ?? Set<Day>();
  set bydayList(Set<Day> value) => byDay = value.map((day) => EnumHandler.enumToString(day)).toList();

  // For exdates field
  Set<DateTime> get exdates =>
      exDates?.map((date) => DateTime.fromMillisecondsSinceEpoch(date))?.toSet() ?? Set<DateTime>();
  set exdates(Set<DateTime> value) => exDates = value.map((date) => date.millisecondsSinceEpoch).toList();

  MonthlyType get monthlyType => frequency == Freq.MONTHLY
      ? setPositions.isEmpty
          ? MonthlyType.BY_DATE
          : MonthlyType.BY_WEEK_DAY
      : MonthlyType.BY_DATE;
}
class Repository {
  Store store;
  Box<ObCalendar> _calBox;
  Box<ObEvent> _eventBox;
  Box<ObCalendarLink> _calLinkBox;
  Box<ObTag> _tagBox;
  Box<ObEventPlace> _placeBox;

  CalendarRepository({
    @required this.store,
  })  : this._calBox = store.box<ObCalendar>(),
        this._eventBox = store.box<ObEvent>(),
        this._calLinkBox = store.box<ObCalendarLink>(),
        this._tagBox = store.box<ObTag>(),
        this._placeBox = store.box<ObEventPlace>(),
        this._recBox = store.box<ObObRecurrence>();

  cacheEvents(data, user) async {
    final _obEvents = data.fold(<ObEvent>[], (prev, event) {
      // This is the modal used in application and properties are the same with ObEvent class
      final Event _event = Event.fromMap(user?.id, event);
      final ObEvent _obOldEvent = _eventBox.get(_event.id) ?? ObEvent();
      final ObEvent _obNewEvent = _obOldEvent.merge(ObEvent.fromEvent(_event))
        ..deleted = false
        ..notSynced = false;

      // No update
      if (_obOldEvent.id != null && (_event.updatedAt?.compareTo(_obNewEvent.updatedAt) ?? 0) == 0) {
        return prev;
      }

      // Update place cache of event
      if (_event.place != null) {
        final ObEventPlace _oldPlace = _obNewEvent.place.target ?? ObEventPlace();
        final ObEventPlace _newPlace = _oldPlace.merge(_oldPlace);

        _placeBox.put(_newPlace);
        _obNewEvent.place.target = _newPlace;
      }

      // Cache reucrrence if exists
      if (_event.recurrence != null) {
        final ObRecurrence _obOldRecurr = _obNewEvent.recurrence.target ?? ObRecurrence();
        final ObRecurrence _obNewRecurr = _obOldRecurr.merge(ObRecurrence.fromRecurr(_event.recurrence));

        _recBox.put(_obNewRecurr);
        _obNewEvent..recurrence.target = _obNewRecurr;
      }

      prev.add(_obNewEvent);

      return prev;
    });

    _eventBox.putMany(_obEvents);
  }
}

临时我在其他线程中使用 compute 执行此操作以防止崩溃,但如果我真的可以获得基准文档所说的性能,我不需要这样做。

我看到的最大问题是在循环中执行许多写入操作 (put()) 时没有使用 Transaction。为此,请使用 Store.runInTransaction(). Also, there is more information in ObjectBox-Java documentation 并且它也适用于 Dart。

简而言之,您可以将整个 cacheEvents() 包装在单个写入事务中,例如:

cacheEvents(data, user) => store.runInTransaction(TxMode.write, () {
  // existing code
});

在那种情况下,你真的不需要使用 putMany(),它只是一个实用函数,如果你只插入一个“实体”,它会为你做交易。您可以在交易中使用纯 put()。所以你的代码可能看起来像这样(未经测试,只是出于我的头脑):

cacheEvents(data, user) =>
    store.runInTransaction(TxMode.write, () =>
        data.forEach((event) {
          // This is the modal used in application and properties are the same with ObEvent class
          final Event _event = Event.fromMap(user?.id, event);
          final ObEvent _obOldEvent = _eventBox.get(_event.id) ?? ObEvent();
          final ObEvent _obNewEvent = _obOldEvent.merge(ObEvent.fromEvent(_event))
            ..deleted = false
            ..notSynced = false;

          // No update
          if (_obOldEvent.id != null && (_event.updatedAt?.compareTo(_obNewEvent.updatedAt) ?? 0) == 0) {
            return;
          }

          // Update place cache of event
          if (_event.place != null) {
            final ObEventPlace _oldPlace = _obNewEvent.place.target ?? ObEventPlace();
            final ObEventPlace _newPlace = _oldPlace.merge(_oldPlace);

            _placeBox.put(_newPlace);
            _obNewEvent.place.target = _newPlace;
          }

          // Cache reucrrence if exists
          if (_event.recurrence != null) {
            final ObRecurrence _obOldRecurr = _obNewEvent.recurrence.target ?? ObRecurrence();
            final ObRecurrence _obNewRecurr = _obOldRecurr.merge(ObRecurrence.fromRecurr(_event.recurrence));

            _recBox.put(_obNewRecurr);
            _obNewEvent..recurrence.target = _obNewRecurr;
          }
          _eventBox.put(_obNewEvent);
        }));

还有其他几个 suggestions/questions,虽然他们不会有这么大的影响:

  • 考虑更改 .merge() 以实际更新 this 对象而不是创建新对象
  • 考虑一下您是否需要所有这些索引:索引虽然有利于查询,但也会增加 insert/update 数据的时间,因为每个索引也需要更新。您是否对所有索引属性使用带条件的查询?
  • 您也可以使用 eventBox.getMany() 首先获取您要接触的所有事件 - 虽然在这种情况下这可能不会对性能产生太大影响

顺便说一句,为什么您拥有所有这些“桥梁”类,例如 ObEvent 而不是将 Event 定义为 @Entity()?很高兴知道是否可以在 ObjectBox 方面做一些事情,这样您就不必这样做了。