우테코 미션 중, PR에서 Activity와 Intent 관련하여 질문을 받았다. 그에 대한 질문들과 답변들을 모아서 정리하여 쓴 글이다. 질문에 답변하느라 힘을 많이 써서 노력한게 아까워서라도 블로그에 써야겠다.
안드로이드의 Activity와 Intent를 배우고 자연스러운 의문이 하나 생긴다, 왜 Intent를 사용해서 값을 전달할까? 이전에 다루었던 프레임워크인 Flutter는 Intent 같은 특별한 경로를 두지 않고 값이 전달이 가능했는데, 정작 네이티브인 안드로이드는 더 복잡한 처리를 하고 있는 것처럼 보인다.
해당 글은 위 의문을 해소하고 그 과정 중에 발생하는 의문을 다룬다.
0. Intent
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("productId", 42)
startActivity(intent)
Intent는 액티비티간 통신을 주관하며, 값을 전하거나 명시적 Intent를 사용하여 내 앱의 다른 액티비티로 전환하거나 묵시적 Intent를 사용하여 다른 앱의 서비스(카메라, 갤러리 등)를 불러올 수 있다.
값을 넣는 쪽에서는 intent에 putExtra를 통해 값을 넣고 값을 받는 쪽에서는 Bundle로 전달되어getExtra 를 통해 값을 받는다. 지금은 단순히 원시값을 전달하려고 하지만, 내가 만든 객체는 넣을 수 없다.
1. Activity란 무엇인가
액티비티란,
- 사용자가 보는 하나의 ”화면”에 해당 하는 단위
- 시스템이 Life Cycle(생애주기)을 관리하는 단위
여기에서, 두번째 줄에서 시스템이 라이프사이클을 관리한다고 하였다. 이말은 즉, “액티비티를 전환하거나 액티비티가 켜지고 꺼질 때의 상태를 개발자가 직접 관리하지 않는다.”라는 말과 같다.
2. 글로벌 변수로 충분한 거 아닌가?
액티비티 전환은 내 앱(같은 프로세스)에서 이루어지고 같은 메모리를 공유하여 글로벌 변수를 사용하여 값을 공유하는 직관은 언뜻 합리적이란 생각이 든다.
object GlobalState {
var productId: Int = 0
}
// ActivityA
GlobalState.productId = 42
startActivity(Intent(this, ActivityB::class.java))
// ActivityB
val id = GlobalState.productId // 42
위는, 싱글톤 객체를 두고 그 값을 공유하는 과정을 표현하였다. 안드로이드에서도 액티비티간 위 코드처럼 실제로 값을 전달할 수 있고 사용할 수 있을 것 같다. 하지만, 아래와 같은 문제가 발생한다.
- 컴포넌트 간 결합도 증가
- 메모리 낭비(가비지 컬렉션 어려움)
- 데이터 이동 경로 파악 어려움
- 테스트 어려움
사실 안드로이드 플랫폼을 생각하면 그리 와닿는 설명은 아니다, 안드로이드뿐만 아니라 다른 플랫폼에서도 위와 같은 이유를 가지고 전역변수로 값을 공유하지 않는 이유를 설명할 수 있을 것이다.
3. 전역 변수가 깨지는 순간
3-1. Process Death
앱을 켜두고 다른 작업을 오랫동안 수행했을 때 메모리가 부족하면 안드로이드 시스템은 메모리를 확보하기 위해 사용하지 않는 앱(프로세스)를 종료시킨다. 이를 Process Death라고 하고 최근 앱 목록에서 종료된 앱을 누르면 시스템은 종료되기 전 마지막 Acitivity를 재생성하려고 한다.
이 때, 프로세스가 종료되었기에 전역 변수에는 초기값이 설정된다. 반면에, Intent로 전달했다면, 시스템의 Bundle에 값이 저장되어 있어 해당 값으로 액티비티를 복구 할 수 있다.
3-2. 다른 프로세스로 가는 경우
프로세스 별로 메모리 공간이 분리되어 있기 때문에 내 앱에서 가지고 있는 전역 변수는 내 프로세스만 알 수 있다. 그렇기에 다른 프로세스로 이동해버리면 내 전역 변수는 사용할 수 없다.
- 묵시적 Intent로 다른 앱을 호출하는 경우 (예: 카메라, 공유 시트)
android:process로 같은 앱 안에서 컴포넌트를 다른 프로세스로 분리한 경우
4. 같은 앱(같은 프로세스)안에서도 IPC가 일어난다
이전 문단에서 다룬 Process Death는 기기 성능이 비약적으로 향상된 지금에서야 잘 발생하기 어렵다. 또한, 내 앱에서만 모든 걸 처리하고 다른 프로세스로 가는 경우만 없으면 전역 변수로 사용해도 될 것 같다.
사실, 같은 앱에서 액티비티를 이동해도 IPC가 필요하다. Activity 전환은 내 앱에서만 이루어지는 게 아니라 시스템의 ActivityManagerService 를 거친다. 즉, 아래와 같은 구조를 갖는다.
5. 왜 “프로세스간 통신”인가?
액티비티에서 액티비티로 이동하는 케이스가 대표적이지만, “프로세스간 통신”이라는 용어는 적절치 않았다고 생각할 수 있다. 하지만, 상술했던 경우를 생각해보면 납득할 수 있을 것이다. 아래는 그 정리다.
- ActivityA -> 시스템 프로세스 -> ActivityB 의 경우에는 시스템 프로세스를 한번 거치기에 프로세스간 통신이라 할 수 있음
- 내 앱 -> 시스템 프로세스 : 내 앱에서 시스템 프로세스의
ActivityManagerService와 통신하기 위해서 IPC가 필요함 - 시스템 프로세스 -> 내 앱 : 시스템 프로세스에서 내 앱의 Activity를 호출하기 위해서 IPC가 필요함
- 내 앱 -> 시스템 프로세스 : 내 앱에서 시스템 프로세스의
android:process로 액티비티를 구분한 경우 같은 앱이지만 다른 프로세스이기에 프로세스간 통신이라 할 수 있음android:process로 액티비티를 분리하면, 같은 앱이어도 다른 프로세스가 되어 메모리가 격리됨. 따라서 두 컴포넌트 간 데이터 공유에 IPC가 필요해진다.
- 묵시적 Intent를 통해 내 앱이 아닌 "다른 앱", 즉 "다른 프로세스"를 호출하는 경우에 다른 프로세스에 정보를 전달하기에 프로세스간 통신이라 할 수 있음
- 내 앱 -> 시스템 프로세스 : 내 앱에서 시스템 프로세스의
ActivityManagerService와 통신하기 위해서 IPC가 필요함 - 시스템 프로세스 -> 다른 앱 : 시스템 프로세스에서 다른 앱으로 통신하기 위해 IPC가 필요함
- 내 앱 -> 시스템 프로세스 : 내 앱에서 시스템 프로세스의
6. 다시 처음 질문으로
안드로이드는 왜 Intent를 쓰는 걸까?
- 액티비티 전환 시 시스템 프로세스를 거치므로, 객체 주소가 아닌 바이트(직렬화된 값)로 넘겨야 한다.
- 그 평탄화된 값들을 담은
Bundle을 시스템이 보관하므로, Process Death 이후에도 복구가 가능하다.
7. Flutter는?
플러터는 "단일 Activity" 모델이다
플러터 앱은 안드로이드에서 보면 보통 FlutterActivity 하나로 시작한다. 그 한 개의 Activity 안에서 모든 UI가 그려진다.
플러터에서 말하는 "화면"은 안드로이드의 Activity가 아니라 위젯 트리의 한 노드다. 화면 전환은 시스템 컴포넌트를 갈아끼우는 게 아니라, Navigator가 위젯 스택을 push/pop하는 것이다.
Navigator.push(
context,
MaterialPageRoute(builder: (_) => DetailPage(product: product)),
);
product라는 객체가 그대로 다음 화면 위젯의 생성자로 들어간다. 직렬화도 putExtra도 없다.
왜 그게 가능한가
같은 프로세스, 즉, 같은 Dart 런타임 안에서 일어나기 때문이다.
- 시스템 프로세스를 거치지 않는다 -
system_server도ActivityManagerService도 끼지 않는다. - 객체의 가상 주소가 그대로 의미를 가진다.
- 그래서 직렬화 자체가 필요 없다.
왜 그런 길을 골랐나
- 플랫폼 독립성 - iOS, Web, Desktop에서 동일한 동작이 필요하다. 안드로이드 컴포넌트 모델에 묶이는 순간 그 약속이 깨진다.
즉, 플러터는 "OS에 컴포넌트 관리를 맡기는 안드로이드의 모델"을 의도적으로 빠져나왔다. 자기 안에서 UI·네비게이션·상태 관리를 다 해버린다.
그 선택의 대가
공짜는 아니다. 안드로이드의 컴포넌트 모델 바깥으로 나오면, 그 모델이 주던 보너스도 같이 잃는다.
- Process Death 자동 복원이 없다. 위젯 트리는 메모리 위에만 살아있으니 프로세스가 죽으면 같이 사라진다. 플러터가 별도로 제공하는
RestorationMixin/RestorableProperty로 개발자가 직접 상태 복원 로직을 짜야 한다. - 외부 앱과의 IPC는 결국 네이티브로 내려간다. 카메라 띄우기, 공유 시트, 딥링크 처리 같은 건 Platform Channel을 통해 결국 Intent를 사용한다. 즉 "Intent를 안 쓰는 것"이 아니라 "내부 화면 전환에서만 안 쓰는 것"이다.
두 길의 비교
| 항목 | 안드로이드 (네이티브) | 플러터 |
|---|---|---|
| 화면 전환 단위 | 시스템이 관리하는 Activity | 위젯 트리의 노드 |
| 화면 전환 경로 | 내 앱 → system_server → 내 앱 | 같은 isolate 안 |
| 데이터 전달 | Intent + Bundle (직렬화) | 객체 참조 그대로 |
| Process Death 복원 | 시스템 + Bundle이 책임짐 | 개발자가 직접 (Restoration API) |
| 외부 앱 통합 | 자연스럽게 Intent | Platform Channel 경유 |
'안드로이드' 카테고리의 다른 글
| 컴포즈 애니메이션 최적화 (0) | 2026.06.07 |
|---|---|
| 비동기에서 suspendCancellableCoroutine까지, 코루틴이 정말 하는 일 (0) | 2026.05.18 |
| Q2. inline + non-local return vs labeled return (0) | 2026.04.26 |
| Q1. by lazy + 순환 초기화 (0) | 2026.04.22 |
| 리컴포지션 원정대 - Restart Scope, inline Composable (0) | 2026.04.19 |