회사 어플 특성상 이미지를 화면에 보여주는 일이 자주 있었습니다.
기존에는 Image.network를 사용하고 있었는데 이렇게 사용하다 보니 어플을 조금만 이용하다보면 메모리 사용량이 올라가 크래시가 발생하였습니다. 이미지 의 크기가 4MB가 넘어가는 이미지를 띄우려고 할 때 그 이미지가 있는 페이지에 가게될 경우 앱이 꺼져버리거나 스크롤 해서 다른 이미지를 빠르게 띄우려고 하다보면 튕기는 일이 발생하였습니다.
이 문제를 해결하기 위해 이미지와 관련된 모든 플러터 패키지를 비교하였습니다. dart DevTools를 활용해서 메모리 추이, CPU 사용량, 크래시 발생 비율, 실제로 캐싱처리를 해서 이미지를 불러오는가? 등을 40MB 이미지로 테스트 하였고, 그 결과 실제로 이미지 크기를 줄여서 캐싱하고, 메모리 추이가 가장 안정적이고, 크래시 발생 비율이 가장 적은 OctoImage + ExtendedResizeImage 조합을 사용하는 것이 가장 효율적이라는 결론을 냈습니다.
OctoImage(
alignment: Alignment.center,
fit: BoxFit.fill,
image: ExtendedResizeImage(
ExtendedNetworkImageProvider(
imageUrl,
cache: true,
cacheMaxAge: const Duration(days: 1),
maxBytes = 50 << 10,
compressionRatio: 0.5,
cacheWidth: cacheWidth,
cacheHeight: cacheHeight,
),
),
errorBuilder: (context, error, stackTrace) =>
ErrorImageWidget(errorIconColor: errorIconColor),
),
cacheMaxAge : 내부적으로 뜯어보면 cacheMaxAge 값은 캐싱 처리를 할 경우 loadCache 함수를 통해 이미지 파일을 가져올 때 사용됩니다. 예를 들어 days:1로 줄 경우에는 로컬 스토리지에 저장을 해놓고 1일이 지나지 않았을 경우에는 로컬에서 이미지 파일을 가져오고, 1일이 지났을 경우에는 imageUrl을 통해 이미지 저장소에서 데이터를 받아옵니다. 중요한 점은 cache를 true로 줘야 로컬에 저장하는 로직이 작동합니다. aws 저장소에서 가져오는 것이 아닌 클라우드 프론트와 같은 캐싱된 이미지를 받아오는 경우에 캐싱컨트롤 정책에 따라 max-age, etag 등으로 관리하는데 플러터 내부적으로 적용 할 수 있는 캐시 관리 정책을 사용할 때 사용하시면 됩니다.
maxBytes, compressionRatio, cacheWidth, cacheHeight 의 경우에는 이미지가 화면 상에서 깨지지 않는 최소 범위를 잡아주시는게 좋습니다.
주의할 점은 url에 종속되기 때문에 해당 이미지 url이 바뀌지 않는 url일 때 적용하셔야 합니다.
추가적으로 메모리 크래시 발생 빈도를 줄이려면 메모리 압력을 감지하고 이미지 캐싱된 저장소를 클리어 해주는 함수를 앱 최상단에 구현하는걸 추천드립니다.