Skip to content

DeviceOrientationUitl

조익성 edited this page Nov 14, 2025 · 4 revisions

DeviceOrientationUtil

코드 위치: https://github.com/YangJJune/U-Compass/blob/dev/app/src/main/java/com/ikseong/ucompass/ui/util/DeviceOrientationUtil.kt

기기의 방향(방위각, azimuth)을 실시간으로 감지하고 관리하는 유틸리티 클래스입니다.

핵심 설계 포인트

🎯 1. remember를 활용한 지속적인 방향 측정

remember를 사용하는가?

@Composable
fun rememberDeviceOrientation(): State<Double> {
    val azimuthState = remember { mutableDoubleStateOf(0.0) }
    val history = remember { mutableListOf<Double>() }
    // ...
}
  • 컴포지션 생명주기 동안 상태 유지: remember를 사용하여 리컴포지션이 발생해도 센서 데이터와 히스토리가 초기화되지 않음
  • DisposableEffect와 결합: 센서 리스너가 한 번만 등록되고, 컴포넌트가 제거될 때 자동으로 정리됨
  • 실시간 업데이트: State<Double> 타입으로 반환하여 Compose UI가 자동으로 재구성됨

라이프사이클과의 통합

val lifecycleObserver = LifecycleEventObserver { _, event ->
    when (event) {
        Lifecycle.Event.ON_RESUME -> {
            // 화면이 활성화되면 센서 시작
            sensorManager.registerListener(...)
        }
        Lifecycle.Event.ON_PAUSE -> {
            // 백그라운드로 가면 센서 중지 (배터리 절약)
            sensorManager.unregisterListener(...)
        }
    }
}

📊 2. 기기 흔들림 완화를 위한 3단계 평활화

센서 데이터는 본질적으로 노이즈가 많고 불안정합니다. 이를 해결하기 위해 3단계 평활화 전략을 사용합니다.

1단계: 저주파 필터 (Low-pass Filter)

private const val ALPHA = 0.05f

filteredAccReading[0] = ALPHA * event.values[0] + (1 - ALPHA) * filteredAccReading[0]
filteredMagReading[0] = ALPHA * event.values[0] + (1 - ALPHA) * filteredMagReading[0]
  • 목적: 센서의 순간적인 노이즈 제거
  • 효과: 급격한 센서 값 변화를 95% 억제 (1 - ALPHA = 0.95)
  • 원리: 이전 값의 가중치를 높여 급격한 변화를 완만하게 만듦

2단계: 이동 평균 (Moving Average)

val history = remember { mutableListOf<Double>() }

// 최근 5개 값의 평균
val averageAzimuth = history.average()

// 평균과의 차이가 3도 이상일 때만 업데이트
if (kotlin.math.abs(azimuthState.value - averageAzimuth) >= 3f) {
    azimuthState.value = averageAzimuth
}
  • 목적: 시간에 따른 흔들림 완화
  • 효과: 5개 데이터 포인트의 평균으로 부드러운 방향 전환
  • 장점: 단순 평균보다 응답성 유지하면서도 안정적

3단계: 변화량 임계값 (Threshold)

if (kotlin.math.abs(azimuthState.value - averageAzimuth) >= 3f) {
    azimuthState.value = averageAzimuth
}
  • 목적: 미세한 떨림으로 인한 불필요한 UI 업데이트 방지
  • 효과: 3도 미만의 변화는 무시하여 성능 최적화
  • 이점: Compose 리컴포지션 횟수 감소 → 배터리 절약

평활화 효과 비교

원본 센서 데이터:    45° → 48° → 44° → 50° → 46° → 52° → 45° → ...
1단계 필터 후:      45° → 45.15° → 45.09° → 45.34° → 45.38° → ...
2단계 평균 후:      45° → 45° → 45° → 45.72° → 45.89° → ...
3단계 임계값 적용:  45° → 45° → 45° → 45° → 45° → 48° (3도 차이 발생 시)

3. Compose 통합

@Composable
fun rememberDeviceOrientation(): State<Double>
  • Composable 함수에서 직접 사용 가능: 일반적인 Compose State처럼 사용
  • 자동 업데이트: 센서 값이 변경되면 UI가 자동으로 재구성
  • 라이프사이클 인식: ON_RESUME/ON_PAUSE에 따라 자동으로 센서 등록/해제

기술적 세부사항

항목 설명
센서 딜레이 SENSOR_DELAY_GAME 빠른 업데이트를 위한 게임 모드
저주파 필터 계수 0.05f 센서 평활화 정도 조절
이동 평균 크기 5개 평활화를 위한 히스토리 크기
업데이트 임계값 3도 최소 변화량
출력 범위 0~360도 진북 기준 방위각

방위각 계산 과정

  1. 회전 행렬 계산

    SensorManager.getRotationMatrix(rotationMatrix, null,
        accelerometerReading, magnetometerReading)
  2. 방향 각도 추출

    SensorManager.getOrientation(rotationMatrix, orientationAngles)
  3. 라디안 → 도 변환

    val azimuth = (Math.toDegrees(orientationAngles[0]) + 360) % 360
  4. 평활화 및 업데이트

    • 반올림 (소수점 첫째자리)
    • 히스토리에 추가 및 평균 계산
    • 임계값 확인 후 State 업데이트

참고 사항

DeviceOrientationUtil

  • 센서가 없는 기기에서는 동작하지 않을 수 있음
  • 자기장 간섭이 있는 환경에서는 정확도 저하 가능
  • 실내에서는 GPS와 병행 사용 권장

Clone this wiki locally