2025년 설 연휴, 여러분은 어떻게 보내고 있나요? 새로운 시작을 위해 준비하는 분도, 다시 달리기 위해 푹 쉬는 분도 있을 텐데요. 요즘IT에서 설 연휴를 맞아 국내 주요 기업의 특색 있고 유익한 블로그 콘텐츠를 소개하는 시리즈를 준비했습니다. 이들은 어떻게 사고하고, 어떤 방식으로 일하는 걸까요? 이번 글에서는 CJ온스타일 안드로이드 앱 개발자가 홈 탭 개편 이후, 스크롤 시 버벅임이 생기는 이슈를 어떻게 해결했는지 그 과정을 공유합니다. 안녕하세요. CJ ENM 커머스 부문에서 Android 앱을 개발하는 신규찬입니다. 온스타일 앱은 지난 2024년 4월 홈 탭 개편 기점으로 많은 변화가 있었습니다. 첫 홈 탭 개편 기획 리뷰에 들어갔을 때, 홈 탭 개편은 많은 영상과 다양한 UI로 구성될 예정이라고 했습니다. 개발 리뷰 후 먼저 들었던 생각은 홈 탭 개선 개발 완료 후, 운영 시점에 홈 탭의 메모리 부하로 고생할 수도 있을 것 같다는 걱정이었습니다. 제가 이러한 걱정을 할 때마다 항상 실제 이슈로 다가왔었는데, 이번 프로젝트에서도 홈 탭 스크롤 시 약간씩 버벅인다는 이슈가 나타났습니다. 이번 글에서는 홈 탭 스크롤 시 버벅임을 어떻게 해결했는지에 대한 내용을 공유하고자 합니다. 어디서 느려진 것일까?홈 탭 스크롤을 하다 보면 일부 구간에서 미세하게 덜컹(?)하는 부분을 발견할 수 있었는데요. 이 영역 위주로 체크하니 크게 세 부분을 발견할 수 있었습니다. 하나는 영상 Prepare, Release 시점, 두 번째는 Cookie의 데이터를 가져와 성인 상품 구좌 이미지를 그리는 시점, 세 번째는 비동기 모듈 호출 후 중간에 다량의 영역에 모듈구좌가 들어가는 시점이었습니다. 동영상 영역에서의 느림저희는 영상을 ‘ExoPlayer’ 라이브러리를 활용해서 사용하고 있습니다. 이 ExoPlayer는 준비(prepare)할 때, 플레이어는 버퍼나 디코더와 같은 자원을 할당하기 시작하면서 메모리 사용량이 증가하게 됩니다. 준비에서 메모리 사용하듯이 종료(release)할 때도 디코더, 렌더러, 버퍼 등 다양한 리소스를 제거하면서 메모리를 사용하게 됩니다. 홈 탭은 다양한 동영상을 사용하고 있는 구조로 리스트가 빠르게 스크롤 하다 보면, 많은 동영상을 Prepare와 Release 하는 경우가 생깁니다. 한두 개의 동영상은 영향을 줄 수 없겠지만, 홈 탭은 상단 헤더 영상, 가로 스크롤 영상, 배너에서 펼쳐지는 영상 등 다양한 동영상 요소가 존재하기 때문에, 이를 효과적으로 처리하기 위한 설계가 필요합니다. 홈 탭에 존재하는 영상 예시 이미지 로드의 느림커머스 앱에서 전시되는 상품 중 성인 상품이 등록되는 경우도 있습니다. 앱에서는 성인 상품을 표시하기에 앞서 해당 사용자가 성인인지 확인을 하는데, 성인 여부는 Cookie 데이터를 가져와서 판단합니다. 적은 양의 Cookie 데이터를 가져오는 것은 메모리 사용에 크게 무리를 줄 수 없지만 디바이스 내 Cookie가 많은 경우, Cookie 가져오는 것으로도 메모리를 어느 정도 사용하게 됩니다. 홈 탭은 다수의 상품이 꽂혀있는 상품 구좌가 존재하는데요. 이 탭에서 빠르게 스크롤을 내리게 되면 많은 상품의 Cookie를 한 번에 가져오면서 메모리에 영향을 줄 수 있게 됩니다. 홈 탭에 사용하는 이미지가 성인 상품인지 확인하는 로직 예시 비동기 화면 증가 로직탭의 모든 상품구좌의 데이터를 다 가져오면 좋지만, 개인화 데이터, 랭킹 데이터, 라이브 방송 데이터와 같은 준실시간 데이터에 대해서는 비동기로 제공하는 게 일반적으로 사용하는 비즈니스 로직입니다. 온스타일도 비동기 통신 시 로딩 뷰를 보여주고, 비동기 API 완료 후 로딩 뷰를 제거하며 해당 구좌를 표시하게 됩니다. 탭 내 스크롤로 하단에 있는 상품 구좌로 이동 중 비동기 통신하는 상품 구좌를 만날 경우, 로딩 뷰를 보여주게 될 것이고 비동기 통신 완료 후에 상품 구좌를 보여줄 것입니다. 만약 표시할 상품 구좌가 로딩 뷰보다 긴 경우, 상품 구좌가 스크롤의 위치를 조정하면서 스크롤에 영향을 주게 됩니다. 그리고 로딩 뷰와 상품 구좌의 배경색이 반전으로 구성되어 있으면, 깜박임 또는 버벅임처럼 느껴질 수 있습니다. 어떻게 해결할 것인가?“문제를 정확하게 정의하면 해결의 50%는 이미 달성된 것”(“The ability to ask the right question is more than half the battle of finding the answer.”)Thomas J. Watso(IBM 창립자) IBM의 창립자 Thomas J.Watso의 말을 인용하면, 모든 문제를 해결하는데 있어 문제에 대해 정확하게 정의하면 50% 이상은 해결할 수 있다고 합니다. 저 또한 50% 이상의 성공 확률이라는 믿음을 갖고 스크롤 버벅임 문제를 해결하기 위해 분석했고, 그 결과 여러 가지 해결 시나리오를 도출했습니다. 이제 각 문제 해결 과정을 하나씩 살펴보겠습니다. 비디오 영역 해결 방안Lazy Prepare Video동영상 영역에서의 느림은 ExoPlayer의 Prepare하는 과정 중 메모리를 사용하는 데 영향이 있음을 확인했습니다. 빠른 스크롤 하는 사용자는 탭에 존재하는 영상을 보는 것보다, 원하는 상품이 있는 구좌로 이동하는 목적을 갖고 행동합니다. 그렇기에 빠른 스크롤 중 지나치는 동영상의 prepare는 ‘SKIP’ 해도 될 거라고 생각했습니다. 간단히 비즈니스 로직을 설명하면, 스크롤이 이뤄질 경우 Recycler View의 Scroll Listener가 호출되는데요. 이때 호출되는 Scroll Lister로 전달되는 이동 픽셀값을 기반으로 Scroll Speed를 저장하여 평균을 구합니다. 스크롤 도중 Video Prepare를 시도 시 저장된 Scroll Speed의 평균값이 임계치를 초과한 상태인지 확인하고, 임계치를 초과할 경우 Video Prepare는 ‘SKIP’하는 로직을 구성했습니다. Lazy Prepare Video 예시 Lazy Video ReleaseLazy Prepare Video의 비즈니스 로직과 비슷한 방식으로 Video Release의 비즈니스 로직을 구성했습니다. 이전에는 ExoPlayer Release를 View가 onDetachedFromWindow()를 호출 시점에 처리했습니다. 이렇게 구성된 탭에서는 동영상 영역을 빠르게 지나갈 때마다 동영상 Release를 처리하게 됩니다. 이러한 비즈니스 로직은 동영상 해제를 통한 메모리 사용으로, 빠르게 상품 구좌로 이동하려는 사용자의 행동을 방해합니다. 그래서 고안한 방식이 지연 비디오 해제(Lazy Video Release)입니다. 지연 비디오 해제(Lazy Video Release) 비즈니스 로직을 간단히 공유하면, 동영상 해제 시도 시 Video Release Pool Manager에 보관하고 있다가, 스크롤이 정지되었을 경우 한 번에 Video Release를 처리합니다. 만약 사용자의 스크롤이 연속으로 이어져 많은 동영상이 Video Release Pool에 저장될 경우, Video Release Pool에서 저장할 최대 개수를 지정합니다. 만약 최대치를 넘어가면 선입선출(FIFO:First In First Out) 방식으로 가장 먼저 Video Release Pool에 저장한 Video를 Release 처리합니다. Lazy release Video 예시 이미지 영역 해결 방안이미지 로드 시 쿠키 사용 방지이미지를 표시하는 방식 중 성인 상품에 대한 체크 로직을 담당하는 이미지 뷰가 별도로 존재합니다. 여기서 성인 여부에 대한 값을 Cookie 값으로 바라본 것을 SharedPreference의 저장된 값을 기준으로 성인 여부를 체크했습니다. Cookie의 데이터를 가져온다는 것은 Cookie의 양이 비대해질 경우, 속도 저하가 되는 부분을 가져올 수 있습니다. 그래서 Cookie의 데이터를 가져오는 로직에서 SharedPreference를 통해서 성인 여부 값을 가져오도록 수정하여, 메모리 사용을 최소화 할 수 있습니다. 만약 SharedPreference를 변경했지만 여전히 느리다면, 성인 여부 값을 Application의 Global Value로 구성하여 해당 데이터를 메모리에 올려놓고, 성인 이미지 표시 여부를 판단할 때 사용할 예정입니다. 현재는 SharedPreference를 활용하는 것으로도 속도 향상을 경험하여 이 방식을 유지하고 있습니다. 비동기 화면 해결 방안Preload Async API비동기 통신을 한 번에 호출하는 것은 API 비용 문제에 영향을 줄 수 있습니다. 하지만 이런 API 호출 문제는 비동기 통신에 대한 캐시 정책으로 해결할 수 있는 방안입니다. 이러한 비용 문제를 떠나서 홈 화면의 쾌적한 스크롤을 우선순위를 두고 대응한다면, 비동기 API를 상품 구좌가 보였을 때 호출하는 것이 아니라 홈 화면이 노출 시점에 비동기 API를 사전에 호출하여 UI를 구성할 수 있습니다. 이러면 스크롤 중간에 UI 튀는 현상을 최소화할 수 있습니다. 또한 한 탭에서 한 번에 많은 비동기 API 을 호출하다 보면, 모듈의 INSERT, DELETE가 동시에 이뤄질 수 있습니다. 이렇게 동시에 INSERT, DELETE가 발생하면 INDEX가 꼬이는 현상이 발생하여 예상과 다른 결과를 보일 수 있습니다. 이러한 현상을 방어하기 위해서 모든 동작을 TASK로 구분하여 Queue에 담았습니다. 이렇게 담은 TASK는 Queue에서 하나씩 꺼내서 UI 처리하도록 구성하여, 동시에 요청하는 INSERT, DELETE에 대한 index 틀어짐을 대비할 수 있었습니다. 동시에 Api 호출로 인한 Insert, Delete Task Queue 활용 예시 속도 개선 결과는?홈 탭 속도 개선을 위해 여러 시나리오를 갖고 진행했습니다. 속도 개선을 위한 시나리오 리스트 여러 시나리오를 넣고 테스트 한 결과, 앞서 소개한 해결 방안인 Lazy Prepare Video, Lazy Release Video, Preload Dynamic API를 적용했을 때, 이전 버전에 비해 홈 탭 스크롤 시 UI 렌더링 지연 현상(버벅임)이 확연히 줄어든 것을 확인했습니다. 이 해결 방안이 맞는지 검증하기 위해 온스타일 탭 중 홈 탭이 아닌 다른 탭인 ‘특가’ 탭에도 적용했는데, 홈 탭과 같이 특가 탭도 이전에 비해 빨려졌다는 긍정적인 평가를 받았습니다. 홈 탭 스크롤 속도 개선에 대한 객관적인 증표도 수치를 뽑으려고 했으나, 아쉽게도 스크롤이 빨라졌다는 기록에 대한 아이디어 부족으로 실패했습니다. 홈 탭 스크롤 속도 변화에 대한 수치화를 실패한 아쉬운 마음으로, AS-IS와 TO-BE 비교 영상을 준비했습니다. AS-IS 영상에는 스크롤 도중 툭툭 끊기는 현상을 발견할 수 있는데, TO-BE에서는 AS-IS 영상의 버벅임이 개선됨을 확인할 수 있습니다. 마치며이번 속도 개선 프로젝트는 메모리의 활용을 줄이는 방식에 초점을 두고 문제해결에 접근했습니다. 이러한 스크롤 버벅임은 모든 개발자가 맞을 수 있는 문제고, RecyclerView 스크롤에 대한 UI 처리에 대해서는 많은 고민이 필요합니다. “나는 내가 아무것도 모른다는 것을 안다.”(“I know that I know nothing.”)소크라테스(Socrates) 위 소크라테스의 명언처럼 사람은 처음부터 완벽한 결과물을 만들어 낼 수 없듯이, 홈 탭 스크롤 개선에 대해서도 현재의 해결 방식이 완벽하지 않을 수 있습니다. 그러나 이 방식을 공유한 이유는 이 해결 방식에서 인사이트를 얻어, 더 좋은 해결 방안이 나올 수 있기 때문입니다. 저처럼 스크롤 이슈를 맞은 개발자 분들에게 도움이 되었으면 좋겠습니다. 무엇보다 개발자는 개발엔 완성이 없다는 생각을 갖고 임해야 한다고 생각합니다. 이런 점에서 홈 탭 속도 개선은 앞으로 다른 버전으로 계속 이뤄질 것이고, 앞으로 개발되는 모든 탭에서도 속도 개선을 위한 고뇌가 이어질 것입니다.<원문>Android 온스타일 홈탭 스크롤 속도 개선 Mission Possible ©️요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.