memory leak tracking & fixes
This commit is contained in:
parent
bca78a0669
commit
c5fde95c73
19 changed files with 118 additions and 40 deletions
|
@ -4,7 +4,8 @@ class AnalysisController {
|
|||
final bool canStartService, force;
|
||||
final int progressTotal, progressOffset;
|
||||
final List<int>? entryIds;
|
||||
final ValueNotifier<bool> stopSignal;
|
||||
|
||||
final ValueNotifier<bool> _stopSignal = ValueNotifier(false);
|
||||
|
||||
AnalysisController({
|
||||
this.canStartService = true,
|
||||
|
@ -12,8 +13,24 @@ class AnalysisController {
|
|||
this.force = false,
|
||||
this.progressTotal = 0,
|
||||
this.progressOffset = 0,
|
||||
ValueNotifier<bool>? stopSignal,
|
||||
}) : stopSignal = stopSignal ?? ValueNotifier(false);
|
||||
}) {
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectCreated(
|
||||
library: 'aves',
|
||||
className: '$AnalysisController',
|
||||
object: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool get isStopping => stopSignal.value;
|
||||
void dispose() {
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
_stopSignal.dispose();
|
||||
}
|
||||
|
||||
bool get isStopping => _stopSignal.value;
|
||||
|
||||
void enableStopSignal() => _stopSignal.value = true;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,13 @@ mixin SourceBase {
|
|||
|
||||
abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, PlaceMixin, StateMixin, LocationMixin, TagMixin, TrashMixin {
|
||||
CollectionSource() {
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectCreated(
|
||||
library: 'aves',
|
||||
className: '$CollectionSource',
|
||||
object: this,
|
||||
);
|
||||
}
|
||||
settings.updateStream.where((event) => event.key == SettingKeys.localeKey).listen((_) => invalidateAlbumDisplayNames());
|
||||
settings.updateStream.where((event) => event.key == SettingKeys.hiddenFiltersKey).listen((event) {
|
||||
final oldValue = event.oldValue;
|
||||
|
@ -76,6 +83,14 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
|||
});
|
||||
}
|
||||
|
||||
@mustCallSuper
|
||||
void dispose() {
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
_rawEntries.forEach((v) => v.dispose());
|
||||
}
|
||||
|
||||
final EventBus _eventBus = EventBus();
|
||||
|
||||
@override
|
||||
|
@ -447,7 +462,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
|||
|
||||
Future<void> analyze(AnalysisController? analysisController, {Set<AvesEntry>? entries}) async {
|
||||
final todoEntries = entries ?? visibleEntries;
|
||||
final _analysisController = analysisController ?? AnalysisController();
|
||||
final defaultAnalysisController = AnalysisController();
|
||||
final _analysisController = analysisController ?? defaultAnalysisController;
|
||||
final force = _analysisController.force;
|
||||
if (!_analysisController.isStopping) {
|
||||
var startAnalysisService = false;
|
||||
|
@ -481,6 +497,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
|||
updateDerivedFilters(todoEntries);
|
||||
}
|
||||
}
|
||||
defaultAnalysisController.dispose();
|
||||
state = SourceState.ready;
|
||||
}
|
||||
|
||||
|
|
|
@ -109,9 +109,11 @@ class Analyzer {
|
|||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
_stopUpdateTimer();
|
||||
_controller?.dispose();
|
||||
_serviceStateNotifier.removeListener(_onServiceStateChanged);
|
||||
_source.stateNotifier.removeListener(_onSourceStateChanged);
|
||||
_stopUpdateTimer();
|
||||
_source.dispose();
|
||||
}
|
||||
|
||||
Future<void> start(dynamic args) async {
|
||||
|
@ -125,13 +127,13 @@ class Analyzer {
|
|||
progressOffset = args['progressOffset'];
|
||||
}
|
||||
debugPrint('$runtimeType start for ${entryIds?.length ?? 'all'} entries, at $progressOffset/$progressTotal');
|
||||
_controller?.dispose();
|
||||
_controller = AnalysisController(
|
||||
canStartService: false,
|
||||
entryIds: entryIds,
|
||||
force: force,
|
||||
progressTotal: progressTotal,
|
||||
progressOffset: progressOffset,
|
||||
stopSignal: ValueNotifier(false),
|
||||
);
|
||||
|
||||
settings.systemLocalesFallback = await deviceService.getLocales();
|
||||
|
@ -160,7 +162,7 @@ class Analyzer {
|
|||
await _stopPlatformService();
|
||||
_serviceStateNotifier.value = AnalyzerState.stopped;
|
||||
case AnalyzerState.stopped:
|
||||
_controller?.stopSignal.value = true;
|
||||
_controller?.enableStopSignal();
|
||||
_stopUpdateTimer();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,5 +90,6 @@ Future<AvesEntry?> _getWidgetEntry(int widgetId, bool reuseEntry) async {
|
|||
if (entry != null) {
|
||||
settings.setWidgetUri(widgetId, entry.uri);
|
||||
}
|
||||
source.dispose();
|
||||
return entry;
|
||||
}
|
||||
|
|
|
@ -196,13 +196,14 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageTransitionsBuilderNotifier.dispose();
|
||||
_tvMediaQueryModifierNotifier.dispose();
|
||||
_appModeNotifier.dispose();
|
||||
_subscriptions
|
||||
..forEach((sub) => sub.cancel())
|
||||
..clear();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_pageTransitionsBuilderNotifier.dispose();
|
||||
_tvMediaQueryModifierNotifier.dispose();
|
||||
_appModeNotifier.dispose();
|
||||
_mediaStoreSource.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -257,7 +257,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
|
||||
final controller = AnalysisController(canStartService: true, force: true);
|
||||
final collection = context.read<CollectionLens>();
|
||||
collection.source.analyze(controller, entries: entries);
|
||||
collection.source.analyze(controller, entries: entries).then((_) => controller.dispose());
|
||||
|
||||
_browse(context);
|
||||
}
|
||||
|
|
|
@ -89,9 +89,11 @@ abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> exten
|
|||
}
|
||||
|
||||
void _clearChooserOverlayEntry() {
|
||||
if (_chooserOverlayEntry != null) {
|
||||
_chooserOverlayEntry!.remove();
|
||||
_chooserOverlayEntry = null;
|
||||
final overlayEntry = _chooserOverlayEntry;
|
||||
_chooserOverlayEntry = null;
|
||||
if (overlayEntry != null) {
|
||||
overlayEntry.remove();
|
||||
overlayEntry.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -180,9 +180,12 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
|
|||
|
||||
void _onScaleEnd(ScaleEndDetails details) {
|
||||
if (_scaledSizeNotifier == null) return;
|
||||
if (_overlayEntry != null) {
|
||||
_overlayEntry!.remove();
|
||||
_overlayEntry = null;
|
||||
|
||||
final overlayEntry = _overlayEntry;
|
||||
_overlayEntry = null;
|
||||
if (overlayEntry != null) {
|
||||
overlayEntry.remove();
|
||||
overlayEntry.dispose();
|
||||
}
|
||||
|
||||
_applyingScale = true;
|
||||
|
|
|
@ -38,6 +38,12 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
_startDbReport();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposeLoadedContent();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
@ -63,7 +69,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => metadataDb.reset().then((_) => _startDbReport()),
|
||||
onPressed: () => metadataDb.reset().then((_) => _reload()),
|
||||
child: const Text('Reset'),
|
||||
),
|
||||
],
|
||||
|
@ -86,7 +92,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => metadataDb.clearEntries().then((_) => _startDbReport()),
|
||||
onPressed: () => metadataDb.clearEntries().then((_) => _reload()),
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
|
@ -107,7 +113,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => metadataDb.clearDates().then((_) => _startDbReport()),
|
||||
onPressed: () => metadataDb.clearDates().then((_) => _reload()),
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
|
@ -128,7 +134,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => metadataDb.clearCatalogMetadata().then((_) => _startDbReport()),
|
||||
onPressed: () => metadataDb.clearCatalogMetadata().then((_) => _reload()),
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
|
@ -149,7 +155,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => metadataDb.clearAddresses().then((_) => _startDbReport()),
|
||||
onPressed: () => metadataDb.clearAddresses().then((_) => _reload()),
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
|
@ -170,7 +176,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => metadataDb.clearTrashDetails().then((_) => _startDbReport()),
|
||||
onPressed: () => metadataDb.clearTrashDetails().then((_) => _reload()),
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
|
@ -191,7 +197,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => vaults.clear().then((_) => _startDbReport()),
|
||||
onPressed: () => vaults.clear().then((_) => _reload()),
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
|
@ -212,7 +218,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => favourites.clear().then((_) => _startDbReport()),
|
||||
onPressed: () => favourites.clear().then((_) => _reload()),
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
|
@ -233,7 +239,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => covers.clear().then((_) => _startDbReport()),
|
||||
onPressed: () => covers.clear().then((_) => _reload()),
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
|
@ -254,7 +260,7 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => metadataDb.clearVideoPlayback().then((_) => _startDbReport()),
|
||||
onPressed: () => metadataDb.clearVideoPlayback().then((_) => _reload()),
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
|
@ -268,6 +274,11 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _reload() async {
|
||||
await _disposeLoadedContent();
|
||||
_startDbReport();
|
||||
}
|
||||
|
||||
void _startDbReport() {
|
||||
_dbFileSizeLoader = metadataDb.dbFileSize();
|
||||
_dbEntryLoader = metadataDb.loadEntries();
|
||||
|
@ -282,6 +293,10 @@ class _DebugAppDatabaseSectionState extends State<DebugAppDatabaseSection> with
|
|||
setState(() {});
|
||||
}
|
||||
|
||||
Future<void> _disposeLoadedContent() async {
|
||||
(await _dbEntryLoader).forEach((v) => v.dispose());
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
|
|
@ -47,14 +47,17 @@ class _DebugGeneralSectionState extends State<DebugGeneralSection> with Automati
|
|||
SwitchListTile(
|
||||
value: _taskQueueOverlayEntry != null,
|
||||
onChanged: (v) {
|
||||
_taskQueueOverlayEntry?.remove();
|
||||
final overlayEntry = _taskQueueOverlayEntry;
|
||||
_taskQueueOverlayEntry = null;
|
||||
if (overlayEntry != null) {
|
||||
overlayEntry.remove();
|
||||
overlayEntry.dispose();
|
||||
}
|
||||
if (v) {
|
||||
_taskQueueOverlayEntry = OverlayEntry(
|
||||
builder: (context) => const DebugTaskQueueOverlay(),
|
||||
);
|
||||
Overlay.of(context).insert(_taskQueueOverlayEntry!);
|
||||
} else {
|
||||
_taskQueueOverlayEntry = null;
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
|
|
|
@ -163,7 +163,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
|||
_overlayAnimationController.dispose();
|
||||
_overlayVisible.removeListener(_onOverlayVisibleChanged);
|
||||
_mapController.dispose();
|
||||
_selectedIndexNotifier.removeListener(_onThumbnailIndexChanged);
|
||||
_selectedIndexNotifier.dispose();
|
||||
regionCollection?.dispose();
|
||||
// provided collection should be a new instance specifically created
|
||||
// for the `MapPage` widget, so it can be safely disposed here
|
||||
|
|
|
@ -49,7 +49,7 @@ class _ViewerThumbnailPreviewState extends State<ViewerThumbnailPreview> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_entryIndexNotifier.removeListener(_onScrollerIndexChanged);
|
||||
_entryIndexNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -33,11 +33,10 @@ class VideoConductor {
|
|||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
await _disposeAll();
|
||||
_subscriptions
|
||||
..forEach((sub) => sub.cancel())
|
||||
..clear();
|
||||
_controllers.forEach((v) => v.dispose());
|
||||
await _disposeAll();
|
||||
_controllers.clear();
|
||||
if (settings.keepScreenOn == KeepScreenOn.videoPlayback) {
|
||||
await windowService.keepScreenOn(false);
|
||||
|
|
|
@ -73,6 +73,10 @@ class ViewStateConductor {
|
|||
entry,
|
||||
...?entry.burstEntries,
|
||||
}.map((v) => v.uri).toSet();
|
||||
_controllers.removeWhere((v) => uris.contains(v.entry.uri));
|
||||
final entryControllers = _controllers.where((v) => uris.contains(v.entry.uri)).toSet();
|
||||
entryControllers.forEach((controller) {
|
||||
_controllers.remove(controller);
|
||||
controller.dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,5 +29,6 @@ class ViewStateController with HistogramMixin {
|
|||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
viewStateNotifier.dispose();
|
||||
fullImageNotifier.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@ class _EntryPageViewState extends State<EntryPageView> with SingleTickerProvider
|
|||
void dispose() {
|
||||
_unregisterWidget(widget);
|
||||
widget.onDisposed?.call();
|
||||
_actionFeedbackChildNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -305,9 +306,11 @@ class _EntryPageViewState extends State<EntryPageView> with SingleTickerProvider
|
|||
return true;
|
||||
};
|
||||
onScaleEnd = (details) {
|
||||
if (_actionFeedbackOverlayEntry != null) {
|
||||
_actionFeedbackOverlayEntry!.remove();
|
||||
_actionFeedbackOverlayEntry = null;
|
||||
final overlayEntry = _actionFeedbackOverlayEntry;
|
||||
_actionFeedbackOverlayEntry = null;
|
||||
if (overlayEntry != null) {
|
||||
overlayEntry.remove();
|
||||
overlayEntry.dispose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -79,6 +79,7 @@ class _VideoCoverState extends State<VideoCover> {
|
|||
@override
|
||||
void dispose() {
|
||||
_unregisterWidget(widget);
|
||||
_videoCoverInfoNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,12 @@ class MagnifierGestureDetector extends StatefulWidget {
|
|||
class _MagnifierGestureDetectorState extends State<MagnifierGestureDetector> {
|
||||
final ValueNotifier<TapDownDetails?> doubleTapDetails = ValueNotifier(null);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
doubleTapDetails.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final gestureSettings = MediaQuery.gestureSettingsOf(context);
|
||||
|
|
|
@ -12,7 +12,7 @@ import 'package:media_kit_video/media_kit_video.dart';
|
|||
class MpvVideoController extends AvesVideoController {
|
||||
late Player _instance;
|
||||
late VideoStatus _status;
|
||||
bool _firstFrameRendered = false;
|
||||
bool _disposed = false, _firstFrameRendered = false;
|
||||
final ValueNotifier<VideoController?> _controllerNotifier = ValueNotifier(null);
|
||||
final List<StreamSubscription> _subscriptions = [];
|
||||
final StreamController<VideoStatus> _statusStreamController = StreamController.broadcast();
|
||||
|
@ -63,12 +63,15 @@ class MpvVideoController extends AvesVideoController {
|
|||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
assert(!_disposed);
|
||||
_disposed = true;
|
||||
await super.dispose();
|
||||
_stopListening();
|
||||
_stopStreamFetchTimer();
|
||||
await _statusStreamController.close();
|
||||
await _timedTextStreamController.close();
|
||||
await _instance.dispose();
|
||||
_controllerNotifier.dispose();
|
||||
_completedNotifier.dispose();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue