fix: reduce image cache budgets and add memory pressure handling
This commit is contained in:
+41
-5
@@ -2,7 +2,7 @@ import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import 'dart:io' show Platform, ProcessInfo;
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@@ -109,9 +109,14 @@ Future<void> _bootstrapApp() async {
|
||||
// Needed for formatting dates in different locales
|
||||
await initializeDateFormatting(savedLocale.languageCode, null);
|
||||
|
||||
// Configure image cache for large libraries
|
||||
PaintingBinding.instance.imageCache.maximumSize = 2000; // default 1000
|
||||
PaintingBinding.instance.imageCache.maximumSizeBytes = 300 << 20; // 300MB
|
||||
// Configure image cache — keep budget modest to leave headroom for Skia decode buffers
|
||||
if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
|
||||
PaintingBinding.instance.imageCache.maximumSize = 1000;
|
||||
PaintingBinding.instance.imageCache.maximumSizeBytes = 150 << 20; // 150MB
|
||||
} else {
|
||||
PaintingBinding.instance.imageCache.maximumSize = 800;
|
||||
PaintingBinding.instance.imageCache.maximumSizeBytes = 100 << 20; // 100MB
|
||||
}
|
||||
|
||||
// Initialize services in parallel where possible
|
||||
final futures = <Future<void>>[];
|
||||
@@ -309,11 +314,25 @@ class _MainAppState extends State<MainApp> with WidgetsBindingObserver {
|
||||
/// Last time server health probes ran from a resume event (cooldown for desktop)
|
||||
DateTime _lastResumeProbe = DateTime(0);
|
||||
|
||||
/// Periodic memory check timer for desktop platforms
|
||||
Timer? _memoryCheckTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
// On desktop, periodically check RSS and evict image cache if too high
|
||||
if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
|
||||
_memoryCheckTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
||||
final rss = ProcessInfo.currentRss;
|
||||
if (rss > 1536 * 1024 * 1024) { // 1.5GB
|
||||
appLogger.w('RSS high ($rss bytes), evicting image caches');
|
||||
_evictImageCaches();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_serverManager = MultiServerManager();
|
||||
_aggregationService = DataAggregationService(_serverManager);
|
||||
_appDatabase = AppDatabase();
|
||||
@@ -332,10 +351,23 @@ class _MainAppState extends State<MainApp> with WidgetsBindingObserver {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_memoryCheckTimer?.cancel();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didHaveMemoryPressure() {
|
||||
super.didHaveMemoryPressure();
|
||||
appLogger.w('System memory pressure, evicting image caches');
|
||||
_evictImageCaches();
|
||||
}
|
||||
|
||||
void _evictImageCaches() {
|
||||
PaintingBinding.instance.imageCache.clear();
|
||||
PaintingBinding.instance.imageCache.clearLiveImages();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
switch (state) {
|
||||
@@ -357,8 +389,12 @@ class _MainAppState extends State<MainApp> with WidgetsBindingObserver {
|
||||
}
|
||||
case AppLifecycleState.paused:
|
||||
case AppLifecycleState.detached:
|
||||
// App went to background or is closing - end session
|
||||
InAppReviewService.instance.endSession();
|
||||
if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
|
||||
if (ProcessInfo.currentRss > 1024 * 1024 * 1024) { // 1GB
|
||||
_evictImageCaches();
|
||||
}
|
||||
}
|
||||
case AppLifecycleState.inactive:
|
||||
case AppLifecycleState.hidden:
|
||||
// Transitional states - don't trigger session events
|
||||
|
||||
@@ -72,6 +72,10 @@ class BifThumbnailService {
|
||||
try {
|
||||
final bytes = await client.downloadBifFile(partId);
|
||||
if (bytes == null || bytes.isEmpty) return;
|
||||
if (bytes.length > 50 * 1024 * 1024) {
|
||||
appLogger.w('BIF file too large (${bytes.length} bytes), skipping');
|
||||
return;
|
||||
}
|
||||
_entries = await Isolate.run(() => _parseBifBytes(bytes));
|
||||
} catch (e) {
|
||||
appLogger.w('BIF download/parse failed', error: e);
|
||||
|
||||
@@ -17,8 +17,8 @@ class PlexImageCacheManager extends CacheManager with ImageCacheManager {
|
||||
: super(
|
||||
Config(
|
||||
_key,
|
||||
stalePeriod: const Duration(days: 30),
|
||||
maxNrOfCacheObjects: 5000,
|
||||
stalePeriod: const Duration(days: 14),
|
||||
maxNrOfCacheObjects: 3000,
|
||||
fileService: HttpFileService(
|
||||
httpClient: IOClient(
|
||||
HttpClient()..maxConnectionsPerHost = 6,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../models/hotkey_model.dart';
|
||||
import 'image_cache_service.dart';
|
||||
import 'package:plezy/utils/app_logger.dart';
|
||||
import '../i18n/strings.g.dart';
|
||||
import '../models/mpv_config_models.dart';
|
||||
@@ -1386,11 +1388,9 @@ class SettingsService extends BaseSharedPreferencesService {
|
||||
|
||||
// Clear cache (for storage cleanup)
|
||||
Future<void> clearCache() async {
|
||||
// This would be expanded to clear various cache directories
|
||||
// For now, we'll just clear any cache-related preferences
|
||||
await Future.wait([
|
||||
// Add cache clearing logic here
|
||||
]);
|
||||
PaintingBinding.instance.imageCache.clear();
|
||||
PaintingBinding.instance.imageCache.clearLiveImages();
|
||||
await PlexImageCacheManager.instance.emptyCache();
|
||||
}
|
||||
|
||||
// Get all settings as a map for debugging/export
|
||||
|
||||
Reference in New Issue
Block a user