问题描述
如果有人有解决方案,请帮助我。
现在我遇到了 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 方面做一些事情,这样您就不必这样做了。