우리 회사는 인스타그램, 핀터레스트 못지 않게 많은 이미지를 사용한다.
이전에 FastCachedNetworkImage패키지를 썼으나, main.dart에서 await init을 하는 시점에서 계획되지 않은 캐싱 전략으로 앱 용량은 1GB가 넘어갔고 결국 OS스플래시 단에서 10초 가까이 걸리는 이슈가 발생했다.
그걸 대체하기 위해 좀 더 이미지를 공부했다.
PaintingBinding.instance.imageCache는 전역 메모리 이미지 캐시다.
디코딩된 비트맵을 LRU(LRU 참고)로 보관한다.
기본 한도는 개수 1000장, 용량 100MB다. 필요하면 maximumSize, maximumSizeBytes로 조정한다.
ImageCache Class
ImageProvider가 이미지를 불러오면 ImageCache.putIfAbsent로 캐시에 등록된다. 항목 상태는 pending / live / keepAlive로 추적한다. 조회는 containsKey나 statusForKey로 한다.
ImageCache class
2025 변경점: 캐시가 큰 이미지에 맞춰 자동 확대되지 않는다. 큰 이미지를 자주 다루면 maximumSizeBytes를 올리거나 디코드 크기를 줄여야 한다.
ImageCache large images
imageCache는 PaintingBinding이 보관하는 싱글턴이다. 프레임워크 전역에서 공유된다. 일반적으로 직접 교체하지 않고 설정값만 조정한다.
imageCache property
LRU 기반. 초과 시 가장 덜 최근 사용 항목부터 퇴출한다.
기본 상한: 최대 1000개, 최대 100MB. 값은 런타임에 조정 가능.
// 전역 캐시 한도 조정 예
void main() {
WidgetsFlutterBinding.ensureInitialized();
final cache = PaintingBinding.instance.imageCache;
cache.maximumSize = 150; // 개수 상한
cache.maximumSizeBytes = 100 << 20; // 100MB
runApp(const MyApp());
}
ImageProvider가 키를 만들고 ImageCache.putIfAbsent(key, loader)를 호출한다.
캐시에 없으면 loader가 디코딩을 시작하고 상태가 pending이 된다.
성공적으로 디코딩되고 크기 제한에 맞으면 keepAlive로 보관된다. 리스너가 붙어 있으면 live로도 표시된다.
한도 초과 시 LRU로 퇴출한다.
putIfAbsent method
상태 조회
final key = /* ImageProvider가 내부적으로 만드는 키 */;
final inCache = PaintingBinding.instance.imageCache.containsKey(key);
final status = PaintingBinding.instance.imageCache.statusForKey(key);
// status.pending / status.keepAlive / status.live
maximumSize : 보관 개수 상한. 초과 시 LRU로 제거.
maximumSizeBytes : 총 용량 상한. 초과 시 LRU로 제거.
값을 0으로 만들면 즉시 비운다. 다시 원래 값으로 돌리면 빈 상태에서 재시작한다.
maximumSize property
final cache = PaintingBinding.instance.imageCache;
debugPrint('count=${cache.currentSize}, bytes=${cache.currentSizeBytes}');
// 개별 제거(키 필요). live도 제거하려면 includeLive: true
PaintingBinding.instance.imageCache.evict(myKey, includeLive: true);
// 전체 제거
PaintingBinding.instance.imageCache.clear();
주의: 빌드 중에 clear()를 반복 호출하면 스크롤 시 재디코딩 난사로 jank가 커진다. (설계상 clear는 즉시 전부 퇴출한다.)
clear method
이전에는 큰 이미지 하나가 들어오면 캐시 용량을 그 크기에 맞게 늘렸다. 이제는 자동 확대 금지다. 캐시보다 큰 이미지는 캐시에 들어가지 않고 화면 전환 때마다 다시 디코딩될 수 있다. 대처:
maximumSizeBytes를 합리적으로 상향한다.
디코드 크기를 줄여 캐시에 들어가게 만든다.
5) “디코딩 크기”를 줄여 메모리와 시간 절약
이미지는 디코딩 후 압축 해제된 비트맵으로 캐시에 저장된다. 4K(3840×2160) 한 장이 30MB+를 차지한다. cacheWidth / cacheHeight로 디코드 크기를 작게 지정하면 메모리 사용을 크게 줄인다.
// 네트워크 이미지: 디코드 타깃 크기 지정
Image.network(
url,
width: 120, height: 90,
cacheWidth: (120 * MediaQuery.devicePixelRatioOf(context)).round(),
cacheHeight: (90 * MediaQuery.devicePixelRatioOf(context)).round(),
);
CachedNetworkImage 사용 시에는 memCacheWidth/Height가 같은 역할을 한다. 둘 다 주지 말고 한 축만 주면 비율 유지로 충분하다.
Save Your Memory Usage By Optimizing Network Images in Flutter
ImageCache는 디코딩된 비트맵의 메모리 캐시다.
cached_network_image + flutter_cache_manager는 파일(압축본)의 디스크 캐시다. 디스크에서 읽어와도 디코딩과 ImageCache 보관은 별개다.
cached_network_image
리스트 썸네일: cacheWidth 또는 memCacheWidth만 지정해 디코드 작게.
상세 보기: 필요할 때만 큰 사이즈로 요청.
캐시 thrash가 보이면 maximumSizeBytes를 올리거나 디코드 크기를 더 줄인다.
Flutter Docs
전역 캐시 구현을 바꾸려면 바인딩을 확장해 createImageCache()를 재정의한다. 앱 시작 전에 해당 바인딩을 초기화한다. (고급 용도)
How do I change or replace the ImageCache in Flutter?
class MyBinding extends WidgetsFlutterBinding with PaintingBinding {
ImageCache createImageCache() {
final c = super.createImageCache();
c.maximumSize = 500;
c.maximumSizeBytes = 200 << 20;
return c;
}
}
void main() {
MyBinding(); // runApp 전에 바인딩 고정
runApp(const MyApp());
}
한도 설정: 메모리 여유가 크지 않다면 maximumSizeBytes는 50–200MB 범위에서 실기기 프로파일링으로 결정한다.
디코드 힌트: 리스트·그리드는 cacheWidth/memCacheWidth만. DPR 상한(예: 2.0)을 둔다.
관찰: currentSizeBytes, currentSize, liveImageCount로 히트율과 thrash 여부를 본다.
클리어 남용 금지: 화면 전환마다 clear() 호출은 금물. 필요 시 키 기반 evict()를 사용한다.
evict method
참고
Flutter API: ImageCache class. 기본 LRU, 기본 한도(1000개/100MB), 전역 인스턴스 설명.
Flutter API: PaintingBinding.imageCache. 전역 캐시 게터.
Flutter API: maximumSize / maximumSizeBytes. 한도 조정과 즉시 퇴출 동작.
Flutter API: putIfAbsent / containsKey / statusForKey / ImageCacheStatus. 상태 추적과 조회.
Flutter API: clear / evict. 캐시 비우기와 개별 퇴출.
Flutter Breaking change: ImageCache large images. 큰 이미지 자동 확대 제거, 마이그레이션 가이드.
Flutter API: Image widget. 메모리 사용량, cacheWidth/cacheHeight로 디코드 크기 지정.
패키지 문서: cached_network_image. 디스크 캐시와 cacheManager 전달.
StackOverflow: 이미지 캐시 교체 방법(Ian Hickson 답변). 바인딩 확장과 createImageCache 재정의.