![[Flutter][Issue] 구글 맵 버벅임 문제 해결](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPOkWZ%2FbtsNuFPv5W9%2FEqNKmDoN3cgNfM1sCZT6u1%2Fimg.png)
문제 상황
구글 맵을 사용중에 엄청나게 렉이 걸리는 상황이 발생했다. 지도 정보가 패칭되고, 핀이 렌더링 되는 순간 엄청나게 렉이 발생한다. 영상에서는 아이폰 16 Pro로 테스트를 해서 그런지 성능으로 커버가 됐지만, 아이폰 12 mini로 테스틀 했을 때는 사용이 어려울 정도로 버벅이는게 눈에 보였다. ▼
|
|
원인 탐색
Debouncer와 Throttler의 부재?
첫번째로 생각난 원인은 Debouncer와 Throttler의 부재. 지도가 움직일 때 마다 API 호출을 하는 것이 버벅임의 주요 원인이 아닐까 생각했다.
하지만 `onCameraIdle` 상태에서만 API를 호출하기에 디바운서도 잘 적용되어있고, 혹시나 구글 맵 패키지의 문제일 수 있기에 로그를 찍어보면서 확인도 다 해보았다. ▼
`onCameraMove`의 `updateCurrentCenter` 역시도 쿼리와 지도에 중앙점을 표시하기 위한 단순 좌표 계산일 뿐, API 호출보다도 더 가벼운 로직을 갖고 있기에 이는 핵심 문제가 될 만한 요소는 아니었다.
전체적인 확인 결과 API 호출과 데이터 처리에서의 문제가 아니었기에 다음 예상 원인으로 넘어갔다.
핀 자체의 문제!
Debouncer의 문제가 아니라면 핀을 렌더링 할 때의 문제라고 생각이 됐다. 그래서 마커를 찍는 부분을 유심히 보고 있다가 문제를 발견했다. 바로 AssetMapBitmap 인스턴스를 생성하는 부분이다. ▼
Set<Marker> mapMarkers({
required void Function(PlaceBottomSheetModel) onTap,
}) {
final nearbyMarkers = state.nearByPlaceList
.map(
(place) => Marker(
markerId: MarkerId(place.id.toString()),
position: LatLng(place.latitude!, place.longitude!),
icon: AssetMapBitmap(AppImage.pin, width: 48, height: 48),
infoWindow: InfoWindow(title: place.title),
onTap: () => onTap(place),
),
)
.toSet();
return nearbyMarkers;
}
Dart에서는 const로 선언이 되면 같은 객체 인스턴스들을 하나의 메모리를 공유하게 된다. 그렇다는 것은 아주 당연하게도, const로 선언되지 않은 인스턴스들은 각자의 메모리를 갖게 된다는 것이다. 그리고 해당 코드는 `AssetMapBitmap`, 즉 핀 이미지 파일을 핀의 개수만큼 메모리에 할당시켜서 사용을 하고 있다. 똑같은 이미지를 사용한다고 해도 이미지 하나를 돌려서 사용하는 것이 아니라 핀의 개수 만큼 이미지를 만들어서 사용하고 있다는 것이다.
"그래봐야 50개면 메모리를 많이 먹지도 않을텐데?"
문제는 단순히 메모리를 많이 차지한다는 것에서 끝나지 않는다. 구글 맵은 네이티브 코드를 사용하고 있기에 메소드 채널을 통한 변환 과정이 필요한데, `AssetMapBitmap`이 이 메소드 채널을 통해 변환이 된 후 렌더링이 된다.
변환은 아주 당연하게도 생성된 모든 `AssetMapBitmap`들에 수행이 되는데, 지도를 움직여서 새로운 프레임을 그릴 때 마다 이 변환이 수행이 된다. 즉, 프레임이 새로 그려질 때 마다 핀의 개수만큼 있는 `AssetMapBitmap`들의 변환이 이루어진다는 것이다. 이로 인해 메소드 채널에 오버헤드가 걸리게 되고 프레임 드롭이 걸리게 되는 것이다.
더 큰 문제는 메모리의 명시적 해제가 안된다는 것이다. 핀마다 있는 `AssetMapBitmap`들이 매번 생성되고 변환되는 과정에서 메모리가 정상적으로 해제가 됐다고 보장받을 수 없다. 이는 구글 맵 SDK 내부를 열어봐야 알 수 있는 부분인데, 열어본다고 해도 그걸 파악하기란 쉽지 않다.
문제 해결
인스턴스 최소화
"그러면 어떻게 해야하는건데?"
메모리 해제도 명시적으로 안되고, `AssetMapBitmap`은 생성된 수 만큼 매번 변환이 이루어지기에 인스턴스를 하나만 만들어 사용하면 해결할 수 있다. 아래와 같이 인스턴스를 하나를 참조시켜 사용하면 위의 문제들을 해결할 수 있다. ▼
final _icon = AssetMapBitmap(AppImage.pin, width: 48, height: 48);
Set<Marker> mapMarkers({
required void Function(PlaceBottomSheetModel) onTap,
}) {
final nearbyMarkers = state.nearByPlaceList
.map(
(place) => Marker(
markerId: MarkerId(place.id.toString()),
position: LatLng(place.latitude!, place.longitude!),
icon: _icon,
infoWindow: InfoWindow(title: place.title),
onTap: () => onTap(place),
),
)
.toSet();
return nearbyMarkers;
}
이미지 사이즈 줄이기
추가로 이미지 크기도 줄여줬다. figma 디자인에서 제공하는 사이즈는 40인데, 시인성 부족 문제로 아주 약간만 크기를 키워 사용하기 위해 이미지를 배율로 추출해서 사용하고 있었다. 그런데 배율을 6배로 해버린 나머지 이미지 크기가 원래 제공한 것보다 6배는 커졌다.
그래서 이를 배율을 1.5배로 줄여서 내보낸 뒤 적용을 했다. 이미지 사이즈는 26KB에서 2KB로 줄었고, 연산량도 10배 가까이 줄게 되었다. 이 과정을 통해 훨씬 쾌적하게 지도가 돌아가게 되었다. ▼
마무리
필자는 처음부터 핀이 너무 많아서가 문제라고 생각했는데, 다른 선임 개발자 분들이 이정도 핀이 이렇게 많은 리소스를 차지할 리가 없다고 하셔서 핀을 처음부터 확인하지 않았다. API 호출 및 다른 원인일 거라고 생각했는데, 어이없게도 핀 문제가 많았고 인스턴스 호출에서의 문제였다는 걸 알게 생각보다 싱겁게 해결을 했다...
'Develop > Flutter' 카테고리의 다른 글
[Flutter][Issue] iOS 카카오 로그인과 버전 관리 이슈 (0) | 2025.04.24 |
---|---|
[Flutter][Error] 난독화와 enum.name (0) | 2025.03.10 |
[Flutter] SizedBox를 Padding 대신 사용하지 말아야 하는 이유 (0) | 2025.02.08 |
[Flutter][Issue] Flutter 카카오 로그인 릴리즈 키 (0) | 2024.12.29 |
[Flutter][Widget][Issue] TabBar 왼쪽에 공간이 생기는 현상 (0) | 2024.12.11 |