[안드로이드]문답-이미지뷰를 포함한 리스트뷰 속도개선
최근에 가장 많은 고민을 한 문제와 그에 대한 대답 그리고 이어지는 문제에 대해 간단하게 정리해봅니다.
혹여나 더 좋은 답변 있으신 분들은 토론하셔도 좋을 듯 합니다:)
문제 : 안드로이드의 ListView(혹은 Gridview)에 ImageView를 여러개 달아놓았다. ImageView에는 Web에서 받을 Image들을 표시하며 빠르게 로딩하고 사용자가 스크롤을 하며 버벅거리지 않도록 처리하려면 어떻게 해야하는가? ( 이미지의 크기는 비교적 큰편이다 )
먼저, 이미지를 표시하기 위해서는 Web에서 Stream으로 받아와 ImageView에 띄우는 방법이 가장 일반적인 방법이다.(이미지가 크지 않고 많은 이미지를 표현하지 않을 때에는 확실히 Stream에서 바로 받아오는 것이 빠르긴하다). 이 과정에서 더욱 더 빠르게 보이게 하기 위해서는 받아온 Image를 Caching한다. Caching에는 디스크와 메모리 캐싱이 있는데, 말그대로 메모리는 앱이 구동되고 있는 Heap Memory상에 이미지를 잠시 얹어놓고 사용하는 방법이며, 디스크는 파일로 저장하고 읽어와 보여주는 방법이다.
먼저 메모리 캐싱은 속도가 굉장히 빠르다. 하지만 메모리의 크기가 크지 않기 때문에, 비교적 큰 이미지를 모두 저장하는 데에는 한계점이 너무나 낮다.
그렇다면 이미지 로딩 시 ImageDownload를 생성 후 (이 안에서) Memory에 해당 파일이 있는지 검사 후
[ 메모리에 캐싱된 이미지가 있을 경우 -> 그냥 로딩하고 끝낸다. ]
[ 메모리에 캐싱된 이미지가 없을 경우 -> 디스크에 캐싱되어 있는지 확인한다. ]
디스크 캐싱 확인 이후,
[ 디스크에 캐싱된 이미지가 있을 경우 -> 보여준다 ]
[ 디스크에 캐싱된 이미지가 없을 경우 -> 이미지를 웹으로 로딩 후 로딩성공 시 메모리와 디스크에 저장한 후 보여준다]
이 때 중요한 포인트는, (스크롤이 버벅거리지 않기 위해서는) 메모리부분은 굳이 AsyncTask 로 돌리지 않아도 영향을 주지 않겠지만, 디스크쪽에서 파일을 Decode하는 부분은 굉장히 버벅거린다. 리스트뷰(그리드뷰)에 이미지가 한장씩 뜨는 경우는 크게 영향을 주지 않겠지만, 만약 여러개의 이미지를 여러컬럼으로 나누어 보여주는 경우에는 Image Decoding을 한번에 진행하기 때문에 Out Of Memory가 발생할 확률이 높아지고 버벅거리는 문제점이 생긴다. 디스크와 웹의 경우는 AsyncTask로 처리하면 훨씬 부드럽다.
만약 보여주는 이미지가 많다면 (당연히 보여주는 이미지가 많으면 이미지의 크기[눈에 보이는]가 작아질 것이다)
그리고 리스트와 그리뷰에서 패턴형식으로 다양한 크기로 같은 이미지를 보여줄 때, 또다른 문제가 생긴다.
이미지 크기에 따라 서버쪽에 이미지 리사이즈를 요청하는 방법이 떠올랐다. 하지만 이 답은 디스크나 메모리의 캐시메모리를 많이 먹는다는 단점이 있다. (물론 캐시가 가득 찼을 때 Out Of Memory는 Exception처리를 통해 Clear를 시켜준다.) 가장 큰 이미지를 작은 패턴에서 사용할 경우에는 스크롤이 버벅대며(Decode문제) Out Of Memory가 나고 다시 Cache Clear때문에 기껏 로딩해놓은 이미지를 싹 날리고 다시 받아온다.
그럼 위의 문제들을 해결할 방법은 Bitmap을 Decode할 때 자체적으로 사이즈를 줄여버리는 것으로 해결한다. BitmapFactory내의 Option중 Sample이미지를 통해 1단에선 1, 2단에선 1/2, 3단에선 1/3... 로 줄여서 Decoding을 하면 스크롤과 메모리문제가 모두 해결된다. (Sample옵션은 600x600 이미지를 로딩 후 줄이는 것이 아니라, 미리 줄여서 Decode를 하기 때문에 위와 같은 방법이 가능)
스크롤을 부드럽게 빠르게 하기 위한 또 하나의 Tip으로는 CacheDuration(캐싱하고 있는 시간)을 설정해서 몇초간 살아있도록 지정을 해주면 스크롤 하는 동안 보이지 않는 (캐싱된) 이미지들을 날려버리게 된다.
+ 여러가지 다른 Cache 관련 응용 기술들은 추후 이 문서를 통해 업데이트 하도록 하겠습니다.