회사 업무 중, 대량의 사진을 뷰페이저로 가져오고, 해당 사진을 비율 별로 보여줘야 하는 작업을 맡았다. 구현하고 테스트 해보던 중, 간헐적으로 아래의 오류가 발생하면서 앱이 강제종료됨을 확인할 수 있었다. 어째서 저런 오류가 발생했는지 알아보고, 어떻게 해결했는지도 기록으로 남겨두자.
Canvas: trying to use a recycled bitmap 오류는 이미 재활용(recycled)된 Bitmap 객체를 다시 사용하려고 시도할 때 발생한다. 안드로이드에서 Bitmap이 recycle() 메서드를 호출하여 해제된 이후에는 더 이상 사용할 수 없으며, 이를 다시 사용하려고 하면 이 오류가 발생한다고 한다.
Glide는 Bitmap을 직접 관리, 재활용 하는 매커니즘을 제공하지만, 사용자가 이 과정에서 Bitmap을 직접 참조하거나 재활용하려고 하면 충돌이 발생할 수 있다. 특히 나의 경우, 서버로부터 받아온 이미지 URL을 Glide 를 통해 로드하고, 이후 바로 imageView로 노출시키는 게 아니라 적절한 처리 후에 Canvas에 그리는 작업이 중간에 필요했다.
이때, 뷰페이저를 통해서 이미지를 약 100개 이상 스크롤해 보고 나면 간헐적으로 앱이 크래시 되었다. 그렇다면 해결방법을 알아보고, 그를 통해 원인에 대해 함께 알아보자.
AS-IS
Glide는 로드한 Bitmap을 내부적으로 관리하면서 BitmapPool에 반환하여 재활용한다. 이 방식은 메모리 효율성을 높이지만, 동시에 Bitmap이 다른 곳에서 사용될 때 예상치 못한 변경이 발생할 수 있어 위험할 수도 있다.
- Glide가 onLoadCleared()에서 리소스를 BitmapPool로 반환하면, 그 Bitmap은 더 이상 사용되지 않도록 관리된다
- 그 후 같은 Bitmap을 다른 곳에서 재사용하려는 시도가 발생할 수 있는데, 이때 Bitmap 내용이 이미 변경되어 있을 수 있다
나의 경우, onResourceReady()에서 Glide로부터 로드된 원본 Bitmap을 View에 설정한 뒤, 이 Bitmap을 계속 참조하려고 해 문제가 발생했다.
- Glide는 onLoadCleared()가 호출되면 해당 리소스를 BitmapPool로 반환하거나 해제할 수 있다
- 그 결과, 원본 Bitmap이 재활용되고 내용이 변경되면, 이미 설정된 뷰에서 그 Bitmap을 여전히 참조하는 상황에서 오류가 발생할 수 있습니다. 이때, Canvas: trying to use a recycled bitmap 오류가 발생했다
TO-BE
따라서 원본 비트맵을 계속 참조해 에러가 발생하는 상황을 없애고, 안전하게 사용하기 위해 기존 비트맵을 복사했다.
resource.copy()를 호출하여 원본 Bitmap을 복사하면 Glide가 관리하는 Bitmap과 별도의 Bitmap 인스턴스를 생성한다. 이 복사된 Bitmap은 Glide의 관리 범위에서 벗어나게 되므로, Glide가 재활용하거나 내용을 변경하더라도 복사된 Bitmap에는 영향을 미치지 않아 안전하다.
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(
resource: Bitmap,
transition: Transition<in Bitmap>?
) {
// 생략
resource.copy(resource.config, true) // 원본 비트맵 복사
// 생략 (리소스 복사 이후 원하는 작업 수행)
}
}
다음과 같이 onResourceReady가 되면, 리소스를 복사해 Glide의 메모리 관리 시스템과 독립적으로 사용할 수 있도록 만들어주었다. 이를 통해 안전한 비트맵을 얻어 사용할 수 있었다.
Glide의 메모리 관련 이슈는 이미지를 로딩할 때 개발자들이 신경써야 하는 문제이고, 리소스의 적절한 참조와 해제, 동시 접근 방지 등 코드로서 생각하면서 써야함을 알 수 있었다. 그래서 가을동안 Glide에 관한 글을 이어서 작성했고, 이 트러블 슈팅으로 막을 내린다. !! 솔직히 이전에는 그렇게 다량의 이미지를 불러오지 않았고, 이미지에 처리를 가해본적이 없기에 Glide의 원리나, 비트맵의 재활용, 앱의 메모리 관리에 대해 깊이 생각해본적이 없었다.
하지만 이 문제를 해결하고, 블로그를 작성하면서 해온것만 해도
- Glide 공식문서 읽기
- 비트맵이 정확히 무엇인지 알아보기
- BitmapPool에 대해 알아보기
- 기기의 메모리 관리 전략
- Glide의 캐시 관리 전략
- GC 원리
등 연이어서 공부할거리가 생겨났다. 개인적으로는 필요성에 의해 했던 공부와 포스팅이어서 의미 깊었고, 실제로 이 포스팅을 하던 중 회사에서 다른 Glide 사용 업무를 맡았고, 그 과정에서 공식문서의 가이드라인을 복습할 수 있었다. 덤으로 내 포스팅까지 참고 ^*^
혹시, 오류 트러블 슈팅을 위해 검색을 타고 들어온 개발자들이라면 Glide의 OOM 해결 전략과, 리소스 관리에 대해 열심히 .. 정리해 놓았으니 참고하면 도움이 될 수 도 있겠다 ㅎ ㅎ 결국 모든건 리소스 ....... 참조... 누수 ..... 재활용 ... 메모리 ... 어쩌구인듯 ! 아 또 다른 업무 때문에 앱 메모리 관리에 관심을 가지기도 했다. 다음글은 해당 주제로 돌아오게 될 것 같다 ! 아무튼 끝 ~ Glide 안녕하자 제 ~ 발 ~
[Android] 안드로이드 UI 어디까지 아는데 ? - (6) 이미지 로더 Glide의 BitmapPool, OOM (Out Of Memory) 방지 전
5편동안 이어진 Glide의 마지막 글이고, 사실상 이 시리즈를 작성하게 된 계기이다. BitmapPool 이란 ?BitmapPool은 Glide에서 Bitmap 객체를 재사용하기 위해 사용하는 메모리 풀이다. 이미지 로더가 이
sxunea.tistory.com
'안드로이드 > 개발기록' 카테고리의 다른 글
[Android] Hilt를 사용한 Clean Architecture, Mock RepositoryImpl (0) | 2025.02.20 |
---|---|
[Android] TabLayout 화면 꽉차게 대응하기 (feat. 갤럭시 z 폴드야 ..... ) (0) | 2024.12.01 |
[Android] TextView 글자 기준 자동 줄바꿈 구현하기 : CustomAutoLineBreakTextView (0) | 2024.11.10 |
[Android] 안드로이드 앱 CI / CD 적용하기 with Github Actions - (2) CD 적용 (4) | 2024.10.29 |
[Android] 안드로이드 앱 CI / CD 적용하기 with Github Actions - (1) CI 적용 (4) | 2024.10.27 |