
이미지가 안 떠요 (캐싱, 메모리, 그리고 에러 처리)
네트워크 이미지가 엑박(X)으로 뜨거나, 리스트 스크롤 시 버벅거림(Jank)이 발생하나요? cached_network_image 사용법부터 메모리 캐시(LruCache) 최적화, 그리고 SSL 인증서 문제까지 해결해드립니다.

네트워크 이미지가 엑박(X)으로 뜨거나, 리스트 스크롤 시 버벅거림(Jank)이 발생하나요? cached_network_image 사용법부터 메모리 캐시(LruCache) 최적화, 그리고 SSL 인증서 문제까지 해결해드립니다.
매번 3-Way Handshake 하느라 지쳤나요? 한 번 맺은 인연(TCP 연결)을 소중히 유지하는 법. HTTP 최적화의 기본.

로그인 화면을 만들었는데 키보드가 올라오니 노란 줄무늬 에러가 뜹니다. resizeToAvoidBottomInset부터 스크롤 뷰, 그리고 채팅 앱을 위한 reverse 팁까지, 키보드 대응의 모든 것을 정리해봤습니다.

HTTP는 무전기(오버) 방식이지만, 웹소켓은 전화기(여보세요)입니다. 채팅과 주식 차트가 실시간으로 움직이는 기술적 비밀.

IP는 이사 가면 바뀌지만, MAC 주소는 바뀌지 않습니다. 주민등록번호와 집 주소의 차이. 공장 출고 때 찍히는 고유 번호.

Image.network를 썼는데, 스크롤을 내렸다가 다시 올리면 이미지가 또 깜빡거리며 로딩됩니다.
사용자는 "앱이 버벅거린다"고 느낍니다.
게다가 가끔 이미지가 깨져서 나오거나, 아예 엑박(X)이 뜹니다.
Flutter의 기본 Image.network는 디스크 캐싱(Disk Caching)을 지원하지 않기 때문입니다.
메모리에만 잠깐 들고 있다가, 스크롤 범위 밖으로 나가면(Dispose) 날려버립니다.
Flutter 개발자라면 숨 쉬듯이 써야 하는 패키지, cached_network_image입니다.
이 친구는 이미지를 다운로드해서 로컬 파일 시스템에 저장해둡니다. 다음 실행 때는 인터넷이 끊겨도 이미지가 나옵니다.
dependencies:
cached_network_image: ^3.3.0
CachedNetworkImage(
imageUrl: "https://example.com/image.jpg",
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
)
이것만 써도 성능 문제의 80%는 해결됩니다. 하지만 나머지 20%는 디테일에 있습니다.
요즘 폰 카메라는 1200만 화소(4000x3000)가 넘습니다. 이 고화질 사진을 작은 썸네일(50x50)에 띄우려고 원본 그대로 로딩하면? 메모리(RAM)가 터집니다. (OOM Crash)
반드시 memCacheWidth나 memCacheHeight를 사용해서 메모리에 올릴 때 리사이징을 해야 합니다.
CachedNetworkImage(
imageUrl: "https://huge-image.com/4k.jpg",
memCacheWidth: 200, // 👈 중요: 실제 렌더링 크기에 맞춰 줄여서 로드
memCacheHeight: 200,
)
디스크에는 원본을 저장하더라도, 메모리에는 줄여서 올려야 앱이 안 죽습니다.
"이미지 URL이 https인데 안 떠요."
로그를 보면 HandshakeException: CERTIFICATE_VERIFY_FAILED가 뜹니다.
개발 서버나 오래된 서버의 SSL 인증서가 유효하지 않아서 그렇습니다.
해결법 (HttpOverrides 사용):
(주의: 배포용 앱에서는 보안상 사용하지 않는 게 좋습니다. 개발 단계에서만 쓰세요.)
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
}
}
void main() {
HttpOverrides.global = MyHttpOverrides();
runApp(MyApp());
}
사용자는 뺑글이(CircularProgressIndicator)를 싫어합니다. 인스타그램이나 유튜브처럼 스켈레톤 UI (Shimmer)를 보여주는 게 UX 국룰입니다.
CachedNetworkImage(
imageUrl: url,
placeholder: (context, url) => Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(color: Colors.white),
),
)
더 고급스러운 경험을 원한다면 BlurHash를 쓰세요. 이미지의 평균 색상을 암호화된 문자열로 미리 받아서, 로딩 전부터 "대충 이런 색의 이미지다"라고 흐릿하게 보여주는 기술입니다.
이미지가 갱신되었는데 앱에서는 계속 옛날 이미지가 뜬다면?
캐시 키(Cache Key)가 URL 기준이라서 그렇습니다.
CDN(AWS CloudFront 등)을 쓴다면 보통 URL 뒤에 v=2 같은 쿼리 파라미터를 붙여서 강제로 캐시를 갱신합니다.
만약 Authorization 토큰이 필요한 이미지라면?
CachedNetworkImage(
imageUrl: "https://private.com/profile.jpg",
httpHeaders: {
"Authorization": "Bearer $token", // 👈 헤더 추가 가능
},
)
캐시는 두 계층으로 나뉩니다.
ImageCache (Flutter 기본). 속도가 매우 빠르지만 앱 끄면 사라짐. RAM을 먹음.sqflite나 파일시스템. 속도는 느리지만(IO 발생) 영구 저장됨. 스토리지 용량을 먹음.cached_network_image는 1번과 2번을 모두 사용합니다.
이미지를 처음 요청하면: Network -> Disk -> Memory -> UI 순서로 이동합니다.
두 번째 요청하면: Memory -> UI (초고속).
앱 껐다 켜면: Disk -> Memory -> UI.
이미지 캐시 제한 늘리기:
기본 ImageCache는 100MB, 1000개 이미지가 한계입니다. 인스타그램 같은 앱이라면 부족할 수 있습니다.
void main() {
// 캐시 용량 증설
PaintingBinding.instance.imageCache.maximumSizeBytes = 1024 * 1024 * 500; // 500MB
PaintingBinding.instance.imageCache.maximumSize = 5000; // 5000개
runApp(MyApp());
}
이미지가 안 나올 때 Icon(Icons.error)만 띄우고 넘어가나요?
로그를 보면 403 Forbidden이나 404 Not Found인 경우가 많습니다.
1. AWS S3 403 Forbidden:
URL에 Signed Parameters (Expires, Signature)가 포함되어 있는데 만료된 경우입니다.
앱에서 이미지를 요청할 때마다 서버에서 새로운 Signed URL을 받아와야 합니다.
CachedNetworkImage는 URL이 바뀌면 새 이미지로 인식하므로 자동으로 갱신됩니다.
2. 봇 차단 (User-Agent Blocking):
일부 서버는 모바일 앱(HttpClient)의 접근을 봇으로 오인해 차단합니다.
이럴 때는 user-agent 헤더를 브라우저처럼 위장해야 합니다.
httpHeaders: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...",
},
3. Hotlinking Protection:
이미지를 다른 사이트에서 못 쓰게 막는 Referer 체크가 걸려있을 수 있습니다.
Referer 헤더에 해당 도메인을 넣어주면 해결됩니다.
CachedNetworkImage는 선택이 아니라 필수다.
memCacheWidth로 메모리 폭발을 막아라.
HttpOverrides로 우회 가능하다.
PaintingBinding에서 늘려라.
이미지만 잘 처리해도 앱의 완성도가 200% 올라갑니다.