컴포넌트를 잇는 메시지 객체.
// 명시적 — 대상 클래스를 지정한다
startActivity(Intent(this, DetailActivity::class.java).putExtra("id", 42L))
// 암시적 — "이런 일을 할 수 있는 앱" 을 요청한다
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com")))
startActivity(Intent(Intent.ACTION_SEND).apply {
type = "text/plain"; putExtra(Intent.EXTRA_TEXT, "공유할 내용")
})
암시적 인텐트가 실패하는 경우
val intent = Intent(Intent.ACTION_VIEW, uri)
if (intent.resolveActivity(packageManager) != null) startActivity(intent)
else showError("이 작업을 처리할 앱이 없습니다")
// 또는
runCatching { startActivity(intent) }
.onFailure { /* ActivityNotFoundException */ }
처리할 앱이 없으면 ActivityNotFoundException 으로 크래시한다
- 암시적 인텐트는 항상 방어한다
Android 11+ 에서는 resolveActivity 가 보이려면 AndroidManifest 에 <queries> 로 대상을 선언해야 한다 (패키지 가시성 제한)
결과를 받아오기
// startActivityForResult 는 폐기됐다
private val picker = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? -> uri?.let { load(it) } }
button.setOnClickListener { picker.launch("image/*") }
왜 바뀌었나 requestCode 정수를 직접 관리해야 했고 충돌이 잦았다 프로세스 죽음 후 결과 복원이 깨졌다
- ActivityResult API 는 등록을 onCreate 전에 하도록 강제해 복원을 보장한다
PendingIntent — 남에게 맡기는 인텐트
val pi = PendingIntent.getActivity(
ctx, 0, Intent(ctx, DetailActivity::class.java),
PendingIntent.FLAG_IMMUTABLE // ★ Android 12+ 필수
)
알림 시스템 · AlarmManager 처럼 '다른 프로세스가 내 대신 실행' 해야 할 때 쓴다
FLAG_IMMUTABLE 을 안 주면 Android 12+ 에서 IllegalArgumentException 이다
- 가변 PendingIntent 는 받은 쪽이 내용을 바꿀 수 있어 보안 구멍이 된다
면접 함정
- ❌ "Intent extra에 큰 객체를 담아 넘긴다" → Binder 트랜잭션 한도(약 1MB)에 걸려
TransactionTooLargeException이 난다. id만 넘기고 다시 조회한다. - ❌ "암시적 인텐트로 내 앱의 Service를 부른다" → Android 5.0부터 금지다. 명시적이어야 한다.
Intent 필터가 매칭되는 규칙
암시적 인텐트는 세 가지로 매칭된다
action 무엇을 하려는가 (ACTION_VIEW · ACTION_SEND)
category 어떤 맥락인가 (DEFAULT 는 암시적 인텐트에 사실상 필수)
data URI 스킴 · 호스트 · 경로 · MIME 타입
셋이 모두 맞아야 후보가 된다
- "딥링크가 안 열려요" 의 절반은 CATEGORY_BROWSABLE 누락이다
인텐트로 다른 앱을 여는 흔한 예
// 전화 걸기 화면 (권한 불필요 — ACTION_CALL 은 권한이 필요하다)
Intent(Intent.ACTION_DIAL, Uri.parse("tel:021234567"))
// 앱 설정 화면
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", packageName, null))
// 지도
Intent(Intent.ACTION_VIEW, Uri.parse("geo:37.5,127.0?q=서울역"))
// 공유 시트를 강제로 띄운다
startActivity(Intent.createChooser(sendIntent, "공유"))
보안 — 받은 인텐트를 믿지 않는다
// exported 컴포넌트는 아무 앱이나 부를 수 있다
override fun onCreate(s: Bundle?) {
val id = intent.getLongExtra("id", -1)
require(id > 0) { "잘못된 진입" } // 검증한다
val redirect = intent.getStringExtra("next")
// ❌ 검증 없이 이 값으로 다른 컴포넌트를 부르면 인텐트 리다이렉션 취약점이다
}
Android 12+ 는 intent-filter 가 있는 컴포넌트에 android:exported 명시를 강제한다 필요 없으면 exported="false" 로 닫는 것이 기본이다