onPause: Android에서 Activity 상태 저장이란

저자: IT Sectr 게시일: 2026-03-04 읽는 시간: 10 분

onPause는 Android 수명 주기 메서드로, Activity가 입력 포커스를 잃었지만 화면에 부분적으로 계속 표시될 때 호출됩니다. 시스템은 새 Activity가 포그라운드로 나오기 전, 대화상자가 열릴 때, 최근 앱 버튼을 누를 때, 또는 수신 전화가 올 때 onPause를 호출합니다. 이 메서드는 사용자 데이터를 저장하는 마지막 보장된 지점입니다. onStop과 onDestroy 이후 시스템은 추가 호출 없이 프로세스를 종료할 수 있기 때문입니다. onPause 내에서 개발자는 초안을 저장하고, 애니메이션을 일시 중지하고, 카메라를 해제하고, 현재 UI 상태를 SharedPreferences에 기록합니다. 전체 Activity 수명 주기에 대한 자세한 내용은 Activity Lifecycle 문서를 참조하세요.

핵심 사항

  • onPause — Activity가 포커스를 잃지만 계속 표시됨; 데이터 저장의 마지막 보장된 지점
  • 상태 저장 — onPause에서 중요한 사용자 데이터 저장: 초안, 양식 텍스트, 진행 상황
  • 리소스 해제 — 카메라, 마이크, 비디오 플레이어를 onPause에서 해제하여 다른 앱에 전달
  • 시간 제한 — onPause는 100ms 내에 완료되어야 함; 초과 시 ANR 발생 및 전환 지연
  • SharedPreferences.apply() — onPause에서 비동기 쓰기; commit()은 스레드를 차단하고 ANR 유발 가능
  • onPause vs onStop — onPause는 부분 표시 시(대화상자), onStop은 완전 숨김 시(다른 Activity)
  • onSaveInstanceState — onPause 후 호출되어 임시 상태를 Bundle에 저장

Android에서 onPause란

onPause는 Activity 수명 주기의 네 번째 메서드로, 화면이 입력 포커스를 잃었지만 사용자에게 부분적으로 계속 표시될 때 호출됩니다. 앱이 활발히 실행되는 상태와 숨겨지는 상태 사이의 '전환' 상태입니다. 시스템은 다음 시나리오에서 onPause를 호출합니다: 다른 Activity 열기(새 화면이 현재 화면을 부분적으로 가림), 대화상자 창 표시(Dialog, PopupWindow, Snackbar는 onPause를 트리거하지 않지만 DialogFragment는 트리거함), 최근 앱 버튼 누르기, 수신 전화, 화면 잠금을 위한 전원 버튼 누르기.

onPause의 주요 작업은 앱이 숨겨지거나 종료될 가능성에 대비하는 것입니다. 이것은 시스템이 다른 구성 요소로의 전환을 진행하기 전에 개발자가 자신의 코드가 실행될 것이라고 확신할 수 있는 수명 주기의 마지막 지점입니다. onPause 후 시스템은 onStop을 호출하고(Activity가 완전히 숨겨진 경우), 이후 프로세스는 추가 알림 없이 언제든지 종료될 수 있습니다.

Android Developers 문서(2025)에 따르면 onPause는 가능한 한 가볍고 빨라야 합니다. onPause가 제어권을 반환할 때까지 시스템은 다음 Activity를 시작할 수 없습니다 — 즉 사용자는 화면 전환 지연을 경험하게 됩니다. Google은 100밀리초 미만으로 onPause를 완료할 것을 권장하며, 모든 장기 작업(데이터베이스 저장, 디스크 쓰기)은 코루틴 또는 apply()를 통해 비동기적으로 수행해야 합니다.

Activity의 onPause

Activity에서 onPause 메서드는 화면이 더 이상 활성화되지 않지만 부분적으로 계속 표시될 수 있을 때마다 호출됩니다. 일반적인 예: 사용자가 지도 앱을 열고 위치 공유를 탭하면 지도 위에 시스템 앱 선택 대화상자가 나타납니다. 지도 Activity는 onPause를 받지만 대화상자 아래에 계속 표시됩니다. 대화상자가 닫히면 지도는 onStart 호출 없이 onResume을 받습니다(화면이 완전히 숨겨지지 않았음).

kotlin
class NoteEditorActivity : AppCompatActivity() {
    private var binding: ActivityNoteEditorBinding? = null
    private val prefs by lazy {
        getSharedPreferences("note_drafts", Context.MODE_PRIVATE)
    }

    override fun onPause() {
        super.onPause()

        // 메모 초안 저장 — 비동기
        prefs.edit()
            .putString("draft_title", binding?.titleInput?.text.toString())
            .putString("draft_body", binding?.bodyInput?.text.toString())
            .putLong("draft_timestamp", System.currentTimeMillis())
            .apply()

        // 비디오 일시 중지
        binding?.videoPlayer?.pause()

        // 독점 리소스 해제
        releaseCamera()
        releaseAudioFocus()
    }

    override fun onResume() {
        super.onResume()
        // 초안 복원
        binding?.titleInput?.setText(prefs.getString("draft_title", ""))
        binding?.bodyInput?.setText(prefs.getString("draft_body", ""))
        acquireCamera()
        acquireAudioFocus()
    }
}

NoteEditorActivity 예제는 onPause의 올바른 처리를 보여줍니다: apply()를 통한 SharedPreferences에 초안 저장, 비디오 파일 일시 중지, 카메라 및 오디오 포커스 해제. 각 호출은 가볍고 빠르며 ANR을 유발할 만큼 UI 스레드를 차단하지 않습니다. 순서에 주목하세요: super.onPause()가 첫 번째 줄에서 호출됩니다 — 이는 사용자 코드에서 예외가 발생해도 시스템 로직이 실행되도록 보장합니다.

onPause에서 상태 저장

onPause는 앱이 숨겨지거나 시스템에 의해 종료되기 전에 개발자가 사용자 데이터를 안정적으로 저장할 수 있는 마지막 지점입니다. onStop 후 시스템은 메모리 부족 시 onDestroy 호출 없이 프로세스를 종료할 수 있습니다. onSaveInstanceState() 메서드는 onPause 후 호출되지만 해당 Bundle은 장기 저장을 위한 것이 아닙니다 — 다음 onCreate까지만 유지됩니다.

apply()를 사용한 SharedPreferences

비동기 apply()를 사용한 SharedPreferences는 onPause에서 소량의 데이터를 저장하는 최적의 방법입니다. 동기적으로 데이터를 디스크에 쓰고 부울을 반환하는 commit()과 달리 apply()는 즉시 메모리에 데이터를 저장하고 비동기 디스크 쓰기를 예약합니다. 이는 commit()의 10~100밀리초에 비해 UI 스레드에서 1밀리초 미만이 소요됩니다.

kotlin
override fun onPause() {
    super.onPause()

    // ❌ 나쁨: 동기 쓰기가 스레드를 차단함
    // prefs.edit().putInt("score", score).commit()

    // ✅ 좋음: 비동기 쓰기
    prefs.edit().putInt("score", score).apply()

    // 복잡한 객체의 경우 — ViewModel에서 캐싱
    viewModel.saveState()
}

Room 및 코루틴

onPause에서 구조화된 데이터(Room을 통한 SQLite)의 경우 lifecycleScope와 함께 코루틴을 사용합니다. ViewModelScope는 ViewModel이 소멸될 때 자동으로 코루틴을 취소하여 닫힌 데이터베이스에 대한 쓰기를 방지합니다. 코루틴을 사용한 Room을 통한 쓰기는 5~15밀리초가 소요되며 UI 스레드를 차단하지 않습니다.

kotlin
// ViewModel에서:
fun saveDraft(title: String, body: String) {
    viewModelScope.launch(Dispatchers.IO) {
        noteDao.insert(NoteDraft(title = title, body = body))
    }
}

// Activity.onPause에서:
viewModel.saveDraft(
    binding?.titleInput?.text.toString(),
    binding?.bodyInput?.text.toString()
)

Fragment의 onPause

Fragment의 onPause는 Fragment가 더 이상 활성화되지 않지만 계속 표시될 수 있을 때 호출됩니다. 이는 다음과 같은 경우에 발생합니다: FragmentTransaction을 통해 Fragment가 다른 Fragment로 교체될 때; Fragment가 ViewPager의 현재 페이지가 아닐 때; Fragment를 포함하는 Activity가 onPause를 받을 때. Activity onPause와 Fragment onPause 간의 상호 작용은 엄격히 계층적입니다: 먼저 Activity가 onPause를 받고, 그 다음 모든 Fragment가 받습니다.

kotlin
class MapFragment : Fragment() {
    private var mapController: MapController? = null

    override fun onPause() {
        super.onPause()
        mapController?.stopFollowMode()
        binding?.mapContainer?.alpha = 0.7f
    }

    override fun onResume() {
        super.onResume()
        binding?.mapContainer?.alpha = 1.0f
        if (isVisible) {
            mapController?.startFollowMode()
        }
    }
}

onPause에서 지도 작업의 특이 사항: Google Maps와 Yandex Maps는 활성 추적 모드에서 상당한 GPU 리소스를 소비합니다. 포커스를 잃으면 지도 애니메이션을 비활성화하고 마커 업데이트 빈도를 줄이는 것이 합리적이며, 포커스가 돌아오면 전체 기능을 복원해야 합니다. 이는 화면 간 전환 시 성능을 개선하고 전력 소비를 줄입니다.

onPause vs onStop: 차이점과 시나리오

초보 Android 개발자들 사이에서 가장 흔한 혼동 중 하나는 onPause와 onStop의 차이를 이해하지 못하는 것입니다. 각 시나리오를 살펴보고 올바른 메서드를 결정해 보겠습니다.

시나리오onPauseonStop
대화상자 창 열기호출됨호출되지 않음
새 Activity 열기(투명하지 않음)호출됨호출됨
홈 버튼 누르기호출됨호출됨
화면 잠금호출됨호출됨
수신 전화호출됨호출됨
투명 Activity가 위에 있음호출됨호출되지 않음
분할 화면(화면 절반)호출됨호출되지 않음
PiP(영상 속 영상)호출됨호출되지 않음

주요 규칙: onPause는 포커스를 잃을 때마다 호출되고, onStop은 가시성이 완전히 상실된 경우에만 호출됩니다. Activity가 계속 표시되면(부분적으로라도) onStop은 호출되지 않습니다. 이는 분할 화면, PiP 및 투명 Activity 모드에서 매우 중요합니다 — 여기서는 onPause/onResume은 작동하지만 onStart/onStop은 작동하지 않습니다.

onPause 타이밍 및 성능

onPause는 다음 Activity의 렌더링을 차단하기 때문에 가장 시간에 민감한 수명 주기 메서드입니다. 시스템은 새 Activity를 표시하기 전에 현재 Activity의 onPause가 완료될 때까지 기다립니다. onPause가 100밀리초를 초과하면 사용자가 전환 지연을 인지하고, 5초를 초과하면 시스템이 ANR을 표시합니다.

성능 권장 사항

Google Android Performance Guide(2025)는 onPause에 대해 다음 권장 사항을 제공합니다: 네트워크 요청을 수행하지 마세요 — 취소하거나 WorkManager로 이동해야 합니다; 디스크에 큰 파일을 쓰지 마세요 — 백그라운드 스레드에서 BufferedWriter를 사용하세요; 복잡한 SQL 쿼리를 실행하지 마세요 — Room 작업은 코루틴을 통해 비동기여야 합니다; 새 객체 생성을 피하세요 — onPause에서 가비지 수집이 지연을 악화시킵니다; SharedPreferences에는 commit() 대신 apply()를 사용하세요.

kotlin
override fun onPause() {
    super.onPause()

    // ❌ 나쁨: HTTP 요청이 UI를 차단함
    // val response = api.syncSave(data).execute()

    // ❌ 나쁨: 파일에 동기 쓰기
    // FileOutputStream(file).write(data)

    // ✅ 좋음: 비동기 저장
    lifecycleScope.launch {
        withContext(Dispatchers.IO) {
            api.saveData(data)
            fileDao.write(data)
        }
    }

    // ✅ 좋음: SharedPreferences에 가벼운 쓰기
    prefs.edit().putString("key", value).apply()
}

Android Studio Profiler(CPU 추적)를 통한 onPause 프로파일링은 정확한 실행 시간을 보여줍니다. onPause가 100ms를 초과하면 Profiler가 메서드를 노란색으로 강조하고, 500ms를 초과하면 빨간색으로 강조합니다. IT Sectr의 상용 프로젝트에서는 Macrobenchmark 테스트를 사용하여 Activity 간 전환 시간을 자동으로 확인하고 CI 파이프라인에서 성능 저하를 알립니다.

onPause의 일반적인 실수

숙련된 개발자도 onPause에서 실수를 합니다. 다섯 가지 일반적인 문제와 해결 방법을 살펴보겠습니다.

동기 데이터베이스 쓰기

onPause에서 동기 쿼리(.executeAsObservable(), 코루틴 없음)로 Room DAO를 호출하면 UI 스레드가 10~50ms 동안 차단됩니다. 동시에 GC나 쓰기 경합이 발생하면 지연이 200~500ms에 도달할 수 있습니다. 해결 방법: Dispatchers.IO와 함께 코루틴을 사용하거나 SharedPreferences에는 apply()를 사용하세요.

새 리스너 등록

onPause는 리스너를 등록하는 곳이 아닙니다. onPause에서 BroadcastReceiver를 등록하면 Activity가 더 이상 표시되지 않을 때도 활성 상태로 유지됩니다. 등록은 onStart/onResume에서만 수행하고, onPause/onStop에서는 등록 해지만 수행해야 합니다. 예외는 호출 전에 등록이 필요한 Intent 기반 API입니다.

예외 무시

onPause에서 처리되지 않은 예외가 발생하면 시스템이 onStop과 onDestroy를 호출하지 않습니다. Activity가 정의되지 않은 상태에서 멈추고, 돌아올 때 onResume이 해제된 리소스를 올바르게 복원하지 못할 수 있습니다. 해결 방법: Log.e()를 통한 로깅과 함께 중요한 작업을 try/catch로 감싸세요.

중복 데이터 저장

쉽게 복원할 수 있는 데이터를 onPause에서 저장할 필요는 없습니다. 예를 들어 API 요청 결과는 onPause가 아닌 검색 시점에 Room이나 DataStore에 캐시됩니다. 사용자가 수동으로 입력했고 자동으로 복원할 수 없는 것만 저장하세요 — 필드의 텍스트, 선택한 항목, 스크롤 위치 등입니다.

super.onPause() 누락

super.onPause()를 호출해야 하지만 onCreate와 달리 생략해도 즉시 충돌이 발생하지는 않습니다. 시스템은 onPause에서 super의 부재를 '용서'하지만 내부 상태 머신이 잘못된 상태가 됩니다. 다음 onResume 호출이 입력 포커스를 복원하지 못해 Activity가 '고정'된 상태로 남을 수 있습니다. 항상 가능한 한 빨리 super.onPause()를 호출하세요.

자주 묻는 질문

onPause에서 finish()를 호출하면 어떻게 되나요?

onPause에서 finish()를 호출하면 메서드에서 반환된 직후 Activity가 종료됩니다. 이는 포커스를 잃을 때 화면을 닫아야 하는 경우(예: 앱 최소화 시 인증 화면)의 유효한 시나리오입니다. 그러나 finish()는 전체 종료 주기(onStop onDestroy)를 트리거하여 전환에 지연을 추가합니다. onPause에서 finish()는 정말 필요한 경우에만 사용하세요.

onPause와 onSaveInstanceState의 차이점은 무엇인가요?

onPause는 프로세스 종료 후에도 유지되어야 하는 데이터를 저장하기 위한 것입니다(SharedPreferences/Room의 초안). onSaveInstanceState는 다음 onCreate까지만 필요한 임시 UI 상태를 저장하기 위한 것입니다(스크롤 위치, 선택한 탭). onSaveInstanceState의 Bundle은 앱이 완전히 종료될 때 보존되지 않습니다 — 메모리에만 존재합니다. onPause 데이터는 디스크에 저장되며 재부팅 후에도 유지됩니다.

onPause에서 대화상자를 열 수 있나요?

권장되지 않습니다. onPause에서 대화상자나 팝업을 열면 Activity가 이미 종료된 경우 WindowLeakException이 발생합니다. 포커스를 잃을 때 알림을 표시해야 하는 경우 NotificationManager(시스템 알림)를 사용하세요 — 안전하며 사용자가 예상하는 동작입니다. 지연된 작업에는 AlarmManager 또는 WorkManager를 사용하세요.

onPause는 보장된 저장 지점인데 onStop은 왜 그렇지 않나요?

onPause는 Activity가 더 이상 활성화되지 않기 전에 호출되는 것이 보장됩니다. 시스템이 메모리를 확보하기 위해 프로세스를 종료하면 onStop이 호출되지 않을 수 있습니다 — 이 경우 onDestroy도 호출되지 않습니다. onPause는 onResume 이후 포커스 손실 이유에 관계없이 항상 호출되는 유일한 메서드입니다. 따라서 모든 중요한 데이터는 정확히 onPause에서 저장됩니다.

단위 테스트에서 onPause를 테스트하려면 어떻게 해야 하나요?

onPause 테스트에는 Robolectric 또는 AndroidX Test의 FragmentScenario를 사용합니다. FragmentScenario.create() moveToState(State.STARTED) moveToState(State.RESUMED) moveToState(State.STARTED)가 순차적으로 onPause를 호출합니다. 그런 다음 데이터가 SharedPreferences에 저장되었는지 또는 모의 객체를 통해 카메라가 해제되었는지 확인합니다. Robolectric 4.12+는 실제 장치 없이 onPause/onResume 에뮬레이션을 지원합니다.

요약

  • onPause — Activity가 입력 포커스를 잃지만 부분적으로 계속 표시됨; 데이터 저장의 마지막 보장된 지점
  • 저장 — SharedPreferences.apply() 또는 코루틴을 통한 Room; commit() 및 동기 작업은 금지
  • 리소스 해제 — 카메라, 오디오 포커스, 비디오 플레이어가 onPause에서 해제되어 다른 앱에 전달됨
  • 100ms 제한 — onPause가 다음 Activity 렌더링을 차단; 제한 초과 시 ANR 발생
  • onPause vs onStop — onPause는 포커스 손실 시(가시성 유지), onStop은 완전 숨김 시
  • Fragment.onPause — Activity.onPause 후 계층적 호출; 지도 및 ViewPager 관련 특이 사항
  • 일반적인 실수 — 동기 쓰기, 리스너 등록, try/catch 무시, 중복 저장
  • super.onPause() — 가능한 한 빨리 호출; 생략해도 충돌하지 않지만 상태 머신이 손상됨

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기