#782 manual GC before/during cataloguing
This commit is contained in:
parent
0b95c80356
commit
a9444b61e2
8 changed files with 64 additions and 20 deletions
|
@ -14,6 +14,10 @@ All notable changes to this project will be documented in this file.
|
||||||
- disabling animations also applies to pop up menus
|
- disabling animations also applies to pop up menus
|
||||||
- upgraded Flutter to stable v3.19.3
|
- 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
|
## <a id="v1.10.5"></a>[v1.10.5] - 2024-02-22
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -14,7 +14,6 @@ import androidx.work.ForegroundInfo
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import app.loup.streams_channel.StreamsChannel
|
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.DeviceHandler
|
||||||
import deckers.thibault.aves.channel.calls.GeocodingHandler
|
import deckers.thibault.aves.channel.calls.GeocodingHandler
|
||||||
import deckers.thibault.aves.channel.calls.MediaStoreHandler
|
import deckers.thibault.aves.channel.calls.MediaStoreHandler
|
||||||
|
@ -98,7 +97,6 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
|
||||||
|
|
||||||
// dart -> platform -> dart
|
// dart -> platform -> dart
|
||||||
// - need Context
|
// - need Context
|
||||||
MethodChannel(messenger, DebugHandler.CHANNEL).setMethodCallHandler(DebugHandler(context))
|
|
||||||
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(context))
|
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(context))
|
||||||
MethodChannel(messenger, GeocodingHandler.CHANNEL).setMethodCallHandler(GeocodingHandler(context))
|
MethodChannel(messenger, GeocodingHandler.CHANNEL).setMethodCallHandler(GeocodingHandler(context))
|
||||||
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(context))
|
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(context))
|
||||||
|
|
|
@ -15,12 +15,15 @@ import android.util.Log
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import com.drew.metadata.file.FileTypeDirectory
|
import com.drew.metadata.file.FileTypeDirectory
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
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.Mp4ParserHelper.dumpBoxes
|
||||||
|
import deckers.thibault.aves.metadata.PixyMetaHelper
|
||||||
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
||||||
import deckers.thibault.aves.model.FieldMap
|
import deckers.thibault.aves.model.FieldMap
|
||||||
import deckers.thibault.aves.utils.LogUtils
|
import deckers.thibault.aves.utils.LogUtils
|
||||||
import deckers.thibault.aves.utils.MemoryUtils
|
|
||||||
import deckers.thibault.aves.utils.MimeTypes
|
import deckers.thibault.aves.utils.MimeTypes
|
||||||
import deckers.thibault.aves.utils.MimeTypes.canReadWithExifInterface
|
import deckers.thibault.aves.utils.MimeTypes.canReadWithExifInterface
|
||||||
import deckers.thibault.aves.utils.MimeTypes.canReadWithMetadataExtractor
|
import deckers.thibault.aves.utils.MimeTypes.canReadWithMetadataExtractor
|
||||||
|
@ -53,7 +56,6 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
|
||||||
"exceptionInCoroutine" -> ioScope.launch { throw TestException() }
|
"exceptionInCoroutine" -> ioScope.launch { throw TestException() }
|
||||||
"safeExceptionInCoroutine" -> ioScope.launch { safe(call, result) { _, _ -> throw TestException() } }
|
"safeExceptionInCoroutine" -> ioScope.launch { safe(call, result) { _, _ -> throw TestException() } }
|
||||||
|
|
||||||
"getAvailableHeapSize" -> safe(call, result, ::getAvailableHeapSize)
|
|
||||||
"getContextDirs" -> ioScope.launch { safe(call, result, ::getContextDirs) }
|
"getContextDirs" -> ioScope.launch { safe(call, result, ::getContextDirs) }
|
||||||
"getCodecs" -> safe(call, result, ::getCodecs)
|
"getCodecs" -> safe(call, result, ::getCodecs)
|
||||||
"getEnv" -> safe(call, result, ::getEnv)
|
"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) {
|
private fun getContextDirs(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
|
||||||
val dirs = hashMapOf(
|
val dirs = hashMapOf(
|
||||||
"cacheDir" to context.cacheDir,
|
"cacheDir" to context.cacheDir,
|
||||||
|
|
|
@ -12,7 +12,7 @@ import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||||
import deckers.thibault.aves.model.FieldMap
|
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.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||||
|
@ -35,6 +35,8 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
|
||||||
"getPerformanceClass" -> safe(call, result, ::getPerformanceClass)
|
"getPerformanceClass" -> safe(call, result, ::getPerformanceClass)
|
||||||
"isSystemFilePickerEnabled" -> safe(call, result, ::isSystemFilePickerEnabled)
|
"isSystemFilePickerEnabled" -> safe(call, result, ::isSystemFilePickerEnabled)
|
||||||
"requestMediaManagePermission" -> safe(call, result, ::requestMediaManagePermission)
|
"requestMediaManagePermission" -> safe(call, result, ::requestMediaManagePermission)
|
||||||
|
"getAvailableHeapSize" -> safe(call, result, ::getAvailableHeapSize)
|
||||||
|
"requestGarbageCollection" -> safe(call, result, ::requestGarbageCollection)
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,6 +125,15 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
|
||||||
result.success(true)
|
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 {
|
companion object {
|
||||||
const val CHANNEL = "deckers.thibault/aves/device"
|
const val CHANNEL = "deckers.thibault/aves/device"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/props.dart';
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/model/geotiff.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/ref/mime_types.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/metadata/svg_metadata_service.dart';
|
import 'package:aves/services/metadata/svg_metadata_service.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
extension ExtraAvesEntryCatalog on AvesEntry {
|
extension ExtraAvesEntryCatalog on AvesEntry {
|
||||||
Future<void> catalog({required bool background, required bool force, required bool persist}) async {
|
Future<void> catalog({required bool background, required bool force, required bool persist}) async {
|
||||||
if (isCatalogued && !force) return;
|
if (isCatalogued && !force) return;
|
||||||
|
|
||||||
|
final beforeAvailableHeapSize = await deviceService.getAvailableHeapSize();
|
||||||
|
|
||||||
if (isSvg) {
|
if (isSvg) {
|
||||||
// vector image sizing is not essential, so we should not spend time for it during loading
|
// 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
|
// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -449,6 +449,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataTypes.contains(EntryDataType.catalog)) {
|
if (dataTypes.contains(EntryDataType.catalog)) {
|
||||||
|
// explicit GC before cataloguing multiple items
|
||||||
|
await deviceService.requestGarbageCollection();
|
||||||
await Future.forEach(entries, (entry) async {
|
await Future.forEach(entries, (entry) async {
|
||||||
await entry.catalog(background: background, force: dataTypes.contains(EntryDataType.catalog), persist: persist);
|
await entry.catalog(background: background, force: dataTypes.contains(EntryDataType.catalog), persist: persist);
|
||||||
await metadataDb.updateCatalogMetadata(entry.id, entry.catalogMetadata);
|
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(),
|
entryIds: entries?.map((entry) => entry.id).toList(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// explicit GC before cataloguing multiple items
|
||||||
|
await deviceService.requestGarbageCollection();
|
||||||
await catalogEntries(_analysisController, todoEntries);
|
await catalogEntries(_analysisController, todoEntries);
|
||||||
updateDerivedFilters(todoEntries);
|
updateDerivedFilters(todoEntries);
|
||||||
await locateEntries(_analysisController, todoEntries);
|
await locateEntries(_analysisController, todoEntries);
|
||||||
|
|
|
@ -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 {
|
static Future<Map> getContextDirs() async {
|
||||||
try {
|
try {
|
||||||
final result = await _platform.invokeMethod('getContextDirs');
|
final result = await _platform.invokeMethod('getContextDirs');
|
||||||
|
|
|
@ -17,6 +17,10 @@ abstract class DeviceService {
|
||||||
Future<bool> isSystemFilePickerEnabled();
|
Future<bool> isSystemFilePickerEnabled();
|
||||||
|
|
||||||
Future<void> requestMediaManagePermission();
|
Future<void> requestMediaManagePermission();
|
||||||
|
|
||||||
|
Future<int> getAvailableHeapSize();
|
||||||
|
|
||||||
|
Future<void> requestGarbageCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformDeviceService implements DeviceService {
|
class PlatformDeviceService implements DeviceService {
|
||||||
|
@ -104,4 +108,24 @@ class PlatformDeviceService implements DeviceService {
|
||||||
await reportService.recordError(e, stack);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue