문의 시 사용하시는 SDK 버전 정보와 플랫폼(Android / iOS) 및 디벨로퍼스 앱ID를 알려주세요.
플러터에서 지도 앱 SDK를 사용합니다.
안녕하세요. Android KakaoMap SDK 사용 중 pause() 이후의 렌더 루프 동작에 대해
문의드립니다.
환경
- SDK: com.kakao.maps.open:android:2.15.0
- 기기: Galaxy A15 (SM-A155N), Android 16 (API 36), MediaTek MT6789
- 구성: Flutter PlatformView 안에서 MapView를 사용합니다.
빠른 화면 복귀를 위해 setFinishManually(true)로 두고, 인스턴스를 유지한 채
탭이 가려질 때 MapView.pause(), 다시 보일 때 MapView.resume()을 호출합니다.
증상
지도를 다른 탭으로 가려 MapView.pause()가 호출된 뒤에도, SDK가
RenderCallback$OnVsync를 Choreographer.postVsyncCallback으로 계속 등록해
매 프레임 메인 스레드가 깨어납니다. GLSurfaceView의 GL 스레드도 계속
돕니다. 그 결과 지도가 화면에 전혀 보이지 않는 상태에서도 앱이 CPU
코어 1개의 약 24%를 계속 사용합니다(그중 메인 스레드가 약 16%).
pause()가 정상 호출된 것은 확인했습니다. 저희 로그에
map_view_pause reason=map_tab_active=false 가 찍히고, 이후 resume까지
추가 pause/resume 호출은 없습니다.
실측 근거
지도가 가려진 상태에서 앱 프로세스를 12~14초간 프로파일링했습니다.
adb shell “run-as sh -c ‘cd /data/data/ &&
simpleperf record -e cpu-clock:u -p --duration 14 -f 300 -g -o g.data’”
adb shell “run-as sh -c ‘cd /data/data/ &&
simpleperf report -i g.data --sort symbol -n --children’”
618 샘플의 포함 비용(children) 상위입니다.
16.18% android.view.Choreographer.doFrame
11.97% kakao::base::invoke::AsyncInvokableWorker::execute()
8.74% com.kakao.vectormap.graphics.gl.GLSurfaceView$GLThread.guardedRun
7.77% com.kakao.vectormap.internal.RenderCallback$OnVsync.onVsync
4.85% android.view.Choreographer.postVsyncCallback
라이브러리별로는 libK3fAndroid.so가 7.6%이고, 앱이 쓰는 UI 프레임워크
쪽 렌더 라이브러리는 2.6%에 불과합니다. 즉 이 소비는 저희 화면 렌더가
아니라 지도 SDK 경로에서 발생합니다.
시도해 본 것
pause()와 함께 MapView의 뷰 visibility를 GONE으로 내려봤습니다.
dumpsys activity top에서 com.kakao.vectormap.MapView{… G.E…} 로
GONE이 적용된 것까지 확인했지만, 위 프로파일은 그대로였습니다
(Choreographer.doFrame 17.18%, RenderCallback$OnVsync.onVsync 8.59%).
자식 KGLSurfaceView는 부모가 GONE인 동안에도 V로 남고 GL 스레드가
계속 돌았습니다.
문의
- MapView.pause()가 멈추는 범위가 정확히 어디까지인가요? vsync 콜백
등록과 GL 스레드 루프는 pause() 대상이 아닌 것으로 보입니다.
- 인스턴스를 파괴하지 않고(finish 없이) 렌더 루프와 vsync 콜백을
완전히 멈추는 정식 방법이 있나요? 화면 복귀 속도 때문에 finish 후
재생성은 피하고 싶습니다.
- 만약 현재 SDK에 그 방법이 없다면, 지도를 오래 가려두는 화면 구성에서
카카오가 권장하는 패턴은 무엇인가요?
- 2.15.0 이후 버전에서 이 동작이 변경된 이력이 있나요?
지도 앱 SDK를 사용 중입니다.
안녕하세요. 자세하게 문의주셔서 감사합니다.
- 우선, 말씀하신대로 MapView.pause() 로는 GLThread 렌더 루프와 Vsync 콜백에 영향을 주지 않습니다. 지도 엔진 내부에서 렌더링 되는 것 자체를 멈춥니다. 그래서, 지도 렌더링이 멈춘 상태에서도 화면을 회전하거나 전체 MapView 크기가 다른 View 에 의해 바뀌더라도 타이밍에 맞춰 회전되고 리사이즈 될 수 있는 이점이 있습니다.
- 현재로서는 지도종료 말고는 Vsync 콜백자체를 멈추는 방법은 API 에는 없습니다. 등록과 해제를 View 와 렌더링 사이클에 맞춰 엄격하게 맞춰주지 않으면 leak 이 발생하기 때문에 일반적인 API 로 열려있진 않습니다. 다만, 앱 사이클에 맞춰 직접 Vsync 를 제어하고 싶으시다면 아래와 같이 SDK 내부 Vsync 를 해제하시고 직접 Vsync 를 등록해서 제어하셔야 합니다.
private Choreographer.VsyncCallback externalVsyncCallback; // API 33+
private Choreographer.FrameCallback externalFrameCallback; // ~API 32
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ... 기존 코드 ...
mapView.start(getMapLifeCycleCallback(), new KakaoMapReadyCallback() {
@Override
public void onMapReady(KakaoMap map) {
kakaoMap = map;
// 1) SDK 내부 vsync 콜백 제거 (중복 방지)
KGLSurfaceView surfaceView = (KGLSurfaceView) mapView.getSurfaceView();
GLMapRenderer renderer = (GLMapRenderer) surfaceView.getMapRenderer();
renderer.removeVsyncCallback();
// 2) 외부에서 vsync 콜백 등록
startExternalVsync(surfaceView);
}
});
mapView.setFinishManually(true); // 3) !!! 꼭 MapView.start() 이후 호출 !!!
}
private void startExternalVsync(final KGLSurfaceView surfaceView) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
externalVsyncCallback = new Choreographer.VsyncCallback() {
@Override
public void onVsync(@NonNull Choreographer.FrameData frameData) {
// 다음 프레임에도 계속 받으려면 자신을 재등록
Choreographer.getInstance().postVsyncCallback(this);
surfaceView.requestRender();
}
};
Choreographer.getInstance().postVsyncCallback(externalVsyncCallback);
} else {
externalFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
Choreographer.getInstance().postFrameCallback(this);
surfaceView.requestRender();
}
};
Choreographer.getInstance().postFrameCallback(externalFrameCallback);
}
}
private void stopExternalVsync() {
Choreographer c = Choreographer.getInstance();
if (externalVsyncCallback != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
c.removeVsyncCallback(externalVsyncCallback);
}
externalVsyncCallback = null;
}
if (externalFrameCallback != null) {
c.removeFrameCallback(externalFrameCallback);
externalFrameCallback = null;
}
}
@Override
protected void onDestroy() {
// 반드시 해제 (leak 방지)
// onDestroy() 에 넣어놓은 것은 단순히 예제입니다. 꼭, 앱 사이클에 맞춰 해제해야 합니다.
stopExternalVsync();
super.onDestroy();
mapView.finish();
}
- 지도 화면이 오랫동안 안보이는 경우라면, 일반적으로 액티비티 혹은 프래그먼트 pause 사이클에 맞춰서 SurfaceView 가 onPause 가 불리면서 GLThread 까지 wait 되고 있습니다. 다만, 이런 라이프사이클과 별개로 MapView 만 visible 처리로 안보이는 상태라면 강제로 onPause 를 불러서 GLThread 까지 wait 상태로 만드는 2가지 방법이 있습니다.
// 1. 직접 onPause/onResume 호출하기
((KGLSurfaceView) mapView.getSurfaceView()).onPause();
((KGLSurfaceView) mapView.getSurfaceView()).onResume();
// 2. MapView 를 detach/attach 하기
아예 MapView 자체를 부모뷰로부터 remove 함으로써 GLThread 자체를 wait 상태로 멈출 수 있습니다. mapView.setFinishManually(true);(주의: MapView.start() 호출 이후 설정해야함) 로 설정되어 있고, MapView 를 parent view 로부터 필요할 때마다 attach/detach 함으로써 GLThread 자체 쓰레드 상태를 제어하실 수 있습니다
- 2.15.0 이후, 위와 관련 된 내용의 변경사항은 없습니다.
3번 문의내용에 추가 코멘트가 있어서 수정해서 다시 올려습니다.
참고 부탁 드립니다.