안드로이드의 애플리케이션 구성은 4대 컴포넌트로 이루어져 있다.
액티비티(Activity): 화면 전환과 UI 관리, 최근 Compose에서는 단일 액티비티에서 여러 화면을 구성하는 것이 일반적.
서비스(Service): 백그라운드에서 작업을 수행, UI와 독립적으로 데이터 동기화나 긴 작업을 처리.
브로드캐스트 리시버(Broadcast Receiver): 시스템 이벤트나 다른 앱에서 발생한 메시지를 수신하고 처리, UI와 분리된 상태에서 이벤트 처리.
컨텐트 프로바이더(Content Provider): 앱 간 데이터 공유를 처리, 다른 앱에서 데이터를 가져오거나 제공하는 데 사용.
-> 이 각각의 컴포넌트간의 통신을 맡고 있는 것이 Intent(인텐트)다.
액티비티 (Activity): 앱의 진입점, 네비게이션 호스트, 권한 요청 또는 특수 작업 처리 등
서비스 (Service): 유튜브 앱에서 백그라운드 작업을 처리한다. 예를 들어, 동영상 다운로드나 푸시 알림 전송을 담당한다.
브로드캐스트 리시버 (Broadcast Receiver): 시스템 이벤트를 수신하여 앱 내 동작을 변경한다. 예를 들어, 네트워크 상태 변경이나 배터리 상태 변화에 반응하여 동작을 조정한다.
컨텐트 프로바이더 (Content Provider): 앱 간 데이터 공유를 담당한다. 예를 들어, 유튜브 동영상을 다른 앱과 공유하거나 다른 앱에서 댓글을 가져오는 데 사용된다.
시작하려는 컴포넌트를 명확히 지정한다. 주로 내부 컴포넌트 간 통신에 사용된다.
예: 특정 Activity를 시작할 때.
val intent = Intent(this, TargetActivity::class.java)
startActivity(intent)
작업의 목적만 정의하고, 이를 처리할 수 있는 앱을 시스템이 선택하도록 맡긴다.
예: 웹 브라우저에서 URL 열기.
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.google.com"))
startActivity(intent)
하지만 위 정의와는 다르게도,
Jetpack Compose에서는 전통적인 Android View 시스템에서 사용하던 Intent 기반 통신을 많이 사용하지 않는다.
앱 내부 통신은 Compose의 선언적 UI 패턴과 더 잘 맞는 State, ViewModel, Navigation 등을 사용하고, 외부 시스템이나 앱과의 통신에만 Intent를 사용하는 것이 현대적인 Android 개발 방식이다.
// 화면 간 이동과 데이터 전달
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("detail/{itemId}") { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailScreen(itemId)
}
}
// 화면 이동
navController.navigate("detail/${item.id}")
class MainViewModel : ViewModel() {
private val _uiState = MutableStateFlow(MainUiState())
val uiState = _uiState.asStateFlow()
fun updateData() {
_uiState.update { ... }
}
}
@Composable
fun MainScreen(viewModel: MainViewModel) {
val uiState by viewModel.uiState.collectAsState()
// UI 업데이트
}
// 여러 화면에서 동일한 ViewModel을 공유
class SharedViewModel : ViewModel() {
private val _data = MutableStateFlow<Data>()
val data = _data.asStateFlow()
}
@Composable
fun Screen1(sharedViewModel: SharedViewModel) {
val data by sharedViewModel.data.collectAsState()
}
@Composable
fun Screen2(sharedViewModel: SharedViewModel) {
val data by sharedViewModel.data.collectAsState()
}
예를 들어 공유하기를 눌렀을 때:
우리 앱의 Activity → 시스템의 Chooser Activity(공유 대상 선택 화면) → 선택된 앱의 Activity 작업이 완료되면 다시 우리 앱의 Activity로 돌아옴
이때 우리 앱의 원래 Activity는 백그라운드로 갔다가 다시 포그라운드로 돌아오는 생명주기를 거침. 참고로 포그라운드에서 실행되는 프로세스는 사용자와 상호 작용하는 프로세스이다.
onPause() → onStop() → onRestart() → onStart() → onResume()
공유하기 기능은 보통 우리 앱(소스 앱)과 공유 대상 앱(목적지 앱) 간의 통신이다. 이 경우, Intent는 다음 역할을 한다.
소스 앱 (우리 앱):
공유하려는 데이터를 Intent에 담아서 시스템에 요청한다.
ex: "이 텍스트를 공유 가능한 앱에게 전달해 주세요."
Android 시스템:
Intent를 받아서, 공유 기능을 처리할 수 있는 앱들을 확인한다. (이를 Implicit Intent라고 함.)
예: 메시지 앱, 이메일 앱, 소셜 미디어 앱 등이 공유를 처리할 수 있는 앱 목록으로 표시된다.
목적지 앱 (공유 대상 앱):
사용자가 선택한 앱이 Intent를 받아 데이터를 처리한다.
예: 사용자가 메시지 앱을 선택하면, Intent에 담긴 텍스트를 메시지 작성 화면에 표시한다.
정확하다.
이전/현재 모두 동일하게 사용
주요 사용 사례:
// 전화 걸기
Intent(Intent.ACTION_DIAL, Uri.parse("tel:$phoneNumber"))
// 공유하기
Intent(Intent.ACTION_SEND)
// 웹 브라우저 열기
Intent(Intent.ACTION_VIEW, Uri.parse(url))
// 갤러리에서 이미지 선택
Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
이러한 변화가 일어난 이유:
1. 단방향 데이터 흐름(Unidirectional Data Flow) 패턴의 보편화
2. 상태 관리의 중요성 증가
3. 컴포넌트 간 결합도를 낮추기 위한 아키텍처 진화
아니요, "메시지"는 Intent가 아니 다.
"메시지"는 Intent에 담긴 데이터에 불과하다.
Intent는 우리가 공유하려는 "메시지"를 담고, Android 시스템과 다른 앱에게 전달하는 택배박스 같은 역할을 한다. 택배박스에는 데이터를 담을 수 있고, 어떤 앱(수신자)에게 전달해야 하는지 주소를 설정할 수도 있다.
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain" // 박스에 들어갈 데이터의 타입 (여기선 텍스트)
putExtra(Intent.EXTRA_TEXT, "메시지") // 박스 안에 담긴 데이터
}
startActivity(Intent.createChooser(shareIntent, "앱을 선택하세요"))