인텐트란?
인텐트는 안드로이드 애플리케이션에서 4대 컴포넌트 구성 요소들 간에 통신하는 데 사용되는 객체입니다. 인텐트는 Activity를 실행하는 데 가장 많이 사용되지만 다른 용도도 있습니다.
인텐트 유형에는 암시적 인텐트와 명시적 인텐트가 있습니다.
명시적 인텐트(Explicity Intent): 특정 컴포넌트를 명시하여 실행하는 인텐트
암시적 인텐트(Implicit Intent): 추상적이며 목적지를 명시적으로 지정하지 않고, 수신자가 필터를 통해 처리할 수 있는 인텐트
명시적 인텐트 예시 - Activity 이동
binding.button.setOnClickListener {
val intent = Intent(this, SecondActivity::class.java).apply {
putExtra("letter", "안녕하세요")
}
startActivity(intent)
}
위의 인텐트는 현재 액티비티에서 `SecondActivity`라는 액티비티로 이동하는 명시적 인텐트를 사용한 예시입니다.
이동할 액티비티는 `SecondActivity::class.java`로 찾습니다. 이 때 리플렉션을 이용해서 액티비티를 찾게 됩니다.
그리고 putExtra 메서드를 호출하여 '문자'를 첫 번째 인수로 전달하고 "안녕하세요" 라는 String을 두 번째 인수로 전달합니다. 대상 액티비티의 인스턴스는 아직 생성되지 않았기에 존재하지 않습니다. extra는 나중에 검색할 수 있도록 이름이 지정된 숫자나 문자열과 같은 데이터 입니다.
마지막으로 컨텍스트 객체에서 `startActivity()` 메서드를 호출해 intent를 전달합니다.
이제 `SecondActivity`의 `onCreate()` 메서드에서 `setContentView` 호출 후에 인텐트에서 전달된 `letter`를 가져오는 코드를 호출합니다.
val letter = intent?.extras?.getString("letter").toString()
binding.textView.text = letter
위의 코드에서의 intent는 특정 액티비티의 속성이 아니라 모든 액티비티의 속성입니다. extras 속성은 `Bundle` 유형이고 인텐트에 전달된 모든 extras에 액세스하는 방법을 제공합니다.
명시적 인텐트 예시 - 다른 애플리케이션 실행
명시적 인텐트를 사용해서 다른 애플리케이션을 실행할 수도 있습니다. 여기서는 유튜브를 한번 실행해 보겠습니다.
binding.youtubeButton.setOnClickListener {
val intent = Intent(Intent.ACTION_MAIN).apply {
`package` = "com.google.android.youtube"
}
startActivity(intent)
}
인텐트 안에 정의한 것은 `액션`입니다. 액션 해당 인텐트가 어떤 동작을 수행할지 명시하는 String입니다. 우리는 유튜브의 기본 화면을 표시할 예정이기에 `ACTION_MAIN`으로 설정했습니다.
우리의 만든 앱이 package가 있듯이, 우리가 사용하는 유튜브나 다른 앱도 package가 존재합니다.
package com.jun.myapplication
Intent에서 패키지를 설정하면 해당 앱을 타깃으로 할 수 있습니다.
다만 유튜브가 없는 기기의 경우에는 위의 코드론 오류가 발생할 수 있습니다.
binding.youtubeButton.setOnClickListener {
val intent = Intent(Intent.ACTION_MAIN).apply {
`package` = "com.google.android.youtube"
}
try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
e.printStackTrace()
}
}
그렇기에 위와 같이 오류를 처리해 줘야 합니다. 만약 유튜브가 없어서 오류가 발생했다면 여기서 유튜브를 설치하는 코드를 작성할 수도 있겠네요
암시적 인텐트
유튜브같이 명확하게 내가 어떤 앱을 실행할 것인지 확실한 경우에는 명시적 인텐트를 사용합니다. 하지만 만약 이메일을 전송하려고 한다고 생각해 보겠습니다.
우리는 어떤 앱을 사용해서 이메일을 전송할 것인지 정하지 못했습니다. 네이버, Gmail 등 선택지는 여러 개입니다. 그렇기에 이때 우리는 암시적 인텐트를 사용합니다.
binding.sendEmailButton.setOnClickListener {
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_EMAIL, arrayOf("jun@email.com"))
putExtra(Intent.EXTRA_SUBJECT, arrayOf("메일 제목"))
putExtra(Intent.EXTRA_TEXT, arrayOf("메일 내용"))
}
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
}
}
위에서는 아까와 같이 액션을 정의하고 있습니다. 추가적으로 설정해야 할 것은 type입니다. type으로 인텐트가 처리하려는 데이터의 종류를 나타낼 수 있습니다.
그리고 resolveActivity(packageManager)를 통해 해당하는 앱이 있는지 확인합니다.
아마 이대로라면 아래와 같은 경고가 발생할 것입니다.
이는 구글에서 우리가 만든 앱이 manifest에서 <queries>를 작성하는 경우에만 이에 해당하는 앱을 검색할 수 있게 하기 윈 하기 때문에 생기는 경고입니다.
이런 경고가 발생하는 이유는 우리가 이메일을 전송할 수 있는 앱을 열고 싶어서 인텐트를 전송할 때, 이메일과 관련 없는 앱가지 우리가 보낸 인텐트를 수신하게 하고 싶지는 않기 때문입니다.
<queries>
<intent>
<action android:name="android.intent.action.SEND"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
코드블록이 위와 같이 추가한다면 경고는 사라집니다.
인텐트 필터
우리가 만든 앱에서 다른 앱을 실행하고 데이터를 보낼 수 있다는 것을 확인했습니다. 그렇다면 반대도 가능하지 않을까요?
이를 위한 것이 바로 인텐트 필터입니다.
인텐트 필터를 학습하기 위해 크롬에서 고양이 사진을 검색하고 해당 사진을 우리의 앱에서 수신하고 이를 띄워보는 방법을 알아보겠습니다.
크롬에서 내가 원하는 고양이 사진을 선택하고 이를 전달합니다. 다만, 위에서 알 수 있듯이, 우리의 앱은 보이지가 않습니다. 그렇기에 우리는 앱의 manifest에서 우리가 이미지를 수신할 Activity의 <intent-filter> 요소에서 수신할 작업을 정의해서 우리의 앱이 보이게 해야 합니다.
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
MainActivity에는 초기에 실행될 때 필요한 인텐트 필터가 정의되어 있습니다. 그렇기에 우리가 원하는 작업을 하기 위해서는 추가로 인텐트 필터를 정의합니다. 아까 전에 queries를 정의할 때와 비슷합니다.
다른 앱에서 인텐트를 보내서 우리 앱이 열리게 되는 것은 우리가 원하는 바입니다.
그런데 만약 우리의 앱이 이미 실행되어서 백스택에 있다면 어떻게 될까요?
바로 우리의 앱이 이미 켜져 있더라도 켜져 있는 인스턴스틀 사용하는 것이 아니라 새로운 인스턴스를 생성해 우리의 앱을 실행하게 됩니다. 이렇게 되면 이전에 실행했던 우리의 앱의 정보는 사라지고 새롭게 액티비티가 생성됩니다.
이를 해결하는 방법은 기존에 열려있던 우리의 앱의 인스턴스를 가져오기 위해 launchMode를 sigleTop으로 변경해야 합니다.
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:exported="true">
이와 관련된 내용은 복잡하기에 여기서는 그냥 넘어가겠습니다. 관심이 있다면 아래의 게시글을 참고해 주세요.
이제 우리의 앱을 공유하기에서 찾을 수 있습니다.
보내진 인텐트의 수신은 액티비티의 생명주기 중 하나인 onNewIntent()에서 할 수 있습니다.
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
} else {
intent?.getParcelableExtra(Intent.EXTRA_STREAM)
}
binding.imageView.load(uri)
}
Reference
'Android' 카테고리의 다른 글
UI 상태 저장과 ViewModel로 상태 저장하기 (0) | 2023.08.05 |
---|---|
작업 및 백 스택 이해 (0) | 2023.08.04 |
안드로이드의 Context (0) | 2023.07.11 |
안드로이드의 앱 구성요소 (4대 컴포넌트) (0) | 2023.07.11 |
[Android] Fragment Life Cycle (프래그먼트 생명주기) (0) | 2023.06.13 |