안드로이드 어플리케이션을 구성하는 4가지 기본 요소에는 Activity, Service, Broadcast Receiver, Content Provider가 있다. 인텐트는 이러한 어플리케이션 구성요소 즉, 컴포넌트간에 작업 수행을 위한 정보를 전달하는 역할을 한다.
인텐트를 가장 손쉽게 사용한 예로는 액티비티간 화면 전환을 들 수 있다. 즉 인텐트는 컴포넌트 A가 컴포넌트 B를 호출할 때 필요한 정보를 담고 있으며, 이 정보에는 호출되는 컴포넌트 B의 이름이 명시적으로 표시되기도 하고, 속성들이 암시적으로 표시되기도 한다.
또한 호출된 컴포넌트 B가 호출한 컴포넌트 A로 어떠한 결과를 전달할 때도 인텐트가 사용된다. 어떠한 컴포넌트를 호출하느냐에 따라서 사용되는 대표적인 메소드는 다음과 같은 것들이 존재한다.
startActivity() : 새로운 액티비티 화면을 띄울 때 사용
startService(), bindService() : 서비스와 관련된 메소드
broadcastIntent() : 브로드캐스팅을 수행할 때
인텐트의 기본 구성 요소로는 액션(Action)과 데이터(Data)가 존재한다. 액션은 수행할 기능이며, 데이터는 액션이 수행될 대상 데이터을 의미한다.
1 2
// 예를 들어 아래의 코드가 있다고 하자. var intent = Intent(Intent.ACTION_DIAL, Uri.parse(data))
액션은 ACTION_DIAL 즉, 전화 다이얼을 걸라는 액션이며,
data 값을 uri로 파싱한 Uri.pars(data)라는 것은 액션이 수행할 data 즉 전화번호일 것이다.
"요약하면 Uri로 파싱한 전화번호 data를 대상으로 전화다이얼을 걸어라"라는 뜻이고 이 뜻을 인텐트에 담아 안드로이드 시스템에게 전달하면 되는 것이다.
인텐트 동작 순서
Componenet가 ActivityManager에게 다른 컴포넌트의 실행을 요청
ActivityManager는 패키지 정보를 가지고 있는 PacakageManager에게 컴포넌트 정보 요청
유요한 컴포넌트임을 확인하면 컴포넌트를 실행
여기서 실행을 요청할 때 컴포넌트의 정보가 Intent이다.
Intent는 IPC 통신을 위한 직렬화 객체로 Parcelable을 통해 직렬화 되어 있다.
즉, 이 객체는 다른 프로세스로 전달하기 위한 데이터 그 자체이다.
활성화 될 컴포넌트 정보 + 활성화 될 컴포넌트에게 전달할 데이터
activity의 경우 startActivity()
service의 경우 startService()
Broadcast Recevier의 경우 sendBroadcast()
외부 패키지에서 접근하려면 이어야 한다.
Intent의 종류
안드로이드의 4대 컴포넌트가 상호 통신을 위해 사용하는 인텐트는 크게 두가지로 나뉜다.
명시적 인텐트
인텐트에 클래스 객체네 컴포넌트 이름을 지정하여 호출될 대상을 확실히 알 수 있는 경우에 사용하는 것을 명시적 인텐트라고 한다. 주로 애플리케이션 내부에서 사용한다.
간단하게 현재 액티비티에서 SecondActivity로 화면 전환을 하는 코드는 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 12
classMainActivity : AppCompatActivity() {
overridefunonCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button.setOnClickListener{ // 주의해서 볼 코드 var intent = Intent(this@MainActivity, SecondActivity::class.java) startActivity(intent) } } }
새로운 인텐트 객체인 intent를 생성하면서 this@MainActivity 즉, getContext와 같은 현재 액티비티 정보가 담겨있는 정보와 SecondActivity 즉, 호출할 컴포넌트를 파라미터로 넘겨준다. 그리고 startActivity()에 인텐트 객체인 intent를 파라미터로 넘겨주면 새로운 화면인 SecondActivity가 실행된다. startActivity()는 새로 띄우는 액티비티로부터 받는 응답을 처리할 필요가 없을 때 간단하게 사용된다.
이에 반해 **startActivityForResult()**의 경우 새로 띄운 액티비티로부터 받는 응답을 처리할 경우에 사용된다.
암시적 인텐트
인텐트의 액션과 데이터를 지정하긴 했지만, 호출할 대상이 달라질 수 있는 경우에는 암시적 인텐트를 사용한다. 즉 설치된 애플리케이션들에 대한 정보를 알고 있는 안드로이드 시스템이 인텐트를 이용해 요청한 정보를 처리할 수 있는 적절한 컴포넌트를 찾아본 다음 사용자에게 그 대상과 처리 결과를 보여주는 과정을 거치게 된다.
특정 컴포넌트에서 암시적 인텐트를 받기 위해서는 매니페스트 파일에서 요소와 함께 어플리케이션 컴포넌트 각각에 대해서 하나 이상의 인텐트 필터를 선언해야 한다. 각각의 컴포넌트는 action, data, category를 기반으로 해서 자신이 받길 원하는 인텐트의 유형을 명시해야 한다.
안드로이드 시스템은 전달되는 암시적 인텐트가 매니페스트에 존재하는 인텐트 필터 중의 하나와 매칭되는 경우 해당 컴포넌트로 암시적 인텐트를 전달해준다.
암시적 인텐트를 사용하는 대표적인 경우로 문서 편집기를 예로 들 수 있다. 카카오톡으로 친구가 자신의 자소서를 봐달라며 PDF 파일을 첨부했다. 우리는 그 PDF를 클릭하여 열기를 하면 해당 안드로이드 폰에 PDF를 편집하거나 보여줄 수 있는 많은 애플리케이션들이 서로 자기가 그 PDF 파일을 보여줄 수 있다고 손을 든다.
그러면 안드로이드 시스템에서는 ‘PDF를 열 수 있는 앱들이 이렇게 많은데 어떤거 선택할래?’ 라고 애플리케이션을 선택할 수 있는 위젯을 띄워준다. 이런 일련의 과정을 가능하게 하는 녀석이 암시적 인텐트이다.
그럼 왜 암시적 인텐트를 사용할까??
위의 예에서 우리는 친구의 자소서 PDF 파일을 열려고 한다. 여기서 PDF 파일을 열기 위해 이미 많은 PDF 리더 앱들이 존재한다. 그런데 굳이 우리가 PDF 리더를 만드는 것은 현실적으로 좋은 방법이 아니다. 따라서 이미 기존에 어떤 기능들을 지원하는 앱들이 있는 경우에 암시적 인텐트를 사용해서 그 앱들을 사용하면 되는 것이다.
1 2 3 4 5 6 7
// 네이버 페이지 var intent = Intent(Intent.ACTION_VIEW, Uri.parse("http://m.naver.com")) startActivity(intent)
// 전화 걸기 var intent = Intent(Intent.ACTION_VIEW, Uri.parse("tel:010-0000-0000")) startActivity(intent)
위와 같이 암시적 인텐트는 보통 액션(Action)과 데이터(data)라는 속성으로 구성되어 있다. 이 두 가지 속성 말고도 Category, Type, Component, Extras라는 속성을 가진다. 여기서 Component라는 속성을 지정할 경우 컴포넌트 클래스 이름을 명시적으로 지정하게 되는데 이 경우가 명시적 인텐트에 속하게 된다.
결국 암시적 인텐트는 Component 속성을 제외한 나머지 속성들로 구성되며, 이러한 속성들에 부합하는 컴포넌트가 실행된다.[호출할 대상들이 달라질 수 있다.]
안드로이드 폰에서 위의 코드 중 네이버에 접속하는 코드를 실행한 결과 화면은 아래와 같다.
이와 같이 암시적 인텐트는 그 속성에 부합하는 컴포넌트가 여러 개 있을 때 선택할 수 있도록 해준다.
Intent Flag
안드로이드 애플리케이션 개발을 하다가 Activity에 대해 중복을 방지하거나 다른 상황에 대처할 때 Flag에 대해 잘 관리를 한다면 Activity에 대한 이해를 할 수 있고 관리 또한 용이하게 할 수 있다.
안드로이드 태스크란?(Android Task, Activity Stack)
Task는 애플리케이션에서 실행되는 액티비티를 보관하고 관리하며 Stack 형태의 연속된 Activity들로 이루어진다.
LIFO(Last In First Out) 즉, 후입 선출 형태로 나중에 적재된 액티비티일수록 가장 먼저 사용된다.
만약 1->2->3 페이지 순으로 액티비티를 이동했을 때 실행 순서대로 Task에 push 했다가 back 버튼을 누르면 3->2->1 페이지 순으로 Task에서 pop 시켜 돌아간다.
서로 다른 애플리케이션 간의 이동에도 Task를 이용해 사용자 경험(UX)를 유지시켜준다.
최초 적재 액티비티는 Root Activity라고도 하며 애플리에키션 런처로부터 시작된다.
마지막으로 적재되는 액티비티는 Top Activity라고 하며 현재 화면에 활성화 되어 있는 액티비티를 말한다.
Task 내에는 서로 다른 애플리케이션의 액티비티들이 포함될 수 있어 애플리케이션에 경계 없이 하나의 애플리케이션인 것처럼 보이게 해준다.
Task의 Stack내에 존재하는 액티비티들은 모두 묶여서 background와 foreground로 함께 이동한다.
홈버튼 클릭 : task interrupt -> background
홈버튼 롱클릭 : recent task -> foreground
Flag를 사용하여 Task 내의 액티비티의 흐름을 제어할 수 있다.
어피니티란?(Android Affinity)
애플리케이션 내의 액티비티들은 하나의 어피니티(affinity : 친화력)을 가지고 있다.
AndroidManifest 파일에서 요소의 taskAffinity 속성을 사용해 개별 affinity가 지정 가능하다.
FLAG_ACTIVITY_NEW_TASK 플래그를 가진 인텐트 객체로부터 호출된 allowTaskReparenting 속성을 true로 가지고 있는 액티비티에 한해 affinity가 동작한다.
위 조건이 만족한 상황에서 시작된 액티비티는 자신과 동일한 어피니티를 갖는 태스크가 있을 경우 해당 태스크로 이동한다.
즉, [b] 어피니티를 가진 A 액티비티가 호출되어 해당 테스크에 속해있을 때 [b] 어피니티를 가진 태스크가 호출되면 A 액티비티는 [b] 어피니티를 가진 태스크로 이동한다.
어피니티에 의해 테스크가 이동된 후에 back 버튼으로 반환시 원래 해당하던 테스크로 돌아간다.
하나의 애플리케이션내에서 하나 이상의 기능을 갖는 애플리케이션이 존재할 경우 각 액티비티별로 다른 어피니티를 지정해 관리할 수 있다.
Flag를 사용하는 방법은 AndroidManifest 파일에서 사용하는 방법과 Intent 코드로 사용하는 방법이 있다.
AndroidManifest에서 사용하기
안드로이드의 launchMode 속성을 사용하여 적용할 수 있다. 원하는 액티비티에 속성을 추가하여 적용하면 된다. launchMode에서 사용 가능한 속성은 다음과 같이 4가지만 가능하다.
standard : 스택 중 어느 곳에나 위치 가능하며 여러 개의 인스턴스가 생성 가능하다.
singleTop : 스택 중 어느 곳에나 위치 가능하며 여러 개의 인스턴스가 생성 가능하고 호출한 activity와 현재 최상위 activity가(top activity) 동일한 경우 최상위 activity가 재사용된다.(기존 최상위 activity는 pop)
singleTask : 루트 액티비티로만 존재하며 하나의 인스턴스만 생성 가능하다. (타 task에서 동일 activity 사용 불가) 다른 액티비티 실행시 동일 Task 내에서 실행이 가능하다.
singleInstance : 루트 액티비티로만 존재하며 하나의 인스턴스만 생성 가능하고 태스크 내에 해당 액티비티 하나만 속할 수 있어 다른 액티비티를 실행시키면 새로운 Task가 생성되어 (FLAG_ACTIVITY_NEW_TASK와 동일) 그 Task 내에 포함된다.
소스 코드에서 플래그를 사용하고 싶을 때는 Intent에 addFlags(), setFlags() 메소드를 사용한다.
FLAG_ACTIVITY_NEW_TASK : 동일 affinity의 task가 있으면 그곳에 실행되고 아니면 새로운 task를 실행한다.[실행은 액티비티를 만들어 테스크에 넣는 것을 의미]
FLAG_ACTIVITY_SINGLE_TOP : 호출되는 액티비티가 최상위에 존재할 경우에는 해당 액티비티를 다시 생성하지 않고 존재하던 액티비티를 재사용한다. [실행시 재사용 액티비티의 실행은 onPause(), onNewIntent(), onResume() 순으로 호출된다.]
[B]를 single top 설정 : [A][B] 상태에서 [B] 호출 시 => [A][재사용된 B]
[A]를 single top 설정 : [B][A] 상태에서 [B] 호출 시 => [B][A][B]
FLAG_ACTIVITY_NO_HISTORY : 해당 액티비티는 재활성화시(back 키를 눌러 다시 활성화될 때) pop된다.
[B]를 no history 설정 : [A][B][A] 상태에서 back 키 사용시 [A]가 pop되고 [B] 역시 no history에 의해 pop => [A] 만 남음.
FLAG_ACTIVITY_REORDER_TO_FRONT : activity 호출 시 이미 같은 activity가 task 내에 있으면 같은 activity는 pop시키고 해당 activity가 push 된다.
[A]를 reorder to front 설정 : [A][B] 상태에서 [A] 호출 시 같은 activity인 [A]가 pop되고 => [B][A]
FLAG_ACTIVITY_CLEAR_TOP : 해당 task에 있는 모든 activity를 pop시키고 해당 activity가 root activity로 task에 push된다.
[A]를 clear top 설정 : [A][B] 상태에서 [A] 호출시 모두 pop되고 => [A]
단, 해당 플래그는 액티비티를 모두 onDestroy()시킨 후 새롭게 onCreate()시키기 때문에 [A]를 유지하려면 FLAG_ACTIVITY_SINGLE_TOP 플래그와 함께 사용하면 된다.
Task를 오랫동안 사용하지 않고 방치해두면 시스템은 Root Activity를 제외한 모든 액티비티를 Clear 시킨다.
이러한 동작은 Activity의 속성을 수정하여 제어할 수 있다.
alwaysRetainTaskState
Task의 Root Activity에 true로 설정되어 있다면 상단에 언급되었던 동작은 발생하지 않으며 Task는 오랜 시간 이후에도 Stack에 있는 모든 Activity를 유지한다.
clearTaskOnLaunch
이 속성이 true로 설정되어 있으면 alwaysRetainTaskState과 정반대로 사용자가 Task를 떠났다가 다시 돌아올 때마다 항상 Stack은 Root Activity로 정리된다.
finishOnTaskLaunch
이 속성은 clearTaskOnLaunch와 유사하지만 전체 Task가 아닌 단일 Activity에서 동작한다. 그리고 그것은 Root Activity를 포함한 어떤 Activity가 사라지는 원인이 될 수도 있다. true로 설정되어 있을 때, Activity는 현재 Sessing 동안 Task의 일부만 유지한다. 만일 사용자가 해당 Task를 벗어났다가 다시 돌아오면 더 이상 존재하지 않는다.
Fragment란 하나의 Activity가 여러 개의 분할된 화면을 가지도록 만들기 위해 고안된 개념이다. 다양한 크기의 화면을 가진 모바일 환경이 늘어나고 태블릿의 큰 화면에 대한 Activity의 비효율성이 부각되었다. 이처럼 하나의 디스플레이 화면 안에서 다양한 화면을 보여주고 싶은 니즈를 충족시키기 위해 등장한 것이 Fragment이다.