Files
submanager/lib/utils/memory_manager.dart
2025-09-07 19:33:11 +09:00

283 lines
7.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'dart:async';
/// 메모리 관리를 위한 헬퍼 클래스
class MemoryManager {
static final MemoryManager _instance = MemoryManager._internal();
factory MemoryManager() => _instance;
MemoryManager._internal();
// 캐시 관리
final Map<String, _CacheEntry> _cache = {};
final int _maxCacheSize = 100;
final Duration _defaultTTL = const Duration(minutes: 5);
// 이미지 캐시 관리
static const int maxImageCacheSize = 50 * 1024 * 1024; // 50MB
static const int maxImageCacheCount = 100;
// 위젯 참조 추적
final Map<String, WeakReference<State>> _widgetReferences = {};
/// 캐시에 데이터 저장
void cacheData<T>({
required String key,
required T data,
Duration? ttl,
}) {
_cleanupExpiredCache();
if (_cache.length >= _maxCacheSize) {
_evictOldestEntry();
}
_cache[key] = _CacheEntry(
data: data,
timestamp: DateTime.now(),
ttl: ttl ?? _defaultTTL,
);
}
/// 캐시에서 데이터 가져오기
T? getCachedData<T>(String key) {
final entry = _cache[key];
if (entry == null) return null;
if (entry.isExpired) {
_cache.remove(key);
return null;
}
entry.lastAccess = DateTime.now();
return entry.data as T?;
}
/// 캐시 비우기
void clearCache() {
_cache.clear();
if (kDebugMode) {
print('🧹 메모리 캐시가 비워졌습니다.');
}
}
/// 특정 패턴의 캐시 제거
void clearCacheByPattern(String pattern) {
final keysToRemove =
_cache.keys.where((key) => key.contains(pattern)).toList();
for (final key in keysToRemove) {
_cache.remove(key);
}
}
/// 만료된 캐시 정리
void _cleanupExpiredCache() {
final expiredKeys = _cache.entries
.where((entry) => entry.value.isExpired)
.map((entry) => entry.key)
.toList();
for (final key in expiredKeys) {
_cache.remove(key);
}
}
/// 가장 오래된 캐시 항목 제거
void _evictOldestEntry() {
if (_cache.isEmpty) return;
var oldestKey = _cache.keys.first;
var oldestTime = _cache[oldestKey]!.lastAccess;
for (final entry in _cache.entries) {
if (entry.value.lastAccess.isBefore(oldestTime)) {
oldestKey = entry.key;
oldestTime = entry.value.lastAccess;
}
}
_cache.remove(oldestKey);
}
/// 이미지 캐시 최적화
static void optimizeImageCache() {
PaintingBinding.instance.imageCache.maximumSize = maxImageCacheCount;
PaintingBinding.instance.imageCache.maximumSizeBytes = maxImageCacheSize;
}
/// 이미지 캐시 상태 확인
static ImageCacheStatus getImageCacheStatus() {
final cache = PaintingBinding.instance.imageCache;
return ImageCacheStatus(
currentSize: cache.currentSize,
maximumSize: cache.maximumSize,
currentSizeBytes: cache.currentSizeBytes,
maximumSizeBytes: cache.maximumSizeBytes,
);
}
/// 이미지 캐시 비우기
static void clearImageCache() {
PaintingBinding.instance.imageCache.clear();
PaintingBinding.instance.imageCache.clearLiveImages();
if (kDebugMode) {
print('🖼️ 이미지 캐시가 비워졌습니다.');
}
}
/// 위젯 참조 추적
void trackWidget(String key, State widget) {
_widgetReferences[key] = WeakReference(widget);
}
/// 위젯 참조 제거
void untrackWidget(String key) {
_widgetReferences.remove(key);
}
/// 살아있는 위젯 수 확인
int getAliveWidgetCount() {
return _widgetReferences.values.where((ref) => ref.target != null).length;
}
/// 메모리 압박 시 대응
void handleMemoryPressure() {
// 캐시 50% 제거
final keysToRemove = _cache.keys.take(_cache.length ~/ 2).toList();
for (final key in keysToRemove) {
_cache.remove(key);
}
// 이미지 캐시 축소
final imageCache = PaintingBinding.instance.imageCache;
imageCache.maximumSize = maxImageCacheCount ~/ 2;
imageCache.maximumSizeBytes = maxImageCacheSize ~/ 2;
if (kDebugMode) {
print('⚠️ 메모리 압박 대응: 캐시 크기 감소');
}
}
/// 자동 메모리 정리 시작
Timer? _cleanupTimer;
void startAutoCleanup({Duration interval = const Duration(minutes: 1)}) {
_cleanupTimer?.cancel();
_cleanupTimer = Timer.periodic(interval, (_) {
_cleanupExpiredCache();
// 죽은 위젯 참조 제거
final deadKeys = _widgetReferences.entries
.where((entry) => entry.value.target == null)
.map((entry) => entry.key)
.toList();
for (final key in deadKeys) {
_widgetReferences.remove(key);
}
});
}
/// 자동 메모리 정리 중지
void stopAutoCleanup() {
_cleanupTimer?.cancel();
_cleanupTimer = null;
}
/// 메모리 사용량 리포트
Map<String, dynamic> getMemoryReport() {
return {
'cacheSize': _cache.length,
'maxCacheSize': _maxCacheSize,
'aliveWidgets': getAliveWidgetCount(),
'totalWidgetReferences': _widgetReferences.length,
'imageCacheStatus': getImageCacheStatus().toJson(),
};
}
}
/// 캐시 항목 클래스
class _CacheEntry {
final dynamic data;
final DateTime timestamp;
final Duration ttl;
DateTime lastAccess;
_CacheEntry({
required this.data,
required this.timestamp,
required this.ttl,
}) : lastAccess = timestamp;
bool get isExpired => DateTime.now().difference(timestamp) > ttl;
}
/// 이미지 캐시 상태 클래스
class ImageCacheStatus {
final int currentSize;
final int maximumSize;
final int currentSizeBytes;
final int maximumSizeBytes;
ImageCacheStatus({
required this.currentSize,
required this.maximumSize,
required this.currentSizeBytes,
required this.maximumSizeBytes,
});
double get sizeUsagePercentage => (currentSize / maximumSize) * 100;
double get bytesUsagePercentage =>
(currentSizeBytes / maximumSizeBytes) * 100;
Map<String, dynamic> toJson() => {
'currentSize': currentSize,
'maximumSize': maximumSize,
'currentSizeBytes': currentSizeBytes,
'maximumSizeBytes': maximumSizeBytes,
'sizeUsagePercentage': sizeUsagePercentage.toStringAsFixed(2),
'bytesUsagePercentage': bytesUsagePercentage.toStringAsFixed(2),
};
}
/// 메모리 효율적인 리스트 뷰
class MemoryEfficientListView<T> extends StatefulWidget {
final List<T> items;
final Widget Function(BuildContext, T) itemBuilder;
final int cacheExtent;
final ScrollPhysics? physics;
const MemoryEfficientListView({
super.key,
required this.items,
required this.itemBuilder,
this.cacheExtent = 250,
this.physics,
});
@override
State<MemoryEfficientListView<T>> createState() =>
_MemoryEfficientListViewState<T>();
}
class _MemoryEfficientListViewState<T> extends State<MemoryEfficientListView<T>>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => false;
@override
Widget build(BuildContext context) {
super.build(context);
return ListView.builder(
itemCount: widget.items.length,
cacheExtent: widget.cacheExtent.toDouble(),
physics: widget.physics ?? const BouncingScrollPhysics(),
itemBuilder: (context, index) {
return widget.itemBuilder(context, widget.items[index]);
},
);
}
}