(원문: http://developer.android.com/guide/components/intents-filters.html)

(위치: Develop > API Guides > Intents and Intent Filters)

(전체 번역 글 목록으로 이동하기)


인텐트와 인텐트 필터


인텐트는 앱의 컴포넌트들 간에 요청 및 메시지를 전달해 주는 객체입니다. 인텐트를 사용하는 방법에는 여러가지가 있지만 대표적인 세가지는 아래와 같습니다.

  • 액티비티 시작하기:
    하나의 액티비티는 내 앱에서 보통 하나의 화면을 구성합니다. 현재 컴포넌트에서 새로운 액티비티를 실행하기 위해서는, 인텐트에 실행할 액티비티 정보와 (필요하다면) 액티비티에 넘겨줄 데이터를 담아서 startActivity() 호출시 인자로 넘겨주면 됩니다.
    현재 액티비티에서 새로운 액티비티를 실행하고 그로부터 결과값을 받아오고 싶다면, startActivityForResult()를 호출합니다. 새로운 액티비티가 결과값을 설정하고 종료되면, 현재 액티비티의 onActivityResult() 콜백 메소드에서 결과값을 인텐트로 받을 수 있습니다. 자세한 내용은 Activity에 대하여에서 학습하실 수 있습니다.

  • 서비스 시작하기:
    서비스는 사용자에게 UI를 제공하지 않고 백그라운드에서 실행되는 컴포넌트입니다. 파일을 다운로드 하는 경우와 같이 백그라운드에서 한가지 일만 하고 종료되게 하려면, 인텐트에 실행할 서비스 정보와 (필요하다면) 넘겨줄 데이터를 담아서 startService() 호출시 인자로 넘겨주면 됩니다.
    만약, 액티비티와 서비스가 클라이언트-서버 구조로 동작하길 원한다면, bindService() 호출시 서비스 및 필요 데이터를 담은 인텐트를 넘겨서, 액티비티에 서비스를 바인드 합니다. 자세한 내용은 Service에 대하여에서 학습하실 수 있습니다.

  • 알림(broadcast) 발송하기:
    알림은 어떤 앱이건 (퍼미션만 가지고 있다면) 받을 수 있는 메시지입니다. 시스템이 부팅되었을 때나 디바이스가 충전되기 시작할 때 등의 상황에, 시스템은 알림 발송을 위해 이벤트를 발생시킵니다. sendBroadcast(), sendOrderedBroadcast(), sendStickyBroadcast() 호출시 필요한 데이터를 담은 인텐트를 인자로 넘기면 됩니다.


인텐트의 타입


인텐트는 아래와 같이 두가지 타입이 있습니다:

  • 명시적 인텐트는 실행할 컴포넌트의 클래스 정보를 명시적으로 가지고 있는 인텐트입니다. 내 앱 안에서 컴포넌트들을 실행할 때는, 컴포넌트들을 다 알고 있기 때문에, 보통 명시적 인텐트를 사용합니다. 

  • 암묵적 인텐트는 실행할 컴포넌트의 클래스 정보 대신 액션값을 선언한 인텐트이며, 다른 앱의 컴포넌트를 실행할 때에도 사용할 수 있습니다. 예를 들어, 내 앱에서 가지고 있는 위치값을 지도 위에서 보고 싶다면, 암묵적 인텐트를 이용하여 지도 기능을 가진 다른 앱의 컴포넌트를 실행할 수 있는 것입니다.

명시적 인텐트를 사용하여 액티비티나 서비스 실행 메소드를 호출하면, 시스템은 즉시 그 인텐트에 지정된 컴포넌트를 실행합니다.

암묵적 인텐트를 사용하면, 시스템은 우선 인텐트에 설정된 액션값 등의 정보와, 다른 앱들의 매니페스트 파일에 선언된 인텐트 필터들을 비교하여 조건에 맞는 컴포넌트를 찾습니다. 그래서 만약 한개의 컴포넌트를 찾았다면 인텐트를 넘겨주며 실행시키고, 두개 이상이라면 사용자가 선택할 수 있도록 화면에 선택 다이얼로그를 보여줍니다.

인텐트 필터는 매니페스트 파일의 컴포넌트 요소 안에 추가되는 요소로서, 컴포넌트가 받고자 하는 인텐트의 액션값 및 데이터 타입 등을 지정합니다. 예를 들면, 액티비티에 인텐트 필터를 선언함으로써, 다른 앱에서 그에 맞는 암묵적 인텐트로 그 액티비티를 실행할 수 있습니다. 반대로 액티비티에 인텐트 필터를 선언하지 않으면, 그 액티비티를 실행할 수 있는 액션값이 없는 것이므로 명시적 인텐트로만 실행할 수 있습니다.

주의사항: 앱의 보안을 위해서, 서비스에는 인텐트 필터를 선언하지 말고 명시적 인텐트로만 실행하도록 합니다. 암묵적 인텐트로 서비스를 실행하는 것은, 그 인텐트에 어떤 서비스가 응답할 것인지를 확신할 수 없고, 어떤 서비스가 실행되는지 사용자가 확인할 수도 없기 때문에 보안적으로 위험합니다.


그림1. 암묵적 인텐트가 어떻게 액티비티를 시작하는지를 표현합니다. 

[1] Activity A에서 인텐트에 액션값을 담아 startActivity() 호출시 인자로 넘깁니다.

[2] Android System이 모든 앱의 인텐트 필터에 대하여, 인텐트의 액션값에 해당하는 것이 있는지 검색합니다.

[3] 검색결과 Activity B를 찾았다면, 시스템은 액티비티 실행을 위해 onCreate() 메소드 호출시 해당 인텐트를 넘겨줍니다.


인텐트 만들기 (Building an Intent)


인텐트 객체는 실행하고자 하는 컴포넌트의 이름이나 카테고리 정보를 전달하는 역할을 하며, 응답하는 컴포넌트에게 액션값과 필요한 데이터들을 전달하기도 합니다.

인텐트가 담고 있는 주요한 정보들은 아래와 같습니다.


컴포넌트 이름 (Component name)

실행할 컴포넌트의 이름입니다.

이것은 명시적 인텐트에 필요한 것으로서, 그 이름에 해당하는 컴포넌트를 실행합니다. 암묵적 인텐트에서는 컴포넌트 이름 대신에 액션(action), 데이터(data), 카테고리(category) 정보를 담고 있습니다(액션은 필수, 데이터와 카테고리는 선택). 보통 앱 내의 컴포넌트를 실행할 때는 컴포넌트 이름을 이용한 명시적 인텐트를 사용합니다. 

주의사항: 서비스는 항상 명시적 인텐트를 사용해야 합니다. 만약 암묵적 인텐트를 사용한다면, 인텐트에 응답하는 서비스가 무엇인지 확신할 수 없고, 사용자들도 어떤 서비스가 실행되는지 볼 수 없기 때문입니다.

컴포넌트 이름은, 패키지명과 클래스명(예를 들면, com.example.ExampleActivity)을 담고 있는 ComponentName 객체로 인텐트에 추가할 수 있으며, setComponent() 메소드를 사용합니다. 그 외에도 인텐트의 setClass()setClassName() 메소드나 생성자에 클래스 정보를 인자로 넘겨서 실행할 컴포넌트를 명시할 수 있습니다.


액션 (Action)

실행하고자 하는 액션을 나타내는 문자열입니다. (예를 들면, view나 pick)

알림(broadcast) 인텐트의 경우, 액션은 리시버가 어떤 일을 할 것인지를 나타냅니다. 액션은 암묵적 인텐트의 주요한 값이 되며, 인텐트는 그 액션에 따라 필요한 데이터 등을 담을 수 있습니다.

내 앱에서 임의로 액션을 추가하여 사용할 수도 있지만, 보통은 Intent 클래스나 다른 프레임웍에 이미 정의되어 있는 값들을 사용하는 것이 좋습니다.

Intent.ACTION_VIEW

실제값은 "android.intent.action.VIEW" 입니다. 사진을 갤러리 앱에서 보거나 어떤 주소에 대한 위치를 지도 앱에서 볼 때와 같이, 어떤 정보를 가지고 있고 그것을 사용자에게 보여주고자 할 때 사용하기 위해 정의된 값입니다.

Intent.ACTION_SEND

실제값은 "android.intent.action.SEND" 입니다. "공유하기" 기능을 구현할 때 사용하는 이 액션값은, 이메일 앱이나 소셜 공유 앱과 같은 다른 앱을 통해 데이터를 공유하고자 할 때 사용하기 위해 정의된 값입니다.

미리 정의된 일반적인 액션값들은 Intent 클래스의 레퍼런스 정보에서 확인할 수 있으며, 그 외에 안드로이드 프레임웍의 다른 부분에서 정의된 액션값들은 그와 관련된 클래스 정보에서 확인할 수 있습니다. 예를 들어, 설정 앱과 관련된 액션값들은 Settings 클래스에서 확인할 수 있습니다.

액션값은 인텐트의 setAction() 메소드 또는 생성자에 인자로 넘겨 설정할 수 있습니다.

만약 내 앱의 자체적인 액션값을 만든다면, 아래와 같이 앞부분에 패키지명을 넣어 주시기 바랍니다.

static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";


데이터 (Data)

액션에 관련된 데이터의 URI(Uri 클래스의 객체)와 그 데이터에 대한 MIME 타입을 말합니다. 데이터는 보통 액션값과 관련되어 있습니다. 예를 들어, 액션값이 ACTION_EDIT이라면, 데이터는 수정할 문서의 URI를 포함하고 있어야 합니다. 

인텐트를 만들 때, 데이터의 URI와 함께 MIME 타입을 지정해야 하는 경우도 있습니다. 예를 들어 이미지 뷰어 액티비티는 동영상 파일을 재생할 필요가 없는데, 이미지나 동영상의 URI가 비슷하여 구분하기 어려운 경우가 있을 수 있습니다. 그래서 안드로이드는 MIME 타입을 이용하여 더 적합한 컴포넌트를 찾습니다. 반면에 MIME 타입을 URI로부터 추론할 수 있는 경우도 있습니다. URI가 content: 로 시작하면, 디바이스의 로컬 저장소에 저장된 데이터이고 컨텐트 프로바이더를 통해 제공된 데이터라는 의미이며, 이때는 프로바이더가 시스템에게 MIME 타입을 알려줍니다.

인텐트 객체에 URI를 담기 위해서는 setData() 메소드를 호출하고, MIME 타입을 담기 위해서는 setType()을 호출하며, 둘다 담기 위해서는 setDataAndType()을 호출합니다.

주의사항: URI와 MIME 타입 모두 셋팅하는 경우에는, setData()와 setType()을 사용하면 안됩니다. setData()는 MIME 타입을 null로 만들고, setType()은 URI를 null로 만들기 때문입니다. 따라서 둘 모두 셋팅하는 경우에는 setDataAndType()을 사용해야만 합니다.


카테고리 (Category)

암묵적 인텐트로 찾고 있는 컴포넌트에 대한 추가적인 정보입니다. 인텐트는 카테고리를 1개 이상 담고 있을 수 있지만, 대부분의 경우 카테고리를 필요로 하지는 않습니다.

일반적으로 접하게 되는 카테고리 두 가지는 아래와 같습니다.

Intent.CATEGORY_BROWSABLE

실행될 액티비티가, 링크로 연결된 컨텐츠(웹문서, 이미지, 이메일 메시지)를 보여줄 수 있는 웹 브라우저 기능을 포함하고 있다는 것을 의미합니다.

Intent.CATEGORY_LAUNCHER

실행될 액티비티가 태스크의 첫 액티비티이며, 런처 화면의 앱 목록에 보여진다는 것을 의미합니다.

카테고리 전체 목록은 Intent 클래스의 레퍼런스 문서에서 확인하실 수 있습니다.

인텐트에 카테고리 정보를 담기 위해서는 addCategory() 메소드를 사용합니다.


위에서 설명한 속성들(컴포넌트 이름, 액션, 데이터, 카테고리)은 인텐트의 성격을 대표하는 값으로서, 시스템이 컴포넌트를 찾을 때 사용됩니다.

하지만 인텐트는, 컴포넌트 찾는 일과 상관 없이 부가적인 정보를 전달할 수 있으며, 아래 두 가지 속성을 지원합니다.


엑스트라 정보 (Extras)

요청된 액션을 수행하는데 필요한 부가적인 정보이며, 키와 값의 조합으로 구성되어 있습니다(key-value pairs). 일부 액션이 특정 종류의 URI를 필요로 하는 것과 마찬가지로, 일부 액션은 특정 엑스트라 정보를 필요로 합니다. 

엑스트라 정보는 인텐트의 다양하게 오버로딩된 putExtra() 메소드에 키와 값을 인자로 넘겨서 셋팅할 수 있습니다. 또한 Bundle 객체를 만들어서 putExtras() 메소드에 넘길 수도 있습니다.

예를 들어 "새 이메일 쓰기" 액티비티를 실행하기 위해서, 인텐트의 액션값은 ACTION_SEND이고, 수신자가 미리 채워져 있도록 하기 위해 putExtra(Intent.EXTRA_EMAIL, "수신자 이메일")을 호출하며, 제목이 미리 채워져 있도록 하기 위해 putExtra(Intent.EXTRA_SUBJECT, "이메일 제목")을 호출합니다.

엑스트라 정보의 키 값을 표준화하기 위해, Intent 클래스에는 상당히 많은 EXTRA_* 상수가 정의되어 있습니다. 만약 내 앱 자체적으로 엑스트라 키를 정의해서 사용해야 한다면, 아래와 같이 앞부분에 패키지명을 넣어야 미리 정의된 값들과 충돌되는 일이 없을 것입니다.

static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";


플래그 (Flags)

플래그는 인텐트의 메타데이터 역할을 합니다. 플래그는 액티비티를 어떻게 실행할 것인지(예를 들어, 어떤 태스크(task)에 포함될 것인지)와 실행된 후에 어떻게 처리할 것인지(예를 들어, 최근 액티비티 목록에 포함을 시킬지 말지 여부)를 지시합니다. 

자세한 내용은 Intent 클래스 문서의 setFlags() 메소드 부분에서 학습할 수 있습니다.


명시적 인텐트의 예제


명시적 인텐트는 내 앱의 액티비티나 서비스와 같은 컴포넌트를 실행하기 위한 수단 중 하나로, 인텐트 객체에 컴포넌트 이름을 명시적으로 지정하여 만듭니다. 컴포넌트 이름 이외의 다른 속성들은 없어도 됩니다.

예를 들어, 내 앱에서 웹으로부터 파일을 다운로드 받는 DownloadService 를 만들었다면, 아래 코드와 같이 서비스를 실행할 수 있습니다.

// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent
.setData(Uri.parse(fileUrl));
startService
(downloadIntent);

Intent(Context, Class) 생성자는 앱의 Context와 실행할 컴포넌트의 Class 객체를 받습니다. 따라서 해당 생성자로 만들어진 인텐트는 명시적으로 DownloadService를 실행할 수 있습니다.

서비스를 만들고 실행하는 것에 대한 자세한 내용은 서비스에 대하여에서 학습하실 수 있습니다.


암묵적 인텐트의 예제


암묵적 인텐트에는, 디바이스의 모든 앱 중에서 원하는 앱을 실행하기 위해 액션값을 지정합니다. 어떤 기능에 대하여 내 앱 안에는 그 기능을 수행할 수 있는 컴포넌트가 없고 다른 앱에는 있을 때 암묵적 인텐트를 사용하여 다른 앱의 컴포넌트를 실행할 수 있습니다. 해당 기능을 수행할 수 있는 컴포넌트가 두 개 이상이라면 사용자가 선택 다이얼로그를 통해 선택할 수 있습니다.

예를 들어, 다른 사람에게 컨텐츠를 공유하고 싶다면, 인텐트에 ACTION_SEND를 설정하고 엑스트라 정보로 공유할 컨텐츠를 추가한 후 startActivity() 호출시 인자로 넘겨주면, 컨텐츠 공유가 가능한 앱들이 선택 다이얼로그로 보여지며 사용자가 그 중 하나를 선택할 수 있습니다.

주의사항: 암묵적 인텐트로 startActivity()를 호출했을 때, 그 인텐트로 찾고자 하는 컴포넌트가 하나도 없을 수도 있습니다. 이 경우, 호출은 실패하고 앱은 종료될 것입니다. 그래서 이러한 경우를 예방하기 위해서는 인텐트 객체의 resolveActivity() 메소드를 이용할 수 있습니다. resolveActivity()의 호출결과가 null이 아니라면 실행될 수 있는 컴포넌트가 있다는 의미이므로 startActivity()가 정상적으로 수행될 것이고, 반대로 null이라면 실행될 컴포넌트가 없으므로 startActivity()를 호출하지 않도록 해당 UI를 비활성 처리해야 할 것입니다.

// Create the text message with a string
Intent sendIntent = new Intent();
sendIntent
.setAction(Intent.ACTION_SEND);
sendIntent
.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent
.setType(HTTP.PLAIN_TEXT_TYPE); // "text/plain" MIME type

// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity
(sendIntent);
}

메모: 위 예제의 경우, URI는 사용되지 않지만 엑스트라 정보의 타입을 지정하기 위해 setType()을 호출합니다.

startActivity()가 호출되면, 시스템은 설치된 모든 앱 중에서 인텐트에 부합하는 앱들을 찾습니다 (위의 예제에서 인텐트의 액션값은 ACTION_SEND이고, 데이터 타입은 text/plain입니다). 만약 인텐트에 부합하는 앱이 한 개라면 그 앱을 바로 실행하고, 두 개 이상이라면 사용자에게 선택 다이얼로그를 보여줍니다. 여기서는 앱을 찾는다고 표현했으나 실제로는 컴포넌트를 찾는 것이며, 선택 다이얼로그에서는 해당 컴포넌트를 포함하고 있는 앱을 보여줍니다. 


앱 선택 다이얼로그 띄우기


암묵적 인텐트에 부합하는 앱이 두 개 이상인 경우, 사용자는 어떤 앱을 실행할 지 선택할 수 있고, 선택한 앱을 기본값으로 사용할 수도 있습니다. 이것은 사용자가 어떤 액션에 대해서는 항상 같은 앱이 실행되기를 바랄 때 유용합니다. 예를 들면, 만약 브라우저 앱이 여러개 있다면 웹페이지 링크를 클릭했을 때 선택 다이얼로그가 뜰 것입니다. 이때 하단에 체크박스(이 작업에 대해 기본값으로 사용)를 체크하고 크롬을 선택하면, 다음부터는 바로 크롬이 실행됩니다.

하지만 사용자가 상황에 따라 다른 앱을 선택할 수 있어야 하는 경우라면, 항상 사용자가 선택 다이얼로그를 볼 수 있도록 명시적으로 다이얼로그를 출력해 줘야 합니다. 예를 들어, 아래 코드 예제에서와 같이 createChooser()로 인텐트를 만들어서 startActivity() 하면 [그림2]와 같이 선택 다이얼로그가 뜨는데, 여기에는 하단의 기본값 설정 관련 체크박스가 없습니다.

Intent intent = new Intent(Intent.ACTION_SEND);
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show chooser
Intent sendIntent = Intent.createChooser(intent, title);

// Verify the intent will resolve to at least one activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity
(sendIntent);
}

createChooser() 호출시 인자로 넘겨주는 title은 선택 다이얼로그의 상단에 출력되는 제목입니다.


그림2. 선택 다이얼로그


암묵적 인텐트 받기 


암묵적 인텐트를 받을 수 있는 컴포넌트가 되려면, 매니페스트 파일의 컴포넌트 요소 안에 하나 이상의 인텐트 필터 요소(<intent-filter>)를 선언해야 합니다. 각 인텐트 필터는 인텐트의 액션, 데이터, 카테고리에 대응되는 요소들을 포함합니다. 시스템은 컴포넌트의 인텐트 필터들을 확인하여 인텐트에 부합하는 인텐트 필터가 있을 경우에만 그 인텐트를 컴포넌트에게 전달합니다. 

메모: 명시적 인텐트는 인텐트 필터와 상관 없이 대상 컴포넌트에게 전달됩니다.

앱의 컴포넌트는 어떤 일을 할 것인가에 따라 구분된 인텐트 필터를 선언해야 합니다. 예를 들어, 갤러리 앱의 액티비티는 아마도 두 개의 인텐트 필터를 가질 것인데, 하나는 이미지를 보는 것에 대한 필터일 것이고 다른 하나는 이미지를 편집하는 것에 대한 필터일 것입니다. 액티비티가 시작되면, 그 액티비티는 넘겨받은 인텐트의 액션값 등을 확인하여, 이미지를 그냥 보여줄 것인지 아니면 편집화면을 보여줄 것인지를 결정합니다.

인텐트 필터는 매니페스트 파일에서 <activity>와 같은 컴포넌트 요소의 자식 요소인 <intent-filter>로 선언되고, <intent-filter> 요소에는 다시 자식 요소로 아래 세가지 요소들이 선언됩니다. 

<action>
받아들일 인텐트의 액션값을 name 속성에 선언합니다. 이때 주의할 점은, 자바코드의 Intent.ACTION_SEND와 같은 변수명이 아니라 "android.intent.action.SEND"와 같은 실제 문자열을 넣어야 한다는 것입니다.

<data>
받아들일 인텐트의 데이터 타입을 한 개 이상 선언할 수 있습니다. 여기에는 URI를 구성하는 부분들(scheme, host, port, path 등)과 MIME 타입이 선언됩니다.

<category>
받아들일 인텐트의 카테고리를 name 속성에 선업합니다. 이때 주의할 점은 변수명이 아니라 실제 문자열을 넣어야 한다는 것입니다.

메모: 암묵적 인텐트를 받기 위해서는 인텐트 필터에 "android.intent.category.DEFAULT" 가 반드시 포함되어 있어야 합니다. startActivity()startActivityForResult() 메소드는 모든 인텐트에 Intent.CATEGORY_DEFAULT가 선언되어 있다고 전제하고 동작하기 때문에, 인텐트 필터에 CATEGORY_DEFAULT가 선언되어 있지 않으면, 인텐트에 부합하는 컴포넌트가 되지 못합니다.

아래 예제는, 액션이 ACTION_SEND이고 데이터 타입이 텍스트인 인텐트를 받을 수 있는 액티비티의 선언입니다.

<activity android:name="ShareActivity">
   
<intent-filter>
       
<action android:name="android.intent.action.SEND"/>
       
<category android:name="android.intent.category.DEFAULT"/>
       
<data android:mimeType="text/plain"/>
   
</intent-filter>
</activity>

하나의 인텐트 필터가 <action>, <data>, <category>를 각각 두 개 이상씩 포함할 수도 있습니다. 이러한 경우에는 모든 조합에 대하여 한 가지만 조건에 맞아도 인텐트가 필터를 통과할 수 있습니다.

만약 여러 종류의 인텐트를 받고 싶으나 특정 조합들에 대해서만 제한하고 싶다면, 각 조합들에 해당하는 인텐트 필터들을 추가하면 됩니다.

암묵적 인텐트는 액션, 데이터, 카테고리에 대하여 인텐트 필터의 그것들과 각각 테스트를 하며, 모든 테스트가 통과되어야만 컴포넌트에게 인텐트가 전달되며, 하나라도 통과하지 못하면 인텐트가 전달되지 않습니다. 하지만 인텐트 필터가 여러개일 경우, 어떤 인텐트 필터에 대하여 테스트가 실패하면 다음 인텐트 필터에 대하여 테스트하게 되고, 테스트가 통과되는 인텐트 필터를 찾을 때까지 모두 확인합니다. 시스템이 인텐트를 처리하는 방법에 대한 자세한 내용은 아래의 인텐트 처리하기에서 학습할 수 있습니다.

주의사항: 우연히 다른 앱의 서비스가 실행되는 일이 없도록 하기 위해, 내 앱의 서비스를 실행할 때는 명시적 인텐트를 사용하고, 서비스에는 인텐트 필터를 선언하지 않도록 합니다.

메모: 액티비티를 선언하거나 액티비티에 인텐트 필터를 선언하는 것은 매니페스트 파일에서만 가능합니다. 하지만 브로드캐스트 리시버는 소스코드에서 registerReceiver()로 등록할 수 있고(이때 인텐트 필터를 설정합니다), unregisterReceiver()로 해제할 수 있습니다. 매니페스트에 선언된 리시버는 시스템이 정해놓은 시점에 등록 및 해제가 되지만, 소스코드에서는 개발자가 원하는 시점에 리시버를 등록 및 해제할 수 있습니다.

컴포넌트로의 접근 제한 방법: 컴포넌트에 인텐트 필터를 선언하는 것은 암묵적 인텐트의 종류를 제한하기는 하지만 다른 앱에 의해 실행될 수 있기 때문에 보안적으로 안전한 방법은 아닙니다. 또한 다른 앱에서 내 앱의 컴포넌트 이름을 알고 있다면 명시적 인텐트를 통해 해당 컴포넌트를 실행할 수도 있습니다. 그래서 다른 앱으로부터 내 앱의 컴포넌트를 안전하게 지키기 위해서는 exported 속성을 "false"로 설정해야 합니다. 


인텐트 필터의 예제

아래 예제는 인텐트 필터의 동작에 대한 이해를 돕기 위해 준비한 어떤 소셜 공유 앱의 매니페스트 파일 일부입니다.

<activity android:name="MainActivity">
   
<!-- This activity is the main entry, should appear in app launcher -->
   
<intent-filter>
       
<action android:name="android.intent.action.MAIN" />
       
<category android:name="android.intent.category.LAUNCHER" />
   
</intent-filter>
</activity>

<activity android:name="ShareActivity">
   
<!-- This activity handles "SEND" actions with text data -->
   
<intent-filter>
       
<action android:name="android.intent.action.SEND"/>
       
<category android:name="android.intent.category.DEFAULT"/>
       
<data android:mimeType="text/plain"/>
   
</intent-filter>
   
<!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
   
<intent-filter>
       
<action android:name="android.intent.action.SEND"/>
       
<action android:name="android.intent.action.SEND_MULTIPLE"/>
       
<category android:name="android.intent.category.DEFAULT"/>
       
<data android:mimeType="application/vnd.google.panorama360+jpg"/>
       
<data android:mimeType="image/*"/>
       
<data android:mimeType="video/*"/>
   
</intent-filter>
</activity>

첫번째 액티비티인 MainActivity는 앱의 진입점으로서 시작화면이고, 사용자가 런처화면에서 아이콘을 눌러 들어오게 되는 액티비티입니다.

  • "android.intent.action.MAIN"(Intent.ACTION_MAIN)은 이 액티비티가 시작화면이고, 부가적인 데이터는 필요로 하지 않는다는 것을 의미합니다.
  • "android.intent.category.LAUNCHER" (Intent.CATEGORY_LAUNCHER)는 이 액티비티의 아이콘이 안드로이드 런처화면에 보여진다는 것을 의미합니다. 만약 <activity> 요소에 icon 속성이 정의되어 있지 않다면, <application> 요소의 icon 속성이 사용됩니다.

런처화면에 아이콘이 보여지기 위해서는 인텐트 필터에 위의 두가지가 모두 있어야 합니다.

두번째 액티비티인 ShareActivity는 텍스트나 미디어 컨텐츠를 공유하기 위한 액티비티입니다. 이것은 MainActivity로부터 실행될 수도 있고, 다른 앱으로부터 (두 필터 중 하나와 부합하는) 암묵적 인텐트를 통해 실행될 수도 있습니다.

메모: MIME 타입 중에 application/vnd.google.panorama360+jpg는 파노라마 사진(360도 촬영 사진)을 나타내는 특별한 데이터 타입이며, 파노라마 사진은 구글 파노라마 API (Google panorama APIs)를 사용하여 만들 수 있습니다.


펜딩 인텐트 사용하기


펜딩 인텐트(PendingIntent) 객체는 인텐트 객체를 포함하는 객체이며, 주요 목적은 다른 앱이 펜딩 인텐트에 포함된 인텐트를 마치 자신의 인텐트인양 권한을 부여하는 것입니다. 

펜딩 인텐트는 주로 아래와 같은 상황에서 사용됩니다.

  • 노티(Notification)를 노티피케이션바에 올린 경우, 노티를 눌렀을 때 시스템의 NotificationManager가 펜딩 인텐트에 포함된 인텐트를 사용하여 컴포넌트를 실행합니다.
  • 앱 위젯(App Widget)을 이용하는 경우, 위젯의 버튼 등을 눌렀을 때, 런처의 홈스크린앱이 펜딩 인텐트에 포함된 인텐트를 사용하여 컴포넌트를 실행합니다.
  • 시스템 알람 기능을 이용하는 경우, 미래의 특정 시점이 되었을 때 AlarmManager가 펜딩 인텐트에 포함된 인텐트를 사용하여 컴포넌트를 실행합니다.

각 인텐트는 액티비티, 서비스, 브로드캐스트 리시버 중 하나를 실행하도록 설계되어 있기 때문에, 펜딩 인텐트도 마찬가지로 한가지 목적을 가지고 만들어져야 합니다. 펜딩 인텐트를 사용하는 경우에는 startActivity()와 같은 메소드를 사용할 수 없습니다. 대신에, 아래 소개되는 펜딩 인텐트의 생성 메소드들 중 하나를 사용함으로써 실행할 컴포넌트를 지정해야 합니다.

  • PendingIntent.getActivity()는, 인텐트가 실행할 컴포넌트가 액티비티인 경우에 사용하며, 펜딩 인텐트 객체를 반환해 줍니다.
  • PendingIntent.getService()는, 인텐트가 실행할 컴포넌트가 서비스인 경우에 사용합니다.
  • PendingIntent.getBroadcast()는, 인텐트가 실행할 컴포넌트가 브로드캐스트 리시버인 경우에 사용합니다.

다른 앱으로부터 펜딩 인텐트를 받기만 하는 것이 아니라 만들기도 해야한다면 위의 세가지 메소드가 필요할 것입니다.

각 메소드의 인자로는, 앱의 Context와 펜딩 인텐트 안에 포함할 인텐트, 그리고 인텐트가 어떤 방식으로 사용될 지를 지정하는 플래그를 받습니다. 

펜딩 인텐트를 사용하는 방법에 대한 더 자세한 내용은 노티피케이션(Notifications)과 앱 위젯(App Widget)에서 학습하실 수 있습니다.


인텐트 처리하기


시스템은, 암묵적 인텐트를 받아 액티비티를 실행하려고 할때, 그 인텐트와 설치된 앱들의 인텐트 필터들을 아래 나열된 세가지 측면에서 비교하여 조건에 맞는 액티비티를 찾습니다.

  • 인텐트의 액션값
  • 인텐트의 데이터 (URI와 데이터 타입)
  • 인텐트의 카테고리

아래 내용에서는 원하는 인텐트를 받기 위해 매니페스트 파일에 인텐트 필터를 어떻게 선언할지에 대하여 학습합니다.


액션 테스트 (Action test)

어떤 액션값을 받을지 지정하기 위해, 인텐트 필터는 아래와 같이 0개 이상의 <action> 요소를 선언할 수 있습니다.

<intent-filter>
   
<action android:name="android.intent.action.EDIT" />
   
<action android:name="android.intent.action.VIEW" />
    ...
</intent-filter>

위의 필터를 통과하기 위해서는 인텐트가 필터에 선언된 두 액션 중 하나를 가지고 있어야 합니다.

만약 필터에 액션이 하나도 선언되지 않았다면, 그 필터를 통과할 수 있는 인텐트는 없을 것입니다.


카테고리 테스트 (Category test)

어떤 카테고리를 받을지 지정하기 위해, 인텐트 필터는 아래와 같이 0개 이상의 <category> 요소를 선언할 수 있습니다.

<intent-filter>
   
<category android:name="android.intent.category.DEFAULT" />
   
<category android:name="android.intent.category.BROWSABLE" />
    ...
</intent-filter>

인텐트가 카테고리 테스트를 통과하기 위해서는, 인텐트가 가지고 있는 카테고리가 모두 필터에 선언된 카테고리들 안에 포함되어야 합니다. 그래서 필터 안에 카테고리들을 더 선언하더라도 기존에 통과하던 인텐트는 여전히 통과할 수 있습니다. 따라서, 카테고리를 가지고 있지 않은 인텐트는 필터에 어떤 카테고리가 선언되었느냐에 상관없이 무조건 통과할 수 있습니다.

메모: 안드로이드는 startActivity()startActivityForResult()를 할때 자동적으로 인텐트에 기본 카테고리(Intent.CATEGORY_DEFAULT, "android.intent.category.DEFAULT")를 적용합니다. 따라서 암묵적 인텐트로 액티비티를 실행하려고 한다면, 그 액티비티의 인텐트 필터에 기본 카테고리가 선언되어 있어야 합니다. 


데이터 테스트 (Data test)

어떤 종류의 데이터를 받을지 지정하기 위해, 인텐트 필터는 아래와 같이 0개 이상의 <data> 요소를 선언할 수 있습니다.

<intent-filter>
   
<data android:mimeType="video/mpeg" android:scheme="http" ... />
   
<data android:mimeType="audio/mpeg" android:scheme="http" ... />
    ...
</intent-filter>

<data>에는 URI를 구성하는 부분들(scheme, host, port, path)과 MIME 타입이 선언됩니다.

<scheme>://<host>:<port>/<path>

와 같은 형태가 되며 예를 들면 아래와 같습니다.

content://com.example.project:200/folder/subfolder/etc

이 URI에서 scheme은 content, host는 com.example.project, port는 200, path는 folder/subfolder/etc 입니다.

<data>의 각 속성들은 옵셔널(optional)하지만, 서로간에 의존성이 있습니다.

  • scheme이 없으면, host는 무시됩니다.
  • host가 없으면, port는 무시됩니다.
  • scheme과 host가 둘다 없으면, path는 무시됩니다.

인텐트가 가지고 있는 URI를 인텐트 필터와 비교할 때는, <data>에 선언된 속성들만을 비교합니다. 예를 들면,

  • <data>에 scheme만 선언되어 있다면, 선언된 scheme으로 시작하는 URI들은 모두 테스트를 통과합니다.
  • <data>에 scheme과 authority(host+port부분, 실제로 port는 선언되는 경우가 별로 없습니다)가 선언되어 있다면, 선언된 값들로 구성된 URI들은 path와 상관없이 테스트를 통과합니다.
  • <data>에 scheme, authority, path가 선언되어 있다면, 선언된 값들로 구성된 URI만이 테스트를 통과합니다.

메모: path 선언시에는 와일드카드 문자 *를 포함하여 융통성 있게 선언할 수 있습니다.

데이터 테스트는 인텐트의 URI와 MIME 타입을, 인텐트 필터에 선언된 URI와 MIME 타입과 비교합니다. 그 규칙은 아래와 같습니다.

  1. URI와 MIME 타입이 모두 없는 인텐트는, URI와 MIME 타입이 모두 선언되지 않은 인텐트 필터에서 테스트를 통과합니다.
  2. URI는 있으나 MIME 타입이 없는 인텐트는, 마찬가지로 URI는 선언되어 있고 MIME 타입이 선언되지 않은 필터에서 테스트를 통과할 수 있습니다.
  3. MIME 타입은 있으나 URI가 없는 인텐트는, MIME 타입이 선언되어 있고 URI가 선언되지 않은 필터에서 테스트를 통과할 수 있습니다.
  4. URI와 MIME 타입이 모두 있는 인텐트는, 일단 필터에 일치하는 MIME 타입이 선언되어 있어야 하고 다음 조건을 만족해야 테스트를 통과할 수 있습니다. 인텐트 필터에 URI가 선언되어 있다면 인텐트의 그것과 일치해야 하며, 필터에 URI가 선언되어 있지 않다면 URI가 content:file: 로 시작하는 값이어야 합니다. 바꿔 말하면, 인텐트 필터의 <data>에 MIME 타입만 선언하는 것은, content:file: 로 시작하는 URI만 받겠다는 것을 의미합니다.

위의 마지막 규칙 D는, MIME 타입만 선언된 필터를 가지고 있는 컴포넌트가 파일 시스템이나 컨텐트 프로바이더를 통해 로컬 파일을 처리할 수 있다는 것을 알려줍니다. 따라서 이러한 경우는 데이터 타입들만 선언하고, 명시적으로 scheme에 contentfile을 선언할 필요는 없습니다. 이것은 전형적인 사용 방식으로서, 아래 예제는 시스템에게, 이 필터를 포함한 컴포넌트는 컨텐트 프로바이더로부터 이미지를 얻어와 보여주는 역할을 한다라고 말해줍니다.

<intent-filter>
   
<data android:mimeType="image/*" />
    ...
</intent-filter>

많은 경우의 데이터들이 컨텐트 프로바이더로부터 오기 때문에, 인텐트 필터의 <data> 요소에 URI 관련 선언이 없는 경우가 일반적입니다.

위와 다르게, <data>에 scheme과 mimeType이 선언된 경우가 있습니다. 아래 예제는 시스템에게, 이 필터를 포함한 컴포넌트가 네트웍을 통해 동영상 데이터를 받아오는 역할을 한다라고 말해줍니다.

<intent-filter>
   
<data android:scheme="http" android:mimeType="video/*" />
    ...
</intent-filter>


인텐트 매칭 (Intent matching)

인텐트는 두 가지 목적에서 인텐트 필터와 비교합니다. 실행할 컴포넌트를 찾기 위해서이기도 하고, 어떤 조건에 맞는 컴포넌트 목록을 얻어오기 위해서이기도 합니다. 예를 들어 홈스크린앱은, 설치된 모든 앱에서 인텐트 필터에 ACTION_MAINCATEGORY_LAUNCHER가 선언된 액티비티들을 찾아와 화면에 그 목록을 보여주는 것이 주된 역할입니다.

내 앱에서도 비슷한 방식으로 인텐트 매칭을 사용할 수 있습니다. PackageManagerquery...() 형태의 메소드들과 resolve...() 형태의 메소드들을 지원합니다. query...()은 인텐트를 받을 수 있는 모든 컴포넌트의 목록을 리턴해주고, resolve...()은 가장 적절한 컴포넌트를 리턴해 줍니다. 예를 들어, queryIntentActivities()는 인텐트를 받을 수 있는 모든 액티비티들을 리턴해주고, queryIntentServices()는 서비스들을 리턴해주며, queryBroadcastReceivers()는 브로드캐스트 리시버들을 리턴해줍니다. 위의 세 메소드는 실제로 컴포넌트를 실행하거나 하지는 않고, 단지 목록을 리턴해주기만 합니다.


Posted by 개발자 김태우
,