Android Session Tracking — 시니어 엔지니어의 관점: OS가 아무것도 약속하지 않을 때

발행: (2025년 12월 31일 오전 12:09 GMT+9)
9 min read
원문: Dev.to

Source: Dev.to

해당 글의 본문을 제공해 주시면, 원본 형식과 마크다운을 유지하면서 한국어로 번역해 드리겠습니다.

Introduction

Android는 사용자가 앱을 떠났을 때 알려주는 약속을 하지 않습니다.
이 글은 트릭에 관한 것이 아니라 Android에서 세션 추적을 올바르게 생각하는 방법—프로덕션 및 SDK 수준—에 관한 것입니다.

대부분의 Android 개발자는 어느 시점에서든 다음 중 적어도 하나를 믿어왔습니다:

  • onStop()은 사용자가 떠났다는 의미
  • onDestroy()는 앱이 종료됐다는 의미
  • 최근 앱 화면에서 스와이프하면 세션이 종료된다는 의미

이 모든 것은 근본적으로 잘못된 생각입니다. Android는 여러분을 위해 앱을 실행하지 않으며, 여러분의 앱은 손님에 불과합니다. 프로세스는 콜백 없이(SIGKILL / LMK) 강제 종료될 수 있으며, 정리(cleanup)에 대한 보장은 전혀 없습니다. 라이프사이클 이벤트는 프로세스가 살아 있는 동안에만 존재합니다.

Implications

세션 종료를 감지하기 위해 콜백을 기다리고 있다면, Firebase, AdMob, Adjust, AppsFlyer와 같은 서비스는 종료를 “감지”하지 않습니다. 이들은:

  1. 프로세스가 포그라운드인지 백그라운드인지 추적
  2. 타임아웃 휴리스틱 적용
  3. 다음 실행 시 세션 종료를 추론

이벤트도 없고, 보장도 없으며—오직 확률적 모델링만 존재합니다. 이것이 고급 사고 방식입니다: 불확실성을 받아들이고 그에 맞게 설계하십시오.

세션 수명 주기

실제 세션은 다음과 같은 상태를 가집니다:

  • Created
  • Active
  • Background
  • Expired
  • Restored (추론됨)

앱이 종료되더라도 세션이 바로 끝나는 것은 아니며, 다음 기준을 통해 더 이상 유효하지 않다고 판단합니다:

  • 백그라운드 지속 시간
  • 다음 실행 시점
  • 영속된 메타데이터

ProcessLifecycleOwner가 제공하는 이벤트:

  • ON_START: 프로세스가 포그라운드로 전환됨
  • ON_STOP: 프로세스가 백그라운드로 전환됨

하지만 다음은 제공하지 않습니다:

  • 앱 강제 종료
  • 최근 앱 화면에서 스와이프
  • 네이티브 크래시

그리고 그래서는 안 됩니다. 시니어 엔지니어는 불가능한 일을 API에 요구하지 않습니다.

비즈니스 규칙으로서의 타임아웃

Timeout은 해킹이 아니라 비즈니스 규칙입니다:

“사용자가 X ms 이상 떠 있으면 세션이 종료된 것으로 간주합니다.”

정확한 값(30 초, 1 분, 5 분 등)은 제품 요구사항에 따라 달라지며, 보편적으로 옳은 값은 없습니다.

앱이 강제 종료될 때:

  • onSessionEnd 콜백이 호출되지 않음
  • 정리 작업이나 플러시가 이루어지지 않음

남아 있는 정보:

  • 마지막 백그라운드 타임스탬프
  • 세션 ID
  • 추론된 종료 이유

다음에 앱을 다시 실행하면 SDK는 다음을 수행해야 합니다:

  1. 지속된 상태를 로드한다
  2. 이전 세션이 종료되었음을 추론한다
  3. 논리적인 세션 종료를 발생시킨다

이러한 2단계 설계는 시니어 엔지니어에게 익숙한 방식입니다.

종료 사유 모델링

  • USER_BACKGROUND_TIMEOUT
  • PROCESS_KILLED_INFERRED
  • APP_UPGRADE
  • CRASH_DETECTED

키워드는 inferred입니다. 좋은 SDK는 “사용자가 X를 수행했다”고 주장하지 않습니다.

세션 추적이 경우:

  • 활동에 의존함
  • 시스템 콜백에 의존함
  • 실시간에 의존함

→ 테스트할 수 없으며, 따라서 신뢰할 수 없습니다.

Source:

Proper SessionTracker

올바른 SessionTracker:

  • 순수 로직이다
  • 클록을 주입한다
  • 가짜 라이프사이클 신호를 받아들인다

테스트 가능하다는 것은 이해하기 쉽다는 뜻이다.

SDK 설계 목표

  • Activity 의존성을 피한다
  • 프로세스가 종료될 때 절대 크래시하지 않는다
  • 메인 스레드를 절대 블록하지 않는다

SDK는 Application.onCreate()에서 초기화되어야 한다.

// src/main/kotlin/com/example/App.kt
class App : Application() {

    private lateinit var sessionObserver: AndroidSessionObserver

    override fun onCreate() {
        super.onCreate()

        sessionObserver = AndroidSessionObserver(
            context = this,
            timeoutMs = 30_000L,
            callback = object : SessionTracker.Callback {
                override fun onSessionStart(session: Session) {
                    // analytics / ads init
                }

                override fun onSessionEnd(
                    session: Session,
                    reason: ExitReason
                ) {
                    // flush analytics / revenue sync
                }
            }
        )

        ProcessLifecycleOwner.get()
            .lifecycle
            .addObserver(sessionObserver)
    }
}

핵심 포인트

  • Activity 라이프사이클을 사용하지 않는다
  • UI 참조가 없다
  • 앱이 강제 종료될 경우 콜백이 절대 호출되지 않을 수 있다 (SDK는 이 가정을 전제로 설계되었다)

대신 SDK는 다음과 같이 동작한다:

  1. 백그라운드에서 세션 상태(타임스탬프 + 세션 ID)를 영구 저장한다
  2. 다음 실행 시 이전 상태를 복원하고, 세션이 종료된 것으로 추론하여 ExitReason.PROCESS_KILLED_INFERRED를 방출한다

종료 사유 예시

  • USER_BACKGROUND_TIMEOUT → 시간 기반 증거
  • PROCESS_KILLED_INFERRED → 이력서 추론 누락

Analytics 및 백엔드 시스템은 이를 추론된 데이터로 처리해야 합니다. SDK는 이를 숨기지 않습니다.

테스트 가능한 로직

All session logic:

  • 프레임워크에 독립적이다
  • 시간을 주입한다
  • 완전히 테스트 가능하다
// src/test/kotlin/com/example/SessionTrackerTest.kt
@Test
fun `session ends after timeout`() {
    val fakeClock = FakeClock()
    val tracker = SessionTracker(fakeClock, timeoutMs = 30_000)

    tracker.onForeground()
    fakeClock.advance(31_000)
    tracker.onForeground()

    assertTrue(tracker.lastExitReason is ExitReason.Timeout)
}

테스트 가능한 로직은 결정론적 동작을 생성한다.

Repository

The complete SDK—including pure SessionTracker logic, AndroidSessionObserver, ExitReason model, and unit tests—is available on GitHub:

🔗 https://github.com/vinhvox/ViO---Android-Session-Tracker

The repository is designed to:

  • Read like documentation
  • Be copy‑paste friendly
  • Not pretend Android is controllable

시니어 엔지니어를 위한 최종 말씀

Android에서 세션 추적은 절대 완벽하지 않습니다. 좋은 SDK는:

  • 제한 사항을 숨기지 않는다
  • 제약 조건을 명확히 밝힌다
  • 불확실성을 명시적으로 모델링한다
  • 비즈니스가 올바른(단순히 보기 좋은 것이 아니라) 결정을 내리도록 돕는다

Android는 절대적인 진실을 제공하지 않지만, 올바르게 설계한다면 추론할 수 있을 만큼 충분한 신호를 제공합니다.

Back to Blog

관련 글

더 보기 »