Flutter Objectbox 性能问题

问题描述

如果有人有解决方案,请帮助我。

现在我遇到了 Flutter 中 ObjectBox性能问题。 我有 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 执行此操作以防止崩溃,但如果我真的可以像基准文档所说的那样获得性能,我不需要这样做。

解决方法

我能看到的最大问题是在循环中执行许多写入操作 (Transaction) 时没有使用 put()。为此,请使用 Store.runInTransaction()。此外,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);
        }));

还有一些其他建议/问题,尽管它们不会产生如此大的影响:

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

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