#782 manual GC before/during cataloguing

This commit is contained in:
Thibault Deckers 2024-03-11 23:05:43 +01:00
parent 0b95c80356
commit a9444b61e2
8 changed files with 64 additions and 20 deletions

View file

@ -14,6 +14,10 @@ All notable changes to this project will be documented in this file.
- disabling animations also applies to pop up menus
- upgraded Flutter to stable v3.19.3
### Fixed
- engine leak from analysis worker
## <a id="v1.10.5"></a>[v1.10.5] - 2024-02-22
### Added

View file

@ -14,7 +14,6 @@ import androidx.work.ForegroundInfo
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.calls.DebugHandler
import deckers.thibault.aves.channel.calls.DeviceHandler
import deckers.thibault.aves.channel.calls.GeocodingHandler
import deckers.thibault.aves.channel.calls.MediaStoreHandler
@ -98,7 +97,6 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
// dart -> platform -> dart
// - need Context
MethodChannel(messenger, DebugHandler.CHANNEL).setMethodCallHandler(DebugHandler(context))
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(context))
MethodChannel(messenger, GeocodingHandler.CHANNEL).setMethodCallHandler(GeocodingHandler(context))
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(context))

View file

@ -15,12 +15,15 @@ import android.util.Log
import androidx.exifinterface.media.ExifInterface
import com.drew.metadata.file.FileTypeDirectory
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.metadata.*
import deckers.thibault.aves.metadata.ExifInterfaceHelper
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper
import deckers.thibault.aves.metadata.Metadata
import deckers.thibault.aves.metadata.Mp4ParserHelper
import deckers.thibault.aves.metadata.Mp4ParserHelper.dumpBoxes
import deckers.thibault.aves.metadata.PixyMetaHelper
import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MemoryUtils
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MimeTypes.canReadWithExifInterface
import deckers.thibault.aves.utils.MimeTypes.canReadWithMetadataExtractor
@ -53,7 +56,6 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
"exceptionInCoroutine" -> ioScope.launch { throw TestException() }
"safeExceptionInCoroutine" -> ioScope.launch { safe(call, result) { _, _ -> throw TestException() } }
"getAvailableHeapSize" -> safe(call, result, ::getAvailableHeapSize)
"getContextDirs" -> ioScope.launch { safe(call, result, ::getContextDirs) }
"getCodecs" -> safe(call, result, ::getCodecs)
"getEnv" -> safe(call, result, ::getEnv)
@ -70,10 +72,6 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
}
}
private fun getAvailableHeapSize(@Suppress("unused_parameter") methodCall: MethodCall, result: MethodChannel.Result) {
result.success(MemoryUtils.getAvailableHeapSize())
}
private fun getContextDirs(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
val dirs = hashMapOf(
"cacheDir" to context.cacheDir,

View file

@ -12,7 +12,7 @@ import androidx.core.content.pm.ShortcutManagerCompat
import com.google.android.material.color.DynamicColors
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MemoryUtils
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@ -35,6 +35,8 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
"getPerformanceClass" -> safe(call, result, ::getPerformanceClass)
"isSystemFilePickerEnabled" -> safe(call, result, ::isSystemFilePickerEnabled)
"requestMediaManagePermission" -> safe(call, result, ::requestMediaManagePermission)
"getAvailableHeapSize" -> safe(call, result, ::getAvailableHeapSize)
"requestGarbageCollection" -> safe(call, result, ::requestGarbageCollection)
else -> result.notImplemented()
}
}
@ -123,6 +125,15 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
result.success(true)
}
private fun getAvailableHeapSize(@Suppress("unused_parameter") methodCall: MethodCall, result: MethodChannel.Result) {
result.success(MemoryUtils.getAvailableHeapSize())
}
private fun requestGarbageCollection(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
Runtime.getRuntime().gc()
result.success(true)
}
companion object {
const val CHANNEL = "deckers.thibault/aves/device"
}

View file

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/geotiff.dart';
@ -6,10 +8,14 @@ import 'package:aves/model/video/metadata.dart';
import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/services/metadata/svg_metadata_service.dart';
import 'package:flutter/foundation.dart';
extension ExtraAvesEntryCatalog on AvesEntry {
Future<void> catalog({required bool background, required bool force, required bool persist}) async {
if (isCatalogued && !force) return;
final beforeAvailableHeapSize = await deviceService.getAvailableHeapSize();
if (isSvg) {
// vector image sizing is not essential, so we should not spend time for it during loading
// but it is useful anyway (for aspect ratios etc.) so we size them during cataloguing
@ -53,5 +59,14 @@ extension ExtraAvesEntryCatalog on AvesEntry {
}
}
}
final afterAvailableHeapSize = await deviceService.getAvailableHeapSize();
final diff = beforeAvailableHeapSize - afterAvailableHeapSize;
const largeHeapUsageThreshold = 15 * (1 << 20); // MB
if (diff > largeHeapUsageThreshold) {
debugPrint('Large heap usage (${diff}B) from cataloguing entry=$this size=$sizeBytes');
await deviceService.requestGarbageCollection();
}
}
}

View file

@ -449,6 +449,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
}
if (dataTypes.contains(EntryDataType.catalog)) {
// explicit GC before cataloguing multiple items
await deviceService.requestGarbageCollection();
await Future.forEach(entries, (entry) async {
await entry.catalog(background: background, force: dataTypes.contains(EntryDataType.catalog), persist: persist);
await metadataDb.updateCatalogMetadata(entry.id, entry.catalogMetadata);
@ -499,6 +501,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
entryIds: entries?.map((entry) => entry.id).toList(),
);
} else {
// explicit GC before cataloguing multiple items
await deviceService.requestGarbageCollection();
await catalogEntries(_analysisController, todoEntries);
updateDerivedFilters(todoEntries);
await locateEntries(_analysisController, todoEntries);

View file

@ -46,16 +46,6 @@ class AndroidDebugService {
}
}
static Future<int> getAvailableHeapSize() async {
try {
final result = await _platform.invokeMethod('getAvailableHeapSize');
if (result != null) return result as int;
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return 0;
}
static Future<Map> getContextDirs() async {
try {
final result = await _platform.invokeMethod('getContextDirs');

View file

@ -17,6 +17,10 @@ abstract class DeviceService {
Future<bool> isSystemFilePickerEnabled();
Future<void> requestMediaManagePermission();
Future<int> getAvailableHeapSize();
Future<void> requestGarbageCollection();
}
class PlatformDeviceService implements DeviceService {
@ -104,4 +108,24 @@ class PlatformDeviceService implements DeviceService {
await reportService.recordError(e, stack);
}
}
@override
Future<int> getAvailableHeapSize() async {
try {
final result = await _platform.invokeMethod('getAvailableHeapSize');
if (result != null) return result as int;
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return 0;
}
@override
Future<void> requestGarbageCollection() async {
try {
await _platform.invokeMethod('requestGarbageCollection');
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
}
}