와탭 모니터링
2025-10-23
안드로이드(Android) 앱 사용자 경험 측정하는 법 - WhaTap Screen Group 활용 가이드

"Activity onCreate는 100ms밖에 안 걸리는데, 왜 사용자들은 앱이 느리다고 하는 걸까요?" 이 가이드는 Android 앱의 실제 사용자 경험을 정확하게 측정하고 최적화하는 WhaTap Screen Group의 완전한 활용법을 제공합니다.

1. 개발자님, 이 시나리오 익숙하신가요?

흔한 개발 현장의 풍경

금요일 오후, 스프린트 리뷰 미팅에서:

PM: "사용자 리뷰를 보니 '앱이 너무 느리다'는 불만이 많네요."

개발자: "이상한데요? 성능 프로파일러 돌려봤는데
        MainActivity onCreate: 95ms
        HomeFragment onCreateView: 42ms
        모두 정상 범위인데요?"

PM: "그런데 실제 사용자들은 앱 켜고 3초는 기다린다고 하는데..."

개발자: "...🤔"

이 상황, 어디선가 경험해보신 적 있으신가요?

성능 측정의 함정

우리는 보통 이렇게 성능을 측정합니다:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val startTime = System.currentTimeMillis()
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val duration = System.currentTimeMillis() - startTime
        Log.d("Performance", "MainActivity onCreate: ${duration}ms")
        // 출력: 100ms ✓
    }
}

결과: "100ms! 완벽해!"

하지만 사용자가 실제로 체감하는 것은...

사용자가 진짜 기다린 시간 ⏱️

사용자 관점에서 본 실제 타임라인:

[0ms] 앱 아이콘 클릭 👆
[500ms] Splash 화면 표시
[1000ms] MainActivity 시작
[1100ms] HomeFragment 로딩 시작
[1150ms] 레이아웃 인플레이션 완료
[1200ms] API 호출 시작 🌐
[2100ms] 데이터 로딩 완료
[2300ms] 이미지 렌더링 완료
[2500ms] 화면 완전히 사용 가능 ✅

실제 대기 시간: 2.5초!

- 개발자가 측정한 것: 100ms
- 사용자가 느낀 것: 2500ms

25배 차이! 😱

왜 이런 괴리가 생길까요?

우리가 측정하는 것:

  • ✅ Activity.onCreate() 실행 시간
  • ✅ Fragment.onCreateView() 실행 시간
  • ✅ 개별 API 호출 시간

우리가 측정하지 못하는 것:

  • ❌ Splash → Main으로 전환되는 전체 시간
  • ❌ Fragment + WebView 통합 로딩 시간
  • ❌ 사용자가 "화면이 완전히 표시됐다"고 느끼는 시점
  • ❌ 여러 화면을 거쳐가는 하나의 플로우 (예: 결제 과정)

진짜 문제는...

"개별 컴포넌트의 성능은 좋은데, 전체 사용자 경험은 나쁘다"

이것이 바로 우리가 해결해야 할 과제입니다.

부품 하나하나는 완벽 ✓
하지만 조립된 완성품은? ❌

2. 개별 컴포넌트 측정으로는 보이지 않는 것들

"부분의 합 ≠ 전체" - 성능 측정에서도 마찬가지입니다.

기존 측정 방식의 구조적 한계

대부분의 Android APM(Application Performance Monitoring) 도구들은 개별 컴포넌트 단위로 성능을 측정합니다:

// ✅ Activity 측정
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // 측정 시작
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 측정 종료 → 100ms
    }
}

// ✅ Fragment 측정
class HomeFragment : Fragment() {
    override fun onCreateView(...): View? {
        // 측정 시작
        val view = inflater.inflate(R.layout.fragment_home, container, false)
        // 측정 종료 → 50ms
        return view
    }
}

// ✅ API 호출 측정
suspend fun loadUserData() {
    // 측정 시작
    val response = api.getUserInfo()
    // 측정 종료 → 200ms
}

모니터링 대시보드:

✅ MainActivity onCreate: 100ms (정상)
✅ HomeFragment onCreateView: 50ms (정상)
✅ API getUserInfo: 200ms (정상)

"모든 지표가 정상입니다!"

하지만 실제 사용자 경험은?

시나리오: 앱 시작 플로우

사용자 액션: 앱 아이콘 클릭 👆

실제 발생하는 일들:

[0ms] ━━━━ SplashActivity 시작
       └─ onCreate: 80ms
       └─ 로고 애니메이션: 420ms
       └─ 총 500ms

[500ms] ━━━━ MainActivity 시작
         └─ onCreate: 100ms
         └─ 레이아웃 인플레이션: 50ms
         └─ 총 150ms

[650ms] ━━━━ HomeFragment 로딩
         └─ onCreateView: 50ms
         └─ 뷰 바인딩: 30ms
         └─ 총 80ms

[730ms] ━━━━ 데이터 로딩
         └─ API 호출: 200ms
         └─ 파싱: 50ms
         └─ UI 업데이트: 120ms
         └─ 총 370ms

[1100ms] ━━━━ 이미지 로딩
          └─ 메인 배너: 800ms
          └─ 썸네일: 300ms
          └─ 총 1100ms

[2200ms] ✅ 화면 완전히 사용 가능

개발자가 본 것:

  • SplashActivity: 80ms ✅
  • MainActivity: 100ms ✅
  • HomeFragment: 50ms ✅
  • API 호출: 200ms ✅

사용자가 체감한 것:

  • 앱 시작: 2.2초 ⏱️

왜 이런 괴리가 발생할까?

1. 측정 시점의 불일치

개발자 측정 시점:
├─ Activity.onCreate() 시작 → 끝
└─ 100ms

사용자 체감 시점:
├─ 앱 아이콘 클릭 → 화면 완전 표시
└─ 2200ms

차이: 22배!

2. 컴포넌트 간 전환 시간 누락

// Activity A → Activity B 전환

Activity A:
onPause() // 측정 안 됨
onStop()  // 측정 안 됨
         ↓ [50ms 전환 시간 - 블랙홀!]
Activity B:
onCreate() // 여기부터 측정

전환 과정의 시간은 어디에도 기록되지 않습니다.

3. 비동기 작업 추적 불가

class ProductDetailFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // Fragment 생성: 50ms 측정 완료 ✓

        // 하지만 진짜 로딩은 지금부터 시작!
        lifecycleScope.launch {
            loadProductInfo()     // 200ms
            loadProductImages()   // 800ms
            loadReviews()         // 300ms
            // 총 1300ms 추가 - 측정 안 됨!
        }
    }
}

측정 도구 입장:

  • Fragment onCreateView: 50ms ✅ 완료!

사용자 입장:

  • 화면 전환 후 1.3초 동안 스피너만 보고 있음

4. WebView 통합 측정 불가

class HybridFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // Fragment 생성: 50ms

        webView.loadUrl("<https://shop.example.com/products>")
        // WebView 로딩: 1500ms

        // 하지만 측정은 Fragment 50ms만 잡힘!
    }
}

놓치고 있는 것들

기존 모니터링 방식으로는 이런 것들을 측정할 수 없습니다:

1. 복합 화면의 통합 로딩 시간

Splash + Permission + Main + Fragment + Data
= 하나의 "앱 시작"으로 봐야 하는데...
→ 개별 측정만 가능

2. 사용자 여정(User Journey) 추적

[회원가입 플로우]
약관동의 → 정보입력 → 인증 → 완료

각 단계는 빠른데, 전체는 왜 느리지?
→ 측정 불가

3. Time to Interactive (TTI)

언제 사용자가 실제로 화면을 쓸 수 있는가?

Fragment 생성: 50ms ✓
하지만 데이터 로딩까지: 1500ms
→ TTI는 1500ms인데 50ms로 기록됨

4. 디바이스별 실제 체감 차이

플래그십: "빠르네요!" (실제 1.2초)
보급형: "느려요" (실제 3.8초)

하지만 개별 측정은 비슷:
플래그십: 80ms
보급형: 120ms
→ 차이가 안 보임

핵심 문제

결국 문제는:

개별 컴포넌트는 최적화되어 있다
하지만 전체 사용자 경험은 나쁘다
왜? 측정하지 못하고 있으니까

우리에게 필요한 것:

"사용자가 실제로 기다린 시간"을 측정하는 방법

3. 사용자 관점에서 측정하는 Whatap Screen Group

"개별 컴포넌트가 아닌, 사용자가 실제로 기다린 시간을 측정합니다."

Screen Group, 무엇이 다른가?

기존 APM 도구들이 개발자 관점에서 측정했다면, WhaTap Screen Group은 사용자 관점에서 측정합니다.

관점의 전환:

[기존 방식]
개발자: "Activity는 100ms, Fragment는 50ms니까 빠르네요!"

[Screen Group]
사용자: "앱 켜고 2.5초 기다렸어요."
Whatap: "네, 정확히 2500ms 측정됐습니다."

Screen Group의 핵심 개념

1. 화면 그룹(Screen Group)이란?

사용자가 하나의 화면 로딩으로 인식하는 모든 컴포넌트를 묶어서 측정하는 것입니다.

예시: 앱 시작 화면

사용자 입장:
"앱을 켰다""홈 화면이 나왔다"
(이게 하나의 화면 로딩)

실제 내부 구조:
SplashActivity
MainActivity
HomeFragment
API 데이터 로딩
이미지 렌더링

→ 이 모든 과정을 하나의 Screen Group으로 측정

2. 실제 측정 예시

// Whatap Screen Group이 자동으로 측정하는 것:

[앱 시작 Screen Group]
시작: 사용자가 앱 아이콘 클릭
  ├─ [500ms] SplashActivity 표시
  ├─ [100ms] MainActivity 초기화
  ├─ [80ms] HomeFragment 생성
  ├─ [370ms] 초기 데이터 로딩
  └─ [1100ms] 이미지 렌더링
종료: 화면이 완전히 사용 가능해진 시점

총 측정 시간: 2150ms

결과:

📊 WhaTap 대시보드

Screen Group: "앱 시작"
━━━━━━━━━━━━━━━━━━━━━━━━━
총 로딩 시간: 2150ms
컴포넌트 구성:
  - SplashActivity: 500ms (23%)
  - MainActivity: 100ms (5%)
  - HomeFragment: 80ms (4%)
  - 데이터 로딩: 370ms (17%)
  - 이미지 렌더링: 1100ms (51%) ← 병목 발견!

Screen Group의 작동 원리

핵심: 시간 중첩(Time Overlap) 감지

Screen Group의 핵심 원리는 간단합니다: "화면 로드 시간이 중첩(Overlap)되는 화면들을 하나의 그룹으로 묶는다"

예시 1: 앱 시작 (중첩됨 ✅)

시간축 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━→
        0ms         500ms    600ms 680ms    1800ms

Splash  [━━━━━━━━━━━━━━━━━]
                   Main  [━━━━━━━━]
                          Frag [━━━━━]
                               Data [━━━━━━━━━━━━━━━━]

        ← 중첩! →← 중첩! →← 중첩! →

├──────────────────────────────────────────────────┤
        하나의 Screen Group (총 1800ms)

→ 사용자는 1800ms 동안 "앱이 시작되는 중"으로 인식

예시 2: 일반 화면 전환 (간격 있음 ❌)

시간축 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━→
        0ms         1000ms      4000ms    4500ms

Main    [━━━━━━━━━━━━]
                           (3초 공백 - 사용자가 화면 보는 중)
                                      Detail [━━━━━━]

├──────────────────┤                  ├──────────┤
  Screen Group 1                       Screen Group 2

→ Main 끝나고 3초 후에 Detail 시작 (안 겹침!)
→ 사용자: "첫 화면 나왔고, 이제 다른 버튼 눌렀어"

WhaTap Screen Group 실제 대시보드

실제 Whatap 서비스에서 Screen Group 데이터를 어떻게 확인하는지 살펴보겠습니다.

1️. Screen Group 목록 조회

리소스 목록에서 SCREENGROUP EVENT 확인:

  • 각 Screen Group이 언제 발생했는지 시간대별로 확인
  • Screen Group 이름, 시작 시간, 총 소요 시간 한눈에 파악
  • 어떤 화면 플로우가 자주 발생하는지 패턴 분석 가능

2️. 사용자 세션 타임라인

해당 세션의 전체 활동 타임라인:

  • 📱 화면 로딩 (Screen Group)
  • 🌐 네트워크 요청 (API 호출)
  • 📝 사용자 로그 (UserLog)
  • ⚠️ 에러 및 크래시

사용자 관점에서 전체 여정(Journey) 확인:

  • "이 사용자는 앱을 켜고 무엇을 했나?"
  • "어느 시점에 네트워크 요청이 발생했나?"
  • "화면 전환과 API 호출의 관계는?"

3️. Screen Group 상세 - 그룹 내 화면 목록

Screen Group 내부 구성 확인:

  • 하나의 Screen Group에 포함된 모든 화면 컴포넌트
  • 각 화면의 로딩 시간과 비율
  • 어느 화면이 병목인지 즉시 파악

예시:

SplashActivityGroup (총 14.8초)
├─ SplashActivity: 46ms (0.3%)
├─ App Launch (ChainView): 5,729ms (38.7%)
├─ MainActivity: 5,831ms (39.4%)
└─ TestFragment: 941ms (6.4%)

4. 개별 화면 상세 분석

특정 화면을 클릭하면:

  • 해당 화면의 상세 로딩 정보
  • Lifecycle 이벤트 타임라인
  • 관련 네트워크 요청
  • 에러 발생 여부

실전 활용:

"MainActivity가 느리다"
→ Screen Group 목록에서 MainActivity 포함된 그룹 찾기
→ MainActivity 클릭하여 상세 분석
→ 어떤 작업이 시간을 소비하는지 파악

5. 성능 탭 - 메서드 레벨 분석

스레드별 메서드 호출 트리:

  • Main Thread: UI 관련 작업들
  • Background Thread: 네트워크, 데이터 처리
  • 각 메서드의 실행 시간과 호출 순서
  • 병목 메서드 한눈에 파악

실제 케이스:

MainActivity onCreate: 3,740ms
├─ testDepth1: 1,008ms
├─ testDepth2: 908ms
├─ testDepth3: 756ms
├─ testDepth4: 554ms
└─ testDepth5: 302ms

→ 중첩된 메서드 호출이 시간 소비의 원인!
→ 리팩토링 필요

WhaTap Screen Group의 차별화 포인트

1. 사용자 중심 측정

기존: "이 함수는 몇 ms 걸렸나?"
Screen Group: "사용자는 얼마나 기다렸나?"

→ 비즈니스 임팩트를 바로 확인

2. 자동화된 추적

// 설정 한 줄이면 끝
WhatapAgent.Builder.newBuilder()
    .setScreenGroupDelay(2000)
    .build(this)

// 이후 모든 화면 전환이 자동으로 추적됨
// 코드 수정 불필요!

3. 유연한 그룹 정의

// 중요한 플로우는 수동으로 명시
ChainView.getInstance().startChain("checkout-flow", chainId)

// 결제 플로우의 모든 화면을 하나로 측정
장바구니 → 주소입력 → 결제수단 → 완료

ChainView.getInstance().endChain(chainId)
// 전체 결제 소요 시간 측정 완료

4. 실시간 병목 지점 파악

📊 Screen Group 상세 분석

홈 화면 로딩: 2150ms
├─ Activity 초기화: 100ms (5%)
├─ Fragment 생성: 80ms (4%)
├─ 레이아웃 인플레이션: 150ms (7%)
├─ API 호출: 370ms (17%)
└─ 이미지 렌더링: 1100ms (51%) ⚠️ ← 여기!

💡 개선 제안:
- 이미지 크기 최적화
- 지연 로딩 적용
- 캐싱 전략 도입

예상 개선 효과: 2150ms → 1200ms (44% 향상)

Screen Group으로 해결되는 문제들

  • "왜 사용자는 느리다고 하는가?" → 실제 대기 시간이 정확히 측정됨
  • "어디가 느린 건가?" → 병목 지점이 명확히 보임
  • "어떤 디바이스에서 문제인가?" → 디바이스별 성능 분포 확인 가능
  • "최적화 효과가 있었나?" → Before/After 명확히 비교 가능

4. 자동으로? 수동으로? 화면을 묶는 두 가지 전략

"설정 한 줄로 자동화할까? 직접 코드로 명시할까?" Whatap Screen Group은 화면을 묶는 두 가지 전략을 제공합니다:

전략 1: Delayed Close (타이머 기반)
"설정한 시간 내 화면 전환을 자동 감지"

전략 2: ChainView (체인 기반)
"중요한 플로우는 직접 연결해서 확실하게"

전략 1: Delayed Close - 자동으로 기다리기

핵심 아이디어: "마지막 화면이 종료되어도 바로 Screen Group을 닫지 말고, 혹시 다음 화면이 올까봐 조금 기다려보자"

시간축 ━━━━━━━━━━━━━━━━━━━━━━━━━━→

MainActivity 종료 [━━━━━━]
                    [2-3초 대기 ⏳]
                    NextActivity 시작 [━━━━━━]

만약 대기 시간 안에 다음 화면이 시작되면?
"아, 시간이 중첩되는구나!"
→ 같은 Screen Group으로 묶음 ✅

설정 방법:

// Application 클래스
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        WhatapAgent.Builder.newBuilder()
            .setServerUrl("<https://api.whatap.io/mobile>")
            .setPCode(YOUR_PROJECT_CODE)
            .setProjectKey("YOUR_PROJECT_KEY")
            .setScreenGroupDelay(2000)  // 2초 대기
            .build(this)
    }
}

이게 전부입니다! 이후 모든 화면 전환이 자동으로 처리됩니다.

지연 시간 권장값:

📱 일반 앱: 2000ms (2초)
🏪 E-commerce 앱: 3000ms (3초)
🎮 게임 앱: 1000ms (1초)
🏦 금융 앱: 3000-5000ms (3-5초)

장단점:

장점:

  1. 완전 자동: 코드 수정 불필요
  2. 간단한 설정: 한 줄이면 끝
  3. 광범위 적용: 모든 화면 전환에 자동 적용
  4. Race Condition 해결: 타이밍 차이로 분리되는 문제 방지

단점:

  1. 타이밍 의존: 설정한 시간 내에 화면이 시작되어야 함
  2. 불필요한 대기: 마지막 화면 후에도 N초 기다림
  3. 불명확한 그룹핑: 어떤 화면들이 묶일지 예측 어려움

전략 2: ChainView - 수동으로 묶기

핵심 아이디어:

"시간 간격이 얼마나 있든, 이 화면들은 무조건 하나의 플로우야!"

시간축 ━━━━━━━━━━━━━━━━━━━━━━━━━━→

ChainView.startChain("app-launch")  ← 체인 시작
SplashActivity [━━━━━━]
(2초 경과) ← 권한 체크 중...
PermissionActivity [━━━━━━]
(5초 경과) ← 사용자가 권한 승인 버튼 누름
MainActivity [━━━━━━]
HomeFragment [━━━━━━]
ChainView.endChain()  ← 체인 종료

→ 모두 하나의 Screen Group으로 측정 ✅
→ 타이머 방식으로는 불가능 (5초 간격이 있음)

왜 필요한가?

Delayed Close로 해결 안 되는 경우:

[예시 1: 첫 실행 시 앱 시작]

SplashActivity 표시
권한 체크 중... (3초)
초기 데이터 동기화... (5초)
MainActivity 표시

→ 총 8초 간격!
→ Delayed Close (3초)로는 분리됨 ❌
→ 하지만 사용자는 계속 "앱이 시작되는 중"으로 인식 ✅
→ ChainView로 강제로 묶어야 함 ✅


[예시 2: WebView 페이지 로드]

Fragment 생성 (50ms)
WebView 초기화 (100ms)
페이지 요청 중... (5초) ← 네트워크 느림
페이지 렌더링 완료

→ 총 5초 이상 소요
→ Fragment는 150ms만에 "완료"로 기록됨 ❌
→ 하지만 사용자는 페이지 로드까지 기다림 ✅
→ ChainView로 전체를 묶어야 정확 ✅

사용 방법:

예시 1: 첫 실행 시 앱 시작 (권한 + 동기화)

// SplashActivity - 앱 시작 체인 시작
class SplashActivity : AppCompatActivity() {
    private val chainId = "app-launch-${System.currentTimeMillis()}"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 🔗 앱 시작 체인 시작
        ChainView.getInstance().startChain("app-launch", chainId)

        // 권한 체크
        checkPermissions { granted ->
            if (granted) {
                // 초기 데이터 동기화
                syncInitialData {
                    // MainActivity로 이동
                    startActivity(Intent(this, MainActivity::class.java).apply {
                        putExtra("chain_id", chainId)
                    })
                    finish()
                }
            }
        }
    }
}

// MainActivity - 앱 시작 체인 종료
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val chainId = intent.getStringExtra("chain_id")

        // HomeFragment 로드 완료 후 체인 종료
        supportFragmentManager.commit {
            replace(R.id.container, HomeFragment())
        }

        // Fragment 로드 완료 후
        lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                // 🔚 앱 시작 체인 종료
                chainId?.let { ChainView.getInstance().endChain(it) }
            }
        })

        // 결과: Splash → 권한 → 동기화 → Main → Fragment
        //       전체가 하나의 "앱 시작" Screen Group으로 측정됨!
    }
}

예시 2: WebView 페이지 로드

class WebViewFragment : Fragment() {
    private val chainId = "webview-${System.currentTimeMillis()}"

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 🔗 WebView 로드 체인 시작
        ChainView.getInstance().startChain("webview-load", chainId)

        webView.webViewClient = object : WebViewClient() {
            override fun onPageFinished(view: WebView?, url: String?) {
                // 🔚 페이지 로드 완료 시 체인 종료
                ChainView.getInstance().endChain(chainId)

                // 결과: Fragment 생성 → WebView 초기화 → 페이지 로드
                //       전체가 하나의 Screen Group으로 측정됨!
            }
        }

        webView.loadUrl("<https://example.com>")
    }
}

장단점:

장점:

  1. 100% 정확: 원하는 화면들만 정확히 묶음
  2. 시간 무관: 몇 초 간격이 있어도 상관없음
  3. 명시적: 코드만 봐도 어떤 플로우인지 명확
  4. 비즈니스 플로우 추적: 회원가입, 결제 등 중요 프로세스 측정

단점:

  1. 수동 작업: 개발자가 직접 코드 추가 필요
  2. chainId 관리: Intent로 전달하거나 전역 관리 필요
  3. 누락 위험: endChain() 깜빡하면 Screen Group이 안 닫힘
  4. 보일러플레이트: 매 Activity마다 코드 추가

Delayed Close vs ChainView 비교

어떤 방법을 선택할까?

📊 의사결정 흐름도

시작
"이 플로우가 중요한 비즈니스 프로세스인가?"
 ├─ Yes → ChainView 사용
 │         (예: 회원가입, 결제, 인증)
 └─ No → "화면 전환이 빠른가? (2-3초 이내)"
          ├─ Yes → Delayed Close로 충분
          │         (예: 일반 화면 전환)
          └─ No → ChainView 고려
                   (예: 사용자가 읽는 시간이 긴 화면)

실전 권장 전략: 혼합 사용

최고의 전략은 두 방법을 함께 사용하는 것입니다!

// Application 클래스
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // 1. 기본 안전망: Delayed Close
        WhatapAgent.Builder.newBuilder()
            .setScreenGroupDelay(2000)  // 2초 자동 대기
            .build(this)

        // 이제 일반적인 화면 전환은 모두 자동으로 처리됨
    }
}

// 2. 중요 플로우만 수동 지정: ChainView
class CheckoutActivity : AppCompatActivity() {
    fun startCheckout() {
        // 결제는 중요하니까 수동으로 보장
        ChainView.getInstance().startChain("checkout", chainId)
    }
}

class SignupActivity : AppCompatActivity() {
    fun startSignup() {
        // 회원가입도 중요하니까 수동으로 보장
        ChainView.getInstance().startChain("signup", chainId)
    }
}

효과:

📊 측정 커버리지

Delayed Close (자동):
├─ 홈 화면 전환: ✅ 자동 측정
├─ 설정 화면 이동: ✅ 자동 측정
├─ 상품 목록 탐색: ✅ 자동 측정
└─ 기타 모든 일반 화면: ✅ 자동 측정

ChainView (수동):
├─ 회원가입 플로우: ✅ 100% 정확 측정
├─ 결제 플로우: ✅ 100% 정확 측정
└─ 인증 플로우: ✅ 100% 정확 측정

→ 일반 화면 + 중요 플로우 모두 커버! 🎉

5. 5분 만에 Screen Group 시작하기

"이론은 충분합니다. 이제 바로 시작해봅시다!"

Screen Group을 시작하는 데 필요한 시간: 5분

1단계: Gradle 설정 (2분)
2단계: Application 초기화 (2분)
3단계: 확인 및 테스트 (1분)

1단계: Gradle 설정 (2분)

1-1. 프로젝트 레벨 build.gradle

// build.gradle (Project level)
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:8.1.0'
        classpath 'io.whatap:whatap-android-plugin:2.1.0'  // Whatap Plugin
    }
}

1-2. 앱 레벨 build.gradle

// build.gradle (Module: app)
plugins {
    id 'com.android.application'
    id 'io.whatap.android.plugin'  // Whatap Plugin 적용
}

dependencies {
    // Whatap Android Agent (BOM 버전 - Screen Group 포함)
    implementation 'io.whatap.android:whatap-android-agent:2.1.0'
}

1-3. Sync Now!

Android Studio 상단의 "Sync Now" 클릭하여 동기화

2단계: Application 초기화 (2분)

2-1. Application 클래스 생성 (없는 경우)

// MyApplication.kt
package com.example.myapp

import android.app.Application
import io.whatap.android.agent.WhatapAgent

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // Whatap 초기화
        WhatapAgent.Builder.newBuilder()
            .setServerUrl("YOUR_SERVER_URL")       // Whatap에서 발급받은 서버 URL
            .setPCode(YOUR_PROJECT_CODE)           // Whatap에서 발급받은 프로젝트 코드
            .setProjectKey("YOUR_PROJECT_KEY")     // Whatap에서 발급받은 프로젝트 키
            .setScreenGroupDelay(2000)             // 2초 Delayed Close
            .build(this)
    }
}

Java 버전:

// MyApplication.java
package com.example.myapp;

import android.app.Application;
import io.whatap.android.agent.WhatapAgent;

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        // Whatap 초기화
        new WhatapAgent.Builder()
            .setServerUrl("YOUR_SERVER_URL")
            .setPCode(YOUR_PROJECT_CODE)
            .setProjectKey("YOUR_PROJECT_KEY")
            .setScreenGroupDelay(2000)
            .build(this);
    }
}

2-2. AndroidManifest.xml 수정

<!-- AndroidManifest.xml -->
<manifest xmlns:android="<http://schemas.android.com/apk/res/android>"
    package="com.example.myapp">

    <!-- 인터넷 권한 추가 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:name=".MyApplication"  <!-- Application 클래스 지정 -->
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">

        <!-- Activities... -->

    </application>
</manifest>

2-3. 프로젝트 정보 발급받기

WhaTap 프로젝트 정보가 없다면:

  1. WhaTap 홈페이지 접속
  2. 회원가입 / 로그인
  3. 모바일 프로젝트 생성
  4. 발급받은 정보 확인:
    • 서버 URL
    • 프로젝트 코드(pcode)
    • 프로젝트 키 (문자열)
  5. 위 코드에 입력
.setServerUrl("<https://api.whatap.io/mobile>")  // 발급받은 서버 URL
.setPCode(12345)                               // 숫자로 입력
.setProjectKey("ABCD1234EFGH5678")             // 문자열로 입력

3단계: 확인 및 테스트 (1분)

3-1. 앱 실행

# 앱 빌드 및 실행
./gradlew clean assembleDebug
adb install app/build/outputs/apk/debug/app-debug.apk

또는 Android Studio에서 Run ▶️ 클릭

3-2. Logcat 확인

Android Studio Logcat에서 Whatap 초기화 확인:

I/Whatap: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
I/Whatap: Whatap Android Agent initialized
I/Whatap: Project Code: 12345
I/Whatap: Screen Group: ENABLED
I/Whatap: Screen Group Delay: 2000ms
I/Whatap: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

I/ScreenGroupManager: ✅ ScreenGroup initialized
I/ScreenGroupManager: 🔗 Auto tracking enabled

3-3. 화면 전환 테스트

앱에서 여러 화면을 전환하면서 Logcat 확인:

I/ScreenGroupManager: 🎯 ScreenGroup created: MainActivity
I/ScreenGroupManager: ✅ Task started: MainActivity (Active tasks: 1)
I/ScreenGroupManager: 📊 Task started: HomeFragment (Active tasks: 2)
I/ScreenGroupManager: ✅ Task ended: MainActivity (Active tasks: 1)
I/ScreenGroupManager: ⏳ ScreenGroup close scheduled in 2000ms
I/ScreenGroupManager: ⏹️ Scheduled close cancelled - new task added
I/ScreenGroupManager: 📊 Task started: DetailActivity (Active tasks: 2)

3-4. Whatap 대시보드 확인

  1. WhaTap 콘솔 로그인
  2. 모바일 프로젝트 선택
  3. Screen Group 메뉴 클릭
  4. 방금 테스트한 화면 전환 데이터 확인!
📊 Screen Group 목록

Screen Group: MainActivity → HomeFragment → DetailActivity
총 로딩 시간: 1850ms
발생 시간: 2025-10-01 14:23:45
디바이스: Samsung SM-A356N

🎉 완료! 이제 측정 중입니다!

축하합니다! 이제 모든 화면 전환이 자동으로 측정됩니다.

✅ Delayed Close로 자동 측정 중
✅ 화면 전환 시간 자동 기록
✅ Screen Group 자동 생성
✅ Whatap 대시보드에서 실시간 확인 가능

다음 단계

WebView를 사용하나요? WebView를 사용하는 앱이라면 다음 섹션에서 WebView 성능 측정을 확인하세요!

일반 Fragment: 50ms (빠름 ✅)
WebView Fragment: 3.5초 (느림! ⚠️)

→ WebView 페이지 로딩까지 포함한 정확한 측정이 필요합니다

WebView는 일반 화면과 다르게 측정되며, Browser AgentBridge를 통해 Native와 Web 성능을 하나의 Screen Group으로 통합 측정할 수 있습니다.

6. WebView + Browser Agent - 완전한 성능 측정

"Native와 Web을 하나의 Screen Group으로 측정합니다"

WebView, 왜 특별한가?

많은 앱들이 WebView를 사용합니다:

  • 뉴스/블로그 앱의 기사 본문
  • E-commerce 앱의 상품 상세 페이지
  • 공지사항, 이용약관, FAQ
  • 하이브리드 앱의 웹 컨텐츠

문제는:

개발자: "WebViewFragment onCreate는 50ms예요. 빠르죠?"
사용자: "페이지 보는데 3초 넘게 걸렸어요..."

→ 웹페이지 내부의 성능은 어떻게 측정하나요?

WhatTap의 답:

✅ Native: Android Agent가 측정
✅ Web: Browser Agent가 측정
✅ Bridge: 두 데이터를 하나의 Screen Group으로 통합!

Native + Web 통합 측정의 핵심

문제: 두 세계의 분리

[Native 영역]                    [Web 영역]
Android Agent가 측정 ✅           ??? 측정 불가 ❌

WebViewFragment
├─ onCreate: 30ms
├─ onViewCreated: 20ms
└─ loadUrl() 호출
    └─→ [WebView 내부]
         ├─ HTML 파싱: ???
         ├─ CSS 적용: ???
         ├─ JavaScript 실행: ???
         ├─ 이미지 로딩: ???
         └─ 렌더링 완료: ???

→ Fragment는 50ms (빠름)
→ 웹페이지는 ??? (모름)
→ 사용자는 3초 기다림 (느림!)

해결: WhaTap Browser Agent + Bridge

[Native 영역]                    [Web 영역]
Android Agent ✅                  Browser Agent ✅
WebViewFragment                  <script>
├─ onCreate: 30ms                whatap.browser.js
├─ onViewCreated: 20ms           </script>
├─ Bridge 설정                   ↓
└─ loadUrl()                     웹페이지 성능 측정:
    │                            ├─ HTML 파싱: 300ms
    │                            ├─ CSS 적용: 200ms
    │    Bridge 통신 ←→          ├─ JavaScript: 500ms
    │                            ├─ 이미지: 1200ms
    │                            └─ 렌더링: 400ms
    └─→ Screen Group 통합!
        총 2650ms = Native(50ms) + Web(2600ms)

핵심:

1. 웹페이지에 Browser Agent 스크립트 추가
2. Native에서 WebView Bridge 설정
3. Browser Agent 데이터가 Android Agent로 전달
4. 하나의 Screen Group으로 통합 측정!

🛠️ 실전 구현: 3단계 설정

1단계: 웹페이지에 Browser Agent 추가

웹페이지(HTML)에 Whatap Browser Agent 스크립트를 추가합니다:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Whatap WebView Test</title>

    <!-- Whatap Browser Agent -->
    <script>
    (function (w, h, _a, t, a, b) {
        w = w[a] = w[a] || {
            config: {
                projectAccessKey: "YOUR_BROWSER_PROJECT_KEY",
                pcode: YOUR_BROWSER_PROJECT_CODE,
                sampleRate: 100,
                proxyBaseUrl: "<https://rumote.whatap-mobile-agent.io/>",
                agentError: true,
                isWebView: true,      // WebView 환경 필수 설정
                xhrTracing: true
            },
        };
        a = h.createElement(_a);
        a.async = 1;
        a.src = 'whatap-browser-agent.js';  // 또는 CDN URL
        t = h.getElementsByTagName(_a)[0];
        t.parentNode.insertBefore(a, t);
    })(window, document, 'script', 'whatap-browser-agent.js', 'WhatapBrowserAgent', '');
    </script>

    <style>
        body { font-family: sans-serif; padding: 2rem; }
        img { max-width: 100%; margin-top: 2rem; }
    </style>
</head>
<body>
    <h1>🧪 WebView + Browser Agent Test</h1>
    <p>This page loads resources and sends events to Whatap.</p>

    <button onclick="trackClick()">Track Button Click</button>
    <button onclick="sendXhr()">Send XHR</button>
    <button onclick="sendFetch()">Send Fetch</button>

    <script>
        function trackClick() {
            console.log("Button clicked!");
        }

        function sendXhr() {
            const xhr = new XMLHttpRequest();
            xhr.open("GET", "<https://jsonplaceholder.typicode.com/todos/1>");
            xhr.onload = function () {
                console.log("XHR 완료:", xhr.responseText);
            };
            xhr.send();
        }

        function sendFetch() {
            fetch("<https://jsonplaceholder.typicode.com/todos/2>")
                .then(res => res.json())
                .then(json => console.log("Fetch 완료:", json));
        }

        // 페이지 로드 중 자동 XHR 발생
        (function() {
            const xhr = new XMLHttpRequest();
            xhr.open("GET", "<https://jsonplaceholder.typicode.com/todos/1>");
            xhr.onload = function () {
                console.log("초기 XHR 완료:", xhr.responseText);
            };
            xhr.send();
        })();
    </script>

    <!-- Heavy image resource -->
    <img src="<https://picsum.photos/800/400>" alt="Sample Image" />
</body>
</html>

2단계: Native에서 WebView Bridge 설정

MainActivity에서 Fragment를 띄우고, Fragment 내부에 WebView를 설정합니다:

// MainActivity.kt
class MainActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Fragment 추가
        val fragment = WebViewFragment()
        supportFragmentManager.beginTransaction()
            .replace(android.R.id.content, fragment)
            .commit()
    }
}

// WebViewFragment.kt
class WebViewFragment : Fragment() {
    private lateinit var webView: WebView

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return inflater.inflate(R.layout.fragment_webview, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        webView = view.findViewById(R.id.webView)

        // 🔗 Whatap Bridge 설정 (핵심!)
        val bridge = WhatapWebviewBridge(requireContext())
        bridge.configureWebView(webView)
        bridge.startDataUploadTimer()

        // WebView 기본 설정
        webView.settings.apply {
            javaScriptEnabled = true  // Browser Agent에 필수
            domStorageEnabled = true
            cacheMode = WebSettings.LOAD_DEFAULT
        }

        // WebViewClient 설정
        webView.webViewClient = WhatapWebViewClient(bridge, object : WebViewClient() {
            override fun onPageFinished(view: WebView?, url: String?) {
                super.onPageFinished(view, url)
                Log.d("WebView", "페이지 로드 완료: $url")
            }
        })

        // 페이지 로드
        // 개발 환경: localhost:18000 (PC에서 웹서버 실행)
        webView.loadUrl("<http://10.0.2.2:18000/>")

        // 또는 실제 서비스 URL
        // webView.loadUrl("<https://yourdomain.com>")
    }
}

3단계: 측정 결과 확인

이제 Native와 Web 성능이 하나의 Screen Group으로 측정됩니다!

📊 Whatap Screen Group 분석 결과

WebViewFragment 로딩
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
총 로딩 시간: 2850ms

[Native 영역] - Android Agent 측정
├─ [0-30ms] Fragment onCreate (1%)
├─ [30-50ms] Fragment onViewCreated (1%)
└─ [50-150ms] WebView 초기화 (3%)

[Web 영역] - Browser Agent 측정 (Bridge 전달)
├─ [150-450ms] HTML 다운로드 및 파싱 (11%)
├─ [450-750ms] CSS 적용 (11%)
├─ [750-1250ms] JavaScript 실행 (18%)
├─ [1250-2450ms] 이미지 로딩 (42%) ← 병목!
└─ [2450-2850ms] 렌더링 완료 (14%)

[2850ms] 전체 로딩 완료

💡 통합 인사이트:
✅ Native Fragment: 150ms (5%)
✅ Web 페이지: 2700ms (95%)
✅ 실제 병목: 이미지 로딩 (42%)
→ 웹 이미지 최적화가 핵심!

핵심:

  • Android Agent: Native Fragment 생명주기 측정
  • Browser Agent: 웹페이지 내부 성능 측정
  • Bridge: 두 데이터를 Screen Group으로 통합
  • 결과: 완전한 사용자 경험 측정!

실제 화면: Screen Group에서 WebView 확인하기

WhaTap 콘솔에서 Screen Group을 확인하면 Native와 Web이 하나로 통합된 것을 볼 수 있습니다:

화면 설명:

  • 상단: TestFragmenthttp://192.168.1.31:18000/ 두 컴포넌트가 하나의 Screen Group으로 묶임
  • 타임라인: Fragment 생성부터 WebView 로딩까지 전체 흐름 시각화
  • 하단: 각 단계별 소요 시간과 이벤트 표시
  • 💡 핵심: Fragment(Native)와 WebView(Web)가 별도 span이 아닌, 하나의 사용자 경험으로 측정됨!

WebView 상세 분석

Screen Group에서 WebView 컴포넌트를 클릭하면 Browser Agent가 측정한 웹 성능을 상세히 볼 수 있습니다:

상세 분석 화면에서 확인 가능한 정보:

  • Page Load Timing: DOM 파싱, 렌더링, 완료 시점
  • Resource Loading: 각 리소스(이미지, CSS, JS)별 로딩 시간
  • JavaScript 성능: 실행 시간, 메모리 사용량
  • XHR/Fetch 요청: 웹페이지 내부 API 호출 추적
  • 사용자 이벤트: 버튼 클릭, 스크롤 등 인터랙션

이것이 가능한 이유:

Browser Agent (Web)         Bridge         Android Agent (Native)
       ↓                      ↓                      ↓
성능 데이터 측정  →  JavaScript Interface  →  Screen Group 통합

7. 마무리 - Screen Group으로 완전한 성능 측정

"이제 사용자가 보는 것을 개발자도 볼 수 있습니다"

🎯 우리가 달성한 것

Before: 파편화된 측정

Activity A: 100ms  ✅
Fragment B: 50ms   ✅
Activity C: 80ms   ✅

→ 그래서 사용자 경험은? ❓
→ 전체 플로우는 얼마나 걸렸나? ❓
→ 병목은 어디인가? ❓

After: 통합 측정

Screen Group: 사용자 플로우 전체
├─ Activity A: 100ms (43%)
├─ Fragment B: 50ms (22%)
└─ Activity C: 80ms (35%)

총 230ms
→ 사용자가 느끼는 시간: 230ms ✅
→ 병목: Activity A (43%) ✅
→ 최적화 우선순위: 명확! ✅

📊 Screen Group의 핵심 가치

1. 사용자 관점의 측정

// 개발자가 보는 것
Activity onCreate: 50ms
Fragment onViewCreated: 30ms
Network request: 200ms

// 사용자가 느끼는 것
"상품 상세 페이지 보는데 280ms 걸렸네"

→ Screen Group = 사용자 관점!

2. Native + Web 통합

Native Fragment: 50ms
+ Web 페이지 로딩: 2500ms
─────────────────────────
= 사용자 대기 시간: 2550ms

→ WebView도 Screen Group으로 측정!

3. 자동 + 수동의 균형

// 대부분: 자동으로 측정
MainActivity → ProductFragment → CartActivity
→ Screen Group 자동 생성 ✅

// 필요시: 수동으로 세밀하게 제어
WhatapScreenGroup.startTask("checkout_flow")
// ... 복잡한 비즈니스 로직
WhatapScreenGroup.endTask("checkout_flow")
→ 정확한 측정 ✅

💡 실전 활용 팁

Tip 1: 핵심 플로우부터 시작

// ✅ 자동 측정에 적합 (Delayed Close로 충분)
- 앱 시작 → 메인 화면
- 화면 전환 (리스트 → 상세)
- 탭 전환

// 🔗 수동 측정 필요 (ChainView 권장)
- 로그인 플로우 (입력 대기 시간 있음)
- 결제 플로우 (여러 단계, 사용자 입력)
- 회원가입 (약관 동의, 정보 입력 등)

// ⏸️ 나중에 측정해도 되는 것
- 설정 화면
- 도움말 페이지
- 기타 부가 기능

Tip 2: Screen Group 지연 시간 조정

WhatapAgent.build(this)
    .setScreenGroupCloseDelayMs(5000)  // 기본: 5초
    .start()

// 빠른 플로우: 3초
// 느린 플로우: 10초
// WebView 포함: 7-10초

왜 중요한가?

  • 너무 짧으면: 플로우가 여러 개로 쪼개짐
  • 너무 길면: 불필요한 화면까지 포함됨

Tip 3: 의미 있는 이름 사용 (수동 측정)

// ❌ 나쁜 예
WhatapScreenGroup.startTask("task1")
WhatapScreenGroup.startTask("process")

// ✅ 좋은 예
WhatapScreenGroup.startTask("user_registration_form")
WhatapScreenGroup.startTask("payment_3ds_verification")
WhatapScreenGroup.startTask("image_upload_to_s3")

Tip 4: Extra 데이터로 컨텍스트 추가

// 사용자 정보 추가
WhatapExtra.getMap().put("user_id", userId)
WhatapExtra.getMap().put("user_level", userLevel)

// 비즈니스 컨텍스트 추가
WhatapExtra.getMap().put("order_amount", totalAmount)
WhatapExtra.getMap().put("product_category", category)
WhatapExtra.getMap().put("payment_method", paymentMethod)

// 결과: 성능 데이터와 비즈니스 데이터를 함께 분석!

Screen Group이 제공하는 가치:

사용자 관점: 개별 화면이 아닌, 전체 플로우 측정
자동화: 대부분 추가 코드 없이 자동 측정
통합: Native + Web 완전 통합 측정
실용성: 실제 병목 지점 즉시 파악
확장성: 간단한 설정부터 고급 커스터마이징까지

이제 여러분의 앱에서:

사용자가 보는 것 = 개발자가 측정하는 것

Happy Monitoring! 여러분의 앱이 더 빨라지길 바랍니다! 🚀

와탭 모니터링을 무료로 체험해보세요!