앱 생명주기
고친 사람 github-actions[bot]
앱 생명주기는 앱을 언제 띄우고 멈추고 끝낼지 운영체제가 정하는 방식입니다. 앱은 이 결정을 내리지 못합니다. 운영체제가 상태를 바꿀 때마다 앱을 불러 알리면, 앱은 거기에 맞춰 할 일을 합니다. 모바일 앱이 화면 밖에서 소리 없이 사라졌다가 다시 뜨는 까닭이 이것입니다.
쉽고 빠른 이해
무슨 일을 하는 물건인가 — 운영체제가 앱의 상태를 바꾸고, 바꿀 때마다 앱을 불러 알립니다. 메신저를 보다가 홈 화면으로 나가면 메신저는 화면에서 내려가 곧 멈춥니다. 그 뒤 다른 앱을 많이 쓰면 운영체제가 메신저를 아예 끝냅니다.
왜 이렇게 하나 — 폰은 메모리와 전지가 작습니다. 안 보는 앱까지 계속 돌면 보는 앱이 느려집니다. 전지도 빨리 닳습니다.
어떻게 도나
- 화면 맨 앞에 있는 앱은 마음껏 일합니다
- 화면에서 내려간 앱은 운영체제에게 그 사실을 전해 듣고 곧 멈춥니다
- 메모리가 모자라면 운영체제가 멈춘 앱을 말없이 끝냅니다
- 사용자가 돌아오면 운영체제가 앱을 다시 띄웁니다
대가 — 앱은 자기가 언제 끝날지 모릅니다. 화면에서 내려가는 순간 남길 값을 저장해 둬야 합니다.
상세
이 절은 앱 하나가 켜져서 끝나기까지 거치는 상태를 서버 프로세스와 견주어 봅니다.
책상이 좁은 사람은 지금 읽는 책 한 권만 펼쳐 둡니다. 잠깐 덮은 책은 책상 귀퉁이에 쌓아 둡니다. 귀퉁이가 넘치면 가장 오래 안 본 책부터 책장에 꽂습니다. 그 책을 다시 읽으려면 책장에서 꺼내 읽던 쪽을 다시 찾아야 합니다.
이 장면에서 책상은 폰의 메모리이고, 펼친 책은 사용자가 보는 앱입니다. 귀퉁이에 쌓인 책은 화면에서 내려가 멈춘 앱입니다. 책장에 꽂는 일은 운영체제가 그 앱을 끝내는 일이고, 읽던 쪽을 다시 찾는 일은 앱을 처음부터 다시 띄워 보던 화면을 되살리는 일입니다.
서버 프로세스와 다른 점
프로세스는 운영체제가 실행 중인 프로그램 하나에 내주는 실행 단위입니다. 프로그램이 쓰는 메모리와 실행 흐름이 이 단위에 묶입니다. 백엔드 서버는 보통 프로세스 하나로 떠서 요청을 받습니다.
서버 프로세스는 띄운 사람이 끝낼 때까지 돕니다. 끝낼 때도 대개 종료 신호를 먼저 받습니다. 그 신호를 받고 하던 요청을 마무리한 뒤 끝내는 방식이 그레이스풀 셧다운입니다.
모바일 앱은 이 결정을 운영체제에 넘깁니다. 사용자가 다른 앱으로 넘어가도 앱 프로세스는 바로 끝나지 않습니다. 대신 나중에 운영체제가 아무 예고 없이 끝낼 수 있습니다.
운영체제가 결정을 쥐는 까닭은 폰의 사정에 있습니다. 폰은 메모리가 작고 전지로 돕니다. 사용자가 보는 앱은 늘 하나입니다. 나머지 앱이 쓰는 메모리와 전지는 그만큼 손해입니다.
앱이 거치는 상태
앱이 거치는 상태는 실행 안 됨 · 포그라운드 · 백그라운드 · 정지, 넷입니다. 그림의 화살표에는 상태를 옮기는 일을 적었습니다.
stateDiagram-v2
state "실행 안 됨" as N
state "포그라운드" as F
state "백그라운드" as B
state "정지" as S
[*] --> N
N --> F: 사용자가 연다
F --> B: 다른 앱으로 넘어간다
B --> F: 돌아온다
B --> S: 하던 일이 끝난다
S --> F: 돌아온다
S --> N: 메모리가 모자라다
포그라운드는 앱이 화면 맨 앞에 떠서 사용자의 터치를 받는 상태입니다. 사용자가 지금 쓰고 있는 앱이 이 상태에 있습니다.
사용자가 다른 앱으로 넘어가면 앱은 백그라운드로 내려갑니다. 화면에서는 사라졌지만 코드는 아직 잠깐 돕니다.
백그라운드에서 할 일이 끝나면 앱은 정지 상태가 됩니다. 프로세스와 메모리는 남아 있지만 코드는 한 줄도 돌지 않습니다. 사용자가 돌아오면 운영체제가 이 앱을 곧장 포그라운드로 올립니다.
정지한 앱이 끝나는 때는 운영체제가 정합니다. 메모리가 모자라면 운영체제는 정지한 앱의 프로세스를 끝내 메모리를 되찾습니다. 이때 운영체제는 앱을 부르지 않습니다. 앱은 아무것도 모른 채 실행 안 됨 상태로 돌아갑니다.
네 상태에서 무엇이 살아 있는지를 나란히 놓으면 이렇습니다.
| 상태 | 화면에 보이나 | 코드가 도나 | 메모리에 남았나 |
|---|---|---|---|
| 실행 안 됨 | ✗ | ✗ | ✗ |
| 포그라운드 | ✓ | ✓ | ✓ |
| 백그라운드 | ✗ | ✓ 잠깐 | ✓ |
| 정지 | ✗ | ✗ | ✓ |
두 운영체제가 쓰는 이름
iOS는 이 흐름을 다섯 상태로 부릅니다. 포그라운드를 둘로 나누기 때문입니다. 앱이 맨 앞에 있어도 전화가 오거나 알림 화면을 내리면 잠깐 터치를 못 받습니다. iOS 는 이 짧은 사이를 Inactive 라는 상태로 따로 셉니다.
| 앞 그림의 이름 | iOS 의 이름 |
|---|---|
| 실행 안 됨 | Not running |
| 포그라운드 · 터치를 받음 | Active |
| 포그라운드 · 잠깐 터치를 못 받음 | Inactive |
| 백그라운드 | Background |
| 정지 | Suspended |
Android는 앱 전체의 상태에 이름을 붙이기보다 프로세스의 중요도를 매깁니다. 사용자에게 얼마나 필요한 프로세스인지에 따라 등급을 나눕니다. 이 등급이 다음 절에서 보는 「끝내는 순서」가 됩니다.
운영체제가 끝낼 앱을 고르는 순서
메모리가 모자라면 운영체제는 사용자에게 덜 필요한 프로세스부터 끝냅니다. Android 는 이 순서를 네 등급으로 나눕니다.
등급 표에 포그라운드 서비스라는 이름이 나옵니다. 포그라운드 서비스는 알림을 띄워 둔 채 뒤에서 도는 작업이고, 음악 재생이 대표적입니다. 사용자가 알림으로 그 작업을 보고 있으니 운영체제가 쉽게 끝내지 않습니다. 이름에 포그라운드가 붙지만 맨 앞 화면을 뜻하는 「포그라운드 프로세스」 등급과는 다르고, 그 한 칸 아래 「보이는 프로세스」에 듭니다.
표는 중요한 등급을 위에 두었습니다.
| 등급 | 무엇이 든 프로세스 | 언제 끝내나 |
|---|---|---|
| 포그라운드 프로세스 | 사용자가 지금 쓰는 화면 | 마지막 수단으로만 |
| 보이는 프로세스 | 보이지만 맨 앞은 아닌 화면 · 포그라운드 서비스 | 포그라운드 프로세스를 살리는 데 꼭 필요할 때만 |
| 서비스 프로세스 | 화면 없이 뒤에서 도는 업로드 · 다운로드 | 캐시 프로세스가 다 끝난 뒤 |
| 캐시 프로세스 | 당장 필요 없는 프로세스 | 가장 먼저, 평소에도 수시로 |
표의 마지막 줄이 앞 그림의 「정지」에 가깝습니다. 평소의 메모리 관리는 대개 캐시 프로세스를 끝내는 데서 그칩니다. 서비스 프로세스까지 끝내는 일은 메모리가 아주 모자랄 때만 생깁니다.
리눅스 서버에서도 메모리가 바닥나면 커널이 프로세스 하나를 골라 끝냅니다. 서버에서 이 일은 장애입니다. 모바일에서는 앱 프로세스가 끝나는 것이 평소 동작입니다.
상태가 바뀔 때 앱이 할 일
상태가 바뀔 때마다 운영체제는 앱의 정해진 메서드를 부릅니다. 개발자가 직접 부르지 않고 운영체제가 불러 주는 이런 메서드를 콜백이라고 합니다. 앞에서 운영체제가 앱을 불러 알린다고 한 일이 바로 이 콜백 호출입니다.
iOS 에서 앱이 백그라운드로 내려갈 때 불리는 applicationDidEnterBackground(_:) 가 그런 콜백입니다. Android 는 화면이 가려질 때마다 onStop() 같은 콜백을 부릅니다. 앱은 콜백 안에 그때 할 일을 적어 둡니다.
잠깐 터치를 못 받는 순간은 백그라운드로 내려가기 직전에도 오고, 전화가 와서 앱을 가릴 때도 옵니다. 앞에서 본 iOS 의 Inactive 가 이 순간입니다. 앱은 아직 포그라운드에 있으므로 진행 중인 작업만 멈춥니다. 저장은 그다음, 실제로 백그라운드로 내려갈 때 합니다.
| 언제 | 할 일 |
|---|---|
| 포그라운드로 올라올 때 | 화면을 새로 고친다 · 멈췄던 작업을 다시 시작한다 |
| 잠깐 터치를 못 받을 때 (iOS 의 Inactive) | 재생이나 게임처럼 진행 중인 작업을 멈춘다 |
| 백그라운드로 내려갈 때 | 남길 값을 저장한다 · 카메라나 위치 센서를 놓는다 |
| 정지한 앱이 끝날 때 | 할 수 있는 일이 없다. 콜백이 불리지 않는다 |
정지한 앱을 끝낼 때 운영체제는 앱을 깨우지 않습니다. 그래서 저장은 백그라운드로 내려가는 콜백에서 해야 합니다.
메일을 반쯤 쓰다가 전화를 받는 장면을 떠올려 봅니다. 통화가 길어지는 동안 사용자가 지도와 사진 앱을 엽니다. 운영체제는 메모리를 찾다가 정지한 메일 앱을 끝냅니다. 메일 앱이 내려갈 때 쓰던 글을 저장하지 않았다면, 돌아온 사용자는 빈 화면을 봅니다.
다시 뜰 때의 두 갈래
사용자가 앱으로 돌아올 때 프로세스가 남아 있는지에 따라 길이 갈립니다. 남아 있으면 운영체제가 정지한 앱을 그대로 앞으로 올립니다. 이렇게 다시 뜨는 것이 웜 스타트입니다.
프로세스가 이미 끝났으면 처음부터 다시 띄워야 합니다. 이 경우가 콜드 스타트입니다. 콜드 스타트는 프로세스를 만들고 코드를 읽어 들여 첫 화면을 그리는 일까지 전부 해야 합니다. 그래서 웜 스타트보다 느립니다.
사용자 눈에는 둘이 같아 보여야 합니다. 그래서 앱은 콜드 스타트 때 저장해 둔 값을 읽어 사용자가 보던 화면을 되살립니다. 이 일이 상태 복원입니다. 앞 장면의 메일 앱이 쓰던 글을 저장해 두었다면, 콜드 스타트 뒤에도 그 글이 다시 뜹니다.
화면 하나의 생명주기와 앱 생명주기
모바일 앱 안의 화면 하나도 자기 생명주기를 가집니다. Android 의 액티비티가 그런 예입니다. 액티비티는 화면 한 장을 맡는 부품입니다. 이 부품도 만들어지고 보이고 가려지고 없어지는 상태를 따로 거칩니다.
앱 생명주기는 이보다 한 단계 위입니다. 앱 프로세스 전체가 살아 있는지, 맨 앞에 있는지를 다룹니다. 앱이 포그라운드에 있어도 그 안의 화면 하나는 다른 화면에 가려져 멈춰 있을 수 있습니다.
앱 생명주기가 화면의 생명주기를 감쌉니다. 앱 프로세스가 끝나면 그 안의 화면은 전부 함께 사라집니다. 화면 하나의 생명주기를 맡는 부품들은 앱 컴포넌트 항목에서 따로 봅니다.
서버 쪽에서 보이는 모습
앱 생명주기는 백엔드에도 흔적을 남깁니다. 정지한 앱은 코드가 돌지 않으므로 서버에 아무것도 보내지 못합니다. 앱이 서버와 계속 열어 두던 웹소켓 같은 연결은 이때 끊기기 쉽습니다.
사용자가 앱으로 돌아오면 앱은 끊긴 연결을 다시 맺습니다. 밀린 데이터도 한꺼번에 받아 갑니다. 그래서 서버는 앱의 연결이 끊겼다 이어지는 일을 평소 흐름으로 다뤄야 합니다.
앱이 화면 밖에 있는 동안 서버는 앱을 직접 부를 수 없습니다. 그래서 사용자에게 소식을 전할 때는 운영체제가 대신 받아 띄워 주는 푸시 알림을 씁니다.
화면 밖의 앱이 코드를 돌리는 일에도 한도가 있습니다. 운영체제는 앱이 백그라운드에서 일할 수 있는 시간과 일의 종류를 좁혀 둡니다. 전지를 아끼려는 이 규칙을 백그라운드 실행 제한이라고 합니다. 그래서 서버는 화면 밖의 앱이 늘 응답하리라고 기대하면 안 됩니다.
관련 항목
앱 생명주기가 거치는 상태
포그라운드 · 백그라운드 · 정지 상태 · 실행 안 됨 · Inactive 상태
앱 생명주기를 다루는 수단
생명주기 · 콜백 · 상태 복원 · 상태 저장 · 구성 변경 · 번들 (Android) · 뷰모델 · 앱 델리게이트 · 씬 델리게이트
앱을 다시 띄우는 방식
콜드 스타트 · 웜 스타트 · 핫 스타트 · 앱 시작 시간 · 스플래시 화면
앱 생명주기 아래에서 도는 부품
앱 컴포넌트 · 액티비티 · 액티비티 생명주기 · 서비스 (Android) · 포그라운드 서비스 · 프래그먼트 · 뷰 컨트롤러
앱 생명주기를 정하는 운영체제 장치
운영체제 · 프로세스 · 메모리 · 프로세스 중요도 · 캐시 프로세스 · Low Memory Killer · OOM 킬러 · 메인 스레드
화면 밖의 앱이 일하는 방법
백그라운드 실행 제한 · 푸시 알림 · 백그라운드 작업 · WorkManager · 백그라운드 앱 새로 고침 · ANR
서버가 앱 생명주기를 맞는 방법
웹소켓 · 재연결 · 하트비트 · 롱 폴링 · 지수 백오프 · 오프라인 우선
앱 생명주기와 견주는 서버 쪽 생명주기
그레이스풀 셧다운 · 시그널 · 빈 생명주기 · 서블릿 컨테이너 · 제어의 역전 · 컴포넌트 생명주기
앱 생명주기가 속하는 개발 분야
다른 이름: app lifecycle · application lifecycle · 앱 라이프사이클 · 애플리케이션 생명주기 · 앱 수명 주기