유나이트 서울 2025 - Mobile native debugging and ANR

 

유나이트 서울 2025 - Mobile native debugging and ANR

유니티 코리아 유나이트 2025 서울 행사에 참석했다. 이 포스트는 “Mobile Native Debugging and ANR” 강연 내용을 정리한 것이다.

ANR(Application Not Responding)은 모바일 게임 개발에서 가장 골치 아픈 문제 중 하나다. 이 글이 ANR 문제로 고민하는 모든 개발자들에게 실질적인 도움이 되길 바란다.

들어가며

ANR의 정의와 특징

ANR은 애플리케이션이 특정 작업을 지정된 시간 안에 완료하지 못할 때 OS에서 보고하는 현상이다. iOS에서는 App Freezing이라는 용어를 사용하고, 안드로이드에서는 ANR이라고 부른다.

유니티로 개발된 게임에서 ANR이 특히 복잡한 이유는 두 개의 서로 다른 메인 스레드가 직접 연동하여 동작하기 때문이다:

  • 유니티 메인 스레드: 게임 로직, 렌더링, 물리 연산 등을 담당
  • 안드로이드 메인 스레드: 시스템 이벤트, 시스템 콜백 등을 처리

안드로이드 ANR의 5가지 유형

안드로이드에서 발생하는 ANR은 크게 5가지로 분류할 수 있다:

  1. 입력 이벤트 타임아웃: 사용자 입력(터치, 키) 후 5초 이내 미응답
  2. 서비스 작업 타임아웃: 특정 서비스 작업이 수초 안에 미완료
  3. startForeground() 지연: 포그라운드 서비스 시작 호출 지연
  4. Broadcast Receiver 타임아웃: 브로드캐스트 수신기 처리 지연
  5. JobScheduler 타임아웃: 작업 스케줄러 처리 지연

중요: 5초라는 시간은 OEM에서 조절 가능하므로 절대적인 수치는 아니다. 유니티 관련 ANR의 대부분은 입력 이벤트 타임아웃에 집중되어 있다.

ANR 감지의 한계점

ANR 분석에서 가장 큰 문제는 시스템이 제공하는 트레이스가 항상 정확한 문제 지점을 보여주지 않는다는 점이다.

ANR 감지 과정:

  1. 유저 입력 이벤트 발생
  2. 5초 동안 OS에서 응답 대기
  3. 응답 없음을 감지하고 스레드 덤프 수집 시작
  4. 수초 후 ANR 다이얼로그 표시

이 과정에서 시간 지연이 발생하기 때문에, 실제 ANR이 시작된 시점의 스택 트레이스가 아닌 감지 시점의 정보만 볼 수 있다. 따라서 ANR의 근본 원인은 애플리케이션 코드, 유니티 엔진, 타사 SDK, 안드로이드 시스템 라이브러리 등 다양한 레벨에 숨겨져 있을 수 있다.

효과적인 ANR 디버깅 전략

1. 네이티브 디바이스 로그 수집

ANR 디버깅의 첫 번째 단계는 실시간 로그 수집이다.

무선 디버깅 설정

# 안드로이드 스튜디오 또는 커맨드 라인을 통한 페어링
adb pair <device_ip>:<port>
adb connect <device_ip>:<port>

무선 디버깅을 사용하면 USB 케이블 없이도 PC와 디바이스를 연결하여 실시간으로 로그를 수집할 수 있다.

안드로이드 Logcat 패키지 활용

유니티에서 제공하는 안드로이드 Logcat 패키지는 다음 기능을 포함한다:

  • 기본 Logcat 기능: 실시간 로그 필터링 및 분석
  • 메모리 툴: 패키지별 메모리 점유율 모니터링
  • 인풋 툴: 앱 실행 중 원격 키 입력 테스트
  • 스택 트레이스 유틸리티: 심볼릭 디버깅 지원

ADB를 통한 ANR 데이터 추출

adb shell dumpsys dropbox data_app_anr

추출된 데이터는 방대하므로 data_app_anr 옵션으로 필터링하여 필요한 정보만 확인해야 한다.

2. 버그 리포트 생성

가장 포괄적인 정보를 얻으려면 버그 리포트를 생성하는 것이 좋다:

  1. 개발자 옵션에서 “버그 리포트” 활성화
  2. 전원 버튼 메뉴에서 “버그 리포트” 선택
  3. 생성된 리포트를 안드로이드 문서를 참고하여 분석

주의: 버그 리포트는 매우 복잡하고 방대하므로 사전에 분석 방법을 숙지해야 한다.

3. 로컬 테스팅 팁

타겟 디바이스 집중 분석

  • ANR이 가장 빈번한 안드로이드 버전 및 디바이스 확인
  • ANR이 최초 보고된 버전 체크로 변경점 파악

프로파일링 도구 활용

  • Perfetto: 유니티 프로파일러가 안드로이드 메인 스레드를 체크할 수 없으므로 Perfetto 사용
  • 안드로이드 시뮬레이터: 안드로이드 스튜디오로 게임을 내보내고 문제 디바이스 환경 재현

타사 SDK 격리 테스트

  1. 모든 타사 패키지 제거 후 ANR 발생 여부 확인
  2. 점진적으로 하나씩 추가하며 원인 SDK 식별

ANR 발생 위치별 분석 및 해결 방법

1. 게임 코드 사이드 오류

식별 방법

스택 트레이스에서 libil2cpp.so 라이브러리 함수가 표시되면 C# 코드에서 오류가 발생했음을 의미한다.

해결 전략

코드 분석:

  • C# 프로젝트에서 의심스러운 코드 검색
  • 안드로이드 스튜디오 프로젝트로 내보내서 변환된 소스 코드 분석

엔진 모듈 검토:

  • Physics, 렌더링 등 엔진 모듈 사용 여부 확인
  • 최근 릴리즈에서 발생된 것인지 조사

콜백 최적화:

  • OnApplicationFocus, OnApplicationPause 이벤트 콜백의 과도한 부하 체크
  • 유니티 프로파일링 도구로 로직 정리

메인 스레드 IO 작업 식별:

  • Android의 StrictMode 툴 활용
  • JNI 등 네이티브 안드로이드 코드 호출 부분 검토

2. 엔진 사이드 오류

식별 방법

스택 트레이스에서 libmain.so 또는 libunity.so 라이브러리 함수가 표시되면 엔진 코드에서 오류가 발생했음을 의미한다.

해결 전략

엔진 사이드 오류는 개발자가 직접 해결하기 어려우므로 다음 접근법을 사용한다:

  1. 커뮤니티 검색: 유니티 포럼에서 유사한 문제 사례 확인
  2. 버그 리포팅: 유니티 엔진 내장 버그 리포팅 시스템 활용
  3. 릴리즈 노트 확인: 최신 유니티 LTS에서 관련 개선 사항 여부 체크
  4. 액티비티 확인: 사용자 지정 Activity의 Java 코드 검토

3. 타사 SDK 플러그인 오류

식별 방법

  • 모든 타사 SDK 제거 후 점진적 추가로 원인 SDK 식별
  • 스택 트레이스에서 타사 라이브러리 함수 확인

해결 전략

  1. 버전 업데이트: 사용 중인 모든 타사 라이브러리 최신 버전 확인
  2. 커뮤니티 검색: 유니티 포럼에서 해결 방법 확인
  3. 구글 플레이 ANR 보고서: 구글에서 이미 인지한 문제인지 확인
  4. SDK 제작자 문의: 최후 수단으로 버그 리포트 제출

4. 시스템 라이브러리 오류

시스템 라이브러리 오류는 일반적으로 개발자가 제어할 수 없는 부분이다. 다행히 ANR의 많은 부분을 차지하지는 않으며, 구글 개발자 문의나 로그 추가를 통해 문제 범위를 좁히는 것이 최선이다.

주요 ANR 사례별 해결 방안

1. 크롬 관련 ANR

원인

  • 웹뷰 사용 시 크로뮴 라이브러리 문제
  • 잘못된 구현, 오래된 웹뷰 버전
  • 높은 시스템 리소스 사용량
  • 지원되지 않는 셰이더 사용

해결 방안

영향을 받는 콘텐츠를 식별하기 위해 웹 페이지가 로드, 표시, 닫힐 때 디버깅 정보를 추가해서 원인을 파악하는 데 도움을 받을 수 있다. 광고 비활성화 테스트 등을 통해 잘못된 광고 구현이 있었는지 간접적으로 확인할 수 있다. 메모리 통계 디버깅 정보를 포함해서 메모리와 관련된 이슈인지 확인하는 것도 필요하다.

2. OnApplicationPause ANR

원인

유니티의 가장 빈번한 ANR 중 하나로, OnApplicationPause 콜백에서 과도한 작업 수행 시 발생한다.

발생 과정

  1. 액티비티가 유니티 런타임에 일시 중지 알림
  2. 유니티가 모든 MonoBehaviour의 OnApplicationPause 호출
  3. 사운드, 렌더링, 게임 루프, 애니메이션 등 모든 구성 요소 중지
  4. UAP(Unity Android Player)가 세마포어로 엔진을 4초간 대기
  5. 5초 초과 시 ANR 발생

해결 방안

전반적으로 일시 중지 또는 재개 이벤트 중에 스크립트 실행이 너무 오래 걸리지 않도록 해야 한다. 게임을 프로파일링하고 OnApplicationPause에 과도한 작업이 들어 있는지를 확인해야 한다. Stopwatch를 사용해서 총 시간을 측정할 수 있다.

IO 작업 또는 동기 네트워크 요청을 피할 필요가 있다. 가능하면 닷넷 태스크, 유니태스크 플러그인 또는 새로 추가된 유니티 API를 사용해서 작업을 다른 스레드로 이동해서 처리하는 것이 좋다. 유니티 2023.2(유니티 6)에서는 C# async 및 await 키워드를 사용할 수 있게 되었기 때문에 단순화된 비동기 프로그램 모델을 사용할 수 있다.

3. Unity Surface Destroy ANR

원인

게임이 백그라운드로 전환될 때 그래픽 장치 해제 과정에서 발생하는 ANR이다.

발생 상황

  • 게임 최소화
  • 애플리케이션 전환
  • 홈 버튼 누름
  • 전화 수신
  • 디스플레이 잠금
  • 게임 종료

기술적 원인

유니티의 UpdateDisplayInternal 메서드에서 다음 과정을 거친다:

  1. 초기 카운트 0인 세마포어 생성
  2. 그래픽 상태 재생성 네이티브 메서드 호출하는 Runnable 생성
  3. Semaphore.release()로 허용 카운터 증가
  4. Semaphore.acquire()로 최대 4초간 허용 대기
  5. GPU가 4초 이상 세마포어를 유지하고 추가 1초가 걸리면 ANR 발생

해결 방안

해당 문제는 재현이 굉장히 어렵다. 유저 사이드에서 겪는 이슈이기 때문에 재현이 어렵고, 추정하기로는 애플리케이션과 디바이스들의 컨디션 등 그래픽 장치 간의 잘못된 상태와 관련이 있을 것이라고 보고 있다. 유니티 모바일 팀에서는 그래픽 장치가 해제될 때까지 대기 시간을 줄이는 솔루션을 테스트하고 있고, 해결 방안 등을 모색하고 있는 상태다.

4. Unity SendMessage Block ANR

원인

  • C# 스크립트의 과도한 작업
  • 애플리케이션 재개 시 큐잉된 메시지 일괄 처리
  • 타사 SDK 작업량과 결합된 메인 스레드 부하

해결 방안

애플리케이션 일시 중지 동안 모든 코드 작업이 필요한지를 확인하거나, 사용자의 상태를 로컬 DB, 메모리에 저장해서 재개가 될 때 사용하는 방법을 사용할 수 있다. 또한 일시 중지 기간 외에도 이런 작업을 완료할 수 있는지 확인이 필요하다.

메시지를 처리하는 C# 작업을 다른 스레드로 이동하고, 코드가 유니티 메인 스레드 컨텍스트에 의존하지 않는 경우, 태스크(Task) 형식으로 태스크 접근 방식을 사용해서 이 문제를 해결할 수 있을 것으로 보인다.

가능하면 게임이 일시 중지되었을 때 플러그인에서 여러 메시지를 보내는 부분을 지양하고, 엔진이 일시 중지된 동안 해당 메시지를 받아서 처리할 수 없다는 부분을 인지해야 한다. 그리고 일시 정지에 빠지게 됐을 때 메시지를 바로바로 등록하지 말고 마지막 데이터 상태만 게임으로 전송할 수 있도록 알고리즘, 로직을 구현하는 것이 좋다.

5. nativePollOnce 메인 스레드 Idle ANR

특징

구글에서 플래그를 지정한 ANR로, 근본 원인이 아니므로 무시해야 한다.

발생 이유

  • ANR 감지 후 스레드 덤프 캡처 시간 동안 실제 ANR이 해결됨
  • 덤프에 실제 ANR 관련 정보가 포함되지 않음
  • 시스템 리소스 사용량 과다가 주요 원인

해결 방안

문제가 발생하는 영역을 좁히기 위해 애플리케이션에 더 많은 추적 정보를 기록하거나, 백트레이스를 사용해서 2에서 3초의 시간 제한을 갖는 워치독(watchdog) 서비스를 구현하는 것도 방법이 될 것 같다. 이렇게 하면 초기에 행(hang)이 발생했을 때 식별하여 보고할 수 있다.

6. Install Referrer ANR

원인

광고 분석을 위한 Install Referrer 처리 중 느린 바인더 호출로 인한 ANR이다.

해결 방안

  1. 타사 개발자 소통: SDK 제공업체에 문의
  2. 온라인 검색: 유사한 문제 해결책 확인
  3. SDK 버전 업데이트: 최신 버전에서 수정 여부 확인
  4. 점진적 롤아웃: 작은 규모로 테스트 후 확대

구글 SDK 인덱스 활용: 구글에서 제공하는 SDK 인덱스 페이지를 통해 SDK 채택, 유지, 제거 결정에 도움이 되는 정보를 확인할 수 있다.

7. 바인더 콜 관련 ANR

원인

안드로이드의 프로세스 간 통신(IPC) 메커니즘인 바인더 호출이 느려서 발생한다.

해결 방안

일반적으로 바인더 호출은 리소스도 많이 써서 비용이 많이 들고 지속 시간을 예측할 수 없으므로, 메인 스레드에서 바인더 호출을 하지 않는 것이 좋다. 항상 SDK 릴리즈 노트에서 수정 사항을 확인하고 최신 버전으로 업데이트를 해야 한다.

플러그인을 개발하고 바인더 메커니즘을 사용하는 경우, 성능을 확인하기 위한 바인더 호출을 계측하고 모니터링을 하는 것이 좋다. 심볼화된 스택 트레이스를 SDK 개발자에게 제공해서 해당 문제를 해결하는 데 도움을 줄 필요가 있다.

8. 메인 스레드 IO ANR

원인

메인 스레드에서 IO 작업 수행 시 유니티 메인 스레드와 안드로이드 메인 스레드 간 동기화 과정에서 발생한다.

해결 방안

성능을 확인하기 위해 바인더 호출을 계측하고 모니터링을 해야 한다. 메인 스레드에서 락(lock) 사용을 최소화할 필요가 있다. 락 소유자가 너무 오랫동안 정지하지 않도록 유념해서 로직을 구현해야 하며, IO 또는 바인더 호출과 같이 예측할 수 없는 작업을 실행할 때는 락을 지양하는 것이 좋다.

9. 네이티브 락 경합 ANR

원인

메인 스레드가 뮤텍스 같은 네이티브 동기화 루틴을 기다리며 차단될 때 발생한다.

해결 방안

네이티브 동기화 루틴은 정확한 락이나 락이 유지되는 위치에 대한 세부 내용을 제공하지 않기 때문에, 소스 코드를 사용할 수 있는 경우 소스에서 잠긴 뮤텍스를 찾은 다음, 뮤텍스가 획득되는 다른 코드의 위치를 찾아야 한다.

여러 스레드가 동일한 락을 자주 경쟁하는 경우 안드로이드 스튜디오의 프로파일러를 사용하여 잠재적인 락 경합을 감지할 수 있다. 영향을 받는 모델의 그래픽 품질을 낮추는 것이 가장 중요한 문제 해결 방법으로 꼽히고 있다.

분석 도구 및 리소스

1. ApplicationExitInfo API

유니티 6에서 내장 지원되며, 이전 버전에서는 별도 패키지 설치가 필요하다.

제공 정보:

  • 로드된 라이브러리 목록
  • ANR 내부 메트릭
  • 스레드 스택 트레이스

2. 구글 제공 도구

StrictMode: 메인 스레드 IO 작업 감지를 위한 도구

ADB 스크립트: 커스텀 분석 스크립트 작성 가능

3. Firebase Test Lab

구글에서 제공하는 클라우드 기반 테스트 환경으로 다양한 디바이스에서 ANR 테스트가 가능하다.

메모리와 ANR의 관계

로우 메모리 상황에서의 ANR

라이브 서비스에서는 유저 디바이스 상태를 직접 확인할 수 없어 메모리와 ANR의 직접적 연관성을 증명하기 어렵다. 하지만 유니티에 보고되는 사례 중 ANR 시 로우 메모리 콜백이 호출되는 경우가 발견되고 있다.

발생 상황:

  • 액티비티가 백그라운드로 전환
  • 안드로이드 OS에서 메모리 경고 수신
  • 메모리 정리 과정에서 ANR 발생

대응 방안:

애플리케이션이 백그라운드로 전환될 때 메모리 정리 작업을 수행하고, 불필요한 리소스를 해제하여 메모리 부족 상황을 예방하는 것이 중요하다.

마무리: ANR 없는 안정적인 게임을 위한 체크리스트

개발 단계에서의 예방

  1. 메인 스레드 최적화
    • OnApplicationPause/Focus 콜백 최적화
    • IO 작업의 비동기 처리
    • 락 사용 최소화
  2. 타사 SDK 관리
    • 최신 버전 유지
    • 점진적 통합 테스트
    • 성능 영향 모니터링
  3. 메모리 관리
    • 백그라운드 전환 시 리소스 정리
    • 메모리 사용량 모니터링
    • 적절한 가비지 컬렉션

운영 단계에서의 모니터링

  1. 실시간 모니터링
    • ANR 발생률 추적
    • 특정 디바이스/OS 버전 집중 분석
    • 사용자 피드백 수집
  2. 지속적 개선
    • 정기적인 성능 프로파일링
    • 유니티 엔진 업데이트 검토
    • 커뮤니티 정보 공유

ANR은 복잡하고 다양한 원인을 가진 문제지만, 체계적인 접근과 지속적인 모니터링을 통해 충분히 해결할 수 있다. 가장 중요한 것은 사용자 경험을 최우선으로 생각하며 안정적인 게임을 만들어가는 것이다.

이 글에서 다룬 사례들과 해결 방안들이 여러분의 게임을 더욱 안정적으로 만드는 데 도움이 되길 바란다.