API GUIDE/2. App Components
2014. 2. 25. 22:59

 

이전 글인 인텐트와 인텐트 필터(Intents and Intent Filters)에 이어지는 글입니다.


언제나 그렇듯 오역, 오타, 깨진 링크가 있으면 제보 부탁드립니다.


 

저작권 표시 : 

 

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.

 

현재 보시는 페이지는 안드로이드 오픈 소스 프로젝트에 의해 작성되고 공유된 작업물의 수정/번역본이며 크리에이티브 커먼즈 저작자 표시 2.5 라이센스에 기술된 조건의 사용 근거를 따른 것입니다.

 

본 문서의 원본은 http://developer.android.com/guide/components/intents-filters.html이며 안드로이드 4.4 Kitkat 기준으로 설명되었습니다.

 

암시적 인텐트 수신하기



여러분의 앱이 암시적 인텐트를 다룰 수 있다는 것을 시스템에 알리려면 매니페스트 파일<intent-filter>엘리먼트를 사용하여 하나 이상의 인텐트 필터를 선언해야 한다. 각 인텐트 필터는 인텐트의 액션, 데이터, 카테고리에 기반하여 수용 가능한 인텐트의 타입을 지정한다. 인텐트 필터의 조건을 뚫고 통화할 수 있는 인텐트가 있다면 시스템은 암시적 인텐트를 여러분의 앱에 전달해 줄 것이다.


Note : 명시적 인텐트는 컴포넌트에 인텐트 필터가 있건 없건 상관없이 항상 지정된 컴포넌트에 배달된다.


앱 컴포넌트는 독립된 일 하나하나에 대해서 각각의 필터를 사용해야 한다. 예를 들어, 이미지 갤러리앱은 두 개의 필터를 갖게 될 것이다.; 하나의 필터는 이미지를 보여주기만 하고 나머지 하나는 이미지를 편집하는 필터로 말이다. 액티비티가 시작되면 Intent를 확인해서 Intent에 있는 정보에 대해 어떤 행동을 해야할 지 결정해야 한다.(편집 창을 보여줄지 말지를 결정해야 한다.)


Manifest 파일의 <intent-fillter>엘리먼트를 통해 선언된 인텐트 필터는 앱 컴포넌트(<activity>엘리먼트 같은) 안쪽에 네스트 되어야 한다. <intent-filter>안쪽에는 다음의 3개 엘리먼트 중 하나 이상을 사용하여 인텐트의 타입을 정해줘야 한다.


<action>

name속성에 수용하고자 하는 인텐트 액션을 선언한다. 값은 클래스 상수가 아닌 액션을 문자열로 써야한다.


<data>

URI데이터(scheme, host, port, path등)와 MIME타입의 다양한 점을 지정할 수 있도록 하나 이상은 속성을 사용하여 수용하고자 하는 데이터의 타입을 결정한다.


<category>

name속성을 사용하여 수용하고자 하는 인텐트 카테고리를 선언한다. 값은 클래스 상수가 아닌 카테고리를 문자열로 써야한다.


Note : 암시적 인텐트를 받기 위해서는 인텐트 필터에 CATEGORY_DEFAULT 카테고리를 반드시 포함시켜야 한다. startActivity()startActivityForResult()은 모든 인텐트가 CATEGORY_DEFAULT 카테고리를 선언했다고 여기기 때문이다. 만일 카테고리에 이 값을 선언하지 않았다면 암시적 인텐트는 받을 수 없다.


아래 예제는 ACTION_SEND의 액션과 텍스트 타입을 데이터를 갖는 인텐트를 수용하도록 인텐트 필터를 선언하는 법에 대한 것이다.


1
2
3
4
5
6
7
<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>엘리먼트 중 하나 이상을 포함하는 필터를 만드는 것이 좋다. 이렇게 하면 다룰 수 있는 인텐트를 필터에 선언된 엘리먼트들과 비교하여 그 조합된 조건에 맞도록 요구하는 컴포넌트가 되기 때문에 정확히 원하는 인텐트를 지정할 수 있게 된다.


특정 액션, 데이터, 카테고리 타입의 조합에 맞는 여러 종류의 인텐트를 다루도록 하고 싶다면 여러 개의 인텐트 필터를 선언해야 한다.


컴포넌트 액세스 제한하기(Restricting access to components)

인텐트 필터를 사용하는 것은 다른 앱이 여러분의 컴포넌트를 실행시키는 것을 제한하는 보안대책은 아니다. 인텐트 필터가 특정한 종류의 암시적 인텐트에 응답하도록 컴포넌트를 제한했다고 하더라도 개발자가 컴포넌트의 이름을 결정했다면 다른 앱이 명시적 인텐트를 사용하여 컴포넌트를 시작할 수 있는 잠재적 가능성은 있다. "오직 여러분의 앱에서만" 여러분의 컴포넌트를 실행시켜야 한다는 사실일 중요하다면 해당 컴포넌트의 exported 속성을 "false"로 설정해야 한다.


암시적 인텐트는 인텐트의 각각 세가지 엘리먼트를 필터와 비교하는 테스트를거치게 된다. 다시 말하자면 암시적 인텐트가 컴포넌트에 전달되려면 세 가지 테스트를 모두 통과해야만 하는 것이다. 각각의 엘리먼트를 테스트해서 하나라도 맞지 않는다면 안드로이드 시스템은 그 인텐트를 컴포넌트에 전달하지 않는다. 하지만 두스의 인텐트 필터를 갖고 있는 컴포넌트라면 또 다른 필터의 조건에 맞는 인텐트로 판단되어 인텐트가 전달될 수도 있다. 시스템이 어떻게 인텐트를 resolve하는지에 대한 추가 정보는 뒤에 나오는 Intent Resolution 섹션에서 제공된다.


Caution : 우연히 다른 앱이 Service를 실행시키는 일이 발생하지 않도록 하려면 여러분이 만든 서비스는 항상 명시적 인텐트를 사용하여 시작시키도록 서비스에 인텐트 필터를 선언하지 않도록 한다.


Note : 모든 액티비티의 인텐트 필터는 매니페스트 파일을 통해 선언되어야 한다. 하지만 브로드캐스트 리시버를 위한 필터는 registerReceiver() 메서드를 호출해서 동적으로 등록이 가능하다. 반대로 unregisterReceiver()를 통해 등록된 리시버를 해제시키는 것도 가능하다. 이런 메서드들을 사용하면 앱이 실행되는 동안 특정 브로드캐스트에 대해 일정 기간동안만 반응하도록 할 수가 있다.



필터 예제(Example filters)

인텐트 필터의 동작에 대한 이해를 돕기 위해 아래와 같은 예제를 준비했다. 이 예제는 소셜 공유앱의 매니페스트 파일을 일부 발췌한 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<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는 앱의 주 진입점이다. 즉, 사용자가 런치 아이콘으로 앱을 시작시킬 때 열리는 액티비티다.


  • ACTION_MAIN액션(리치크루 : 앞 필터 예제에서는 adnroid.intent.action.Main)은 앱의 주 진입점임을 알리며 다른 데이터는 필요로 하지 않는다.
  • CATEGORY_LAUNCHER(리치크루 : 앞 필터 예제에서는 android.intent.category.LAUNCHER)는 해당 액티비티의 아이콘이 시스템의 앱 런처에 추가되어 위치해야 함을 의미한다. 만일 <activity>엘리먼트에 icon으로 아이콘을 지정하지 않았다면 시스템은 <application>엘리머트에 지정된 아이콘을 대신 사용한다.

앱 럽처에 액티비티가 표시되도록 하기 위해서는 이 두 가지(ACTION_MAIN과 CATEGORY_LAUNCHER)가 함께 짝을 이루어야한다.


두 번째 액티비티인 ShareActivity는 텍스트와 미디어 컨텐츠의 공유가 가능하도록 하려고 한다. 사용자는 MainActivity에서 이 액티비티로 넘어오는 경우도 있겠지만 다른 앱을 사용하다가 발생하는 암시적 인텐트를 통해 MainActivity를 거치지 않고 바로 이 액티비티를 만날 수도 있다.


Note : application/vnd.google.panorama360+jpg라는 MIME 타입은 Google panorama API를 통해 다룰 수 있는 파노라마 사진의 특수한 데이터 타입이다.



펜딩 인텐트 사용하기(Using a Pending Intent)



PendingIntent 오브젝트는 Intent 오브젝트의 랩퍼 오브젝트다. PendingIntent의 주목적은 외부 어플리케이션이 Intent를 사용할 때 여러분의 앱이 동작하는 프로세스에서 실행되는 것처럼 사용권한을 부여하기 위해 사용된다.


펜딩 인텐트의 주용도는 다음과 같다. :

  • Notification에 맞는 액션을 사용자가 수행하려할 때 사용될 인텐트를 선언한다.(안드로이드 시스템의 NotificationManagerIntent를 실행한다.)
  • App Widget을 통해 사용자가 액션을 취할 때 사용될 인텐트를 선언한다.(홈 스크린앱은 Intent를 실행한다.)
  • 미래의 특정시간에 사용될 인텐트를 선언한다.(안드로이드 시스템의 AlarmManagerIntent를 실행한다.)


각 Intent 오브젝트는 특정타입의 앱 컴포넌트(액티비티, 서비스, 브로드캐스트 리시버)가 다룰 수 있도록 구성한다. 그래서 PendingIntent도 똑같이 특정 타입의 앱 컴포넌트에서 다뤄질 수 있도록 구성해야한다. 펜딩 인텐트를 이용할 때는 startActivity() 같은 호출을 하지 않는다. 대신 PendingIntent가 각 생성 메서드를 통해 생성될 때 의도하는 컴포넌트 타입을 선언해 주어야 한다. 각 생성 메서드는 다음과 같다.


다른 앱으로부터 펜딩 인텐트를 건네받지 않는 한 PenndingIntent를 생성하는 방법은 위의 메서드들을 사용하는 방법뿐이다.


각 메서드들은 파라메터로 (1) 현재 앱의 Context, (2) 래핑하려는 Intent, (3) 인텐트가 어떻게 사용되어야 하는지(예를 들어, 인텐트가 한 번 이상 사용될 수 있는지)를 나타내는 하나 이상의 플래그를 취한다.


펜딩 인텐트의 사용에 관한 더 많은 정보는 NotificationApp widgets API 가이드 같이 각 사용 케이스에 따른 문서에서 제공된다.


인텐트의 결정성(Intent Resolution)



시스템에서는 암시적 인텐트를 받아 액티비티를 실행시켜야 할 때 인텐트와 인텐트 필터를 다음 세가지 사항에 기초하여 비교함으로써 최적의 액티비티를 찾아낸다.

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


이후에 섹션에서는 인텐트가 어떤 인텐트 필터를 가진 컴포넌트와 어떻게 적절히 짝을 찾게 되는지 설명한다.


액션 테스트(Action test)

수용하고자 하는 인텐트의 액션을 지정하기 위한 것이 <action>엘리먼트다. 인텐트 필터 안에서 정의하게 되며 아예 선언을 안 할 수도 있고 하나 이상, 여러 개를 선언할 수도 있다. 예제는 다음과 같다.


1
2
3
4
5
<intent-filter>
    <action android:name="android.intent.action.EDIT" />
    <action android:name="android.intent.action.VIEW" />
    ...
</intent-filter>

이 필터를 통과하려면 Intent 내의 액션은 필터에 명기된 액션 중 하나와 일치해야만 한다.


만일 필터가 어떤 액션도 포함하지 않으면 인텐트와 매칭 시킬 게 없다. 때문에 모든 인텐트가 테스트를 통과하지 못한다. 반면에 액션을 지정하지 않은 Intent가 있다면 그 인텐트는 테스트를 통과할 수 있게 된다.(필터에 적어도 하나의 액션이 포함되어 있다면)


카테고리 테스트(Category test)

수용하려는 인텐트의 카테고리를 지정하려면 <category>엘리먼트를 사용하며 이 엘리먼트는 사용하지 않거나 하나 이상을 선언할 수 있다. 예제는 다음과 같다.


1
2
3
4
5
<intent-filter>
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    ...
</intent-filter>

인텐트가 카테고리 테스트를 통과하기 위해서는 인텐트의 모든 카테고리가 필터의 카테고리와 일치해야만 한다. 인텐트 필터 내의 카테고리가 인텐트에 지정된 것 이상으로 선언되어 있다면 그 인텐트는 테스트를 통과하게 되지만 그 반대의 경우는 통과할 수 없다. 그래서 인텐트에 지정된 카테고리가 없으면 필터에 선언된 카테고리가 있건 없건 무조건 테스트를 통과하게 된다.


Note : 안드로이드는 startActivity()startActivityForResult()를 통해 전달되는 모든 암시적 인텐트에 자동으로 CATEGORY_DEFAULT를 추가시킨다. 그렇게 때문에 암시적 인텐트를 받고자 하는 액티비티는 인텐트 필터에 "android.intent.category.DEFAULT"를 반드시 포함시켜야 한다.(앞의 <intent_filter>예제처럼 말이다.)


데이터 테스트(Data test)

수용하려는 인텐트 데이터를 지정하려면 <data>엘리먼트를 사용해야 하며 이 엘리먼트는 사용하지 않거나 하나 이상을 선언할 수 있다.


1
2
3
4
5
<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
    ...
</intent-filter>

<data>엘리먼트는 URI 스트럭쳐와 데이터 타입(MIME 미디어 타입)을 지정할 수 있다. URI를 표현할 때는 각각의 부분을 나타내는 별도의 속성들이 있다.(schem, host, port, path)


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


예를 들어, 다음과 같은 URI가 있다면


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


이 URI에서 스킴(scheme)은 content, 호스트(host)는 com.example.project, 포트(port)는 200, 경로(path)는 folder/subfolder/etc가 된다.


이러한 각각의 속성들은 <data>엘리먼트 내에서 선택적으로 사용될 수는 있지만 선형적 의존성을 갖고 있다.

  • 스키마가 지정되지 않으면 호스트는 무시된다.
  • 홋트가 지정되지 않으면 포트는 무시된다.
  • 스킴과 호스트가 모두 지정되지 않으면 경로는 무시된다.


인텐트의 URI와 필터의 URI를 비교할 때는 다음과 같이 필터 URI의 부분, 부분만을 비교한다.

  • 필터에 스킴만 지정되었으면 그 스킴을 갖는 모든 URI는 필터와 일치하는 것으로 본다.
  • 필터에 경로는 없이 스킴과 권한만 지정되었으면 경로와 상관없이 스킴과 권한이 같은 URI는 필터를 통과하게 된다.
  • 필터에 스킴, 권한, 경로가 지정되어 있으면 오직 같은 스킴, 권한, 경로를 가진 URI만 필터를 통과할 수 있다.


Note : 경로 지정에는 경명의 일부에 무엇이 와도 매칭된 것으로 간주하는 와일드카드(* 표시)를 포함시킬 수 있다.


데이터 테스트는 필터와 인텐트의 URI와 MIME 타입을 비교하며 그 규칙은 다음과 같다.

  1. URI와 MIME 타입이 모두 지정되지 않은 인텐트는 URI와 MIME 타입 모두를 지정하지 않은 필터만 통과할 수 있다.
  2. URI는 포함하고 있지만 MIME 타입을 포함하지 않은 (타입이 명시적이지도 않고 URI에 의해 유추할 수도 없는) 인텐트는 URI 포맷은 지정하고 MIME 타입은 지정하지 않은 필터만 통과할 수 있다.
  3. MIME 타입은 포함하고 있지만 URI는 포함하지 않은 인텐트는 같은 MIME 타입이 지정되어 있고 URI 포맷은 지정하지 않은 필터만 통과할 수 있다.
  4. URI와 MIME 타입을 포함한 (타입이 명시적이거나 URI로 부터 유추가 가능한) 인텐트는 필터에서 지정한 타입과 매칭되면 일단 MIME 타입 부분만 테스트를 통과한다. URI 부분의 테스트는 필터에서 지정한 URI와 동일해도 통과할 수 있으며 필터에는 URI를 지정하지 않았지만 URI가 content: 또는 file: 형태라면 통과할 수 있다. 즉, 컴포넌트는 필터에 MIME 타입만 지정한 경우 content:와 file: 데이터는 수용할 수 있는 것으로 간주된다.


마지막의 (D)규칙은 컴포넌트가 파일이나 컨텐트 프로바이더라는 로컬 데이터를 수용할 수 있음을 의미한다. 그래서 컴포넌트들의 필터는 데이터 타입만 지정하고 content:와 file: 스킴은 명시적으로 지정하지 않아도 된다. 아래 예제는 이런 상황에 대한 전형적인 예시다. 예시에서는 컴포넌트가 컨텐트 프로바이더로 부터 이미지 데이터를 받아들이고 디스플레이 할 수 있다는 것을 보여주고 있다.


1
2
3
4
<intent-filter>
    <data android:mimeType="image/*" />
    ...
</intent-filter>

대부분의 쓸모있는 데이터는 컨텐트 프로바이더를 통해 제공받을 수 있기 때문에 필터는 데이터 타입만 지정하고 URI는 지정하지 않는 것이 대체로 일반적인다.


또다른 일반적인 구성은 필터에 스킴과 데이터 타입만 지정하는 경우다. 아래 예제의 <data>엘리먼트는 액션 수행을 위해 네트워크로부터 제공되는 비디오 데이터를 사용할 수 있음을 나타내고 있다.


1
2
3
4
<intent-filter>
    <data android:scheme="http" android:type="video/*" />
    ...
</intent-filter>

인텐트 매칭(Intent matching)

인텐트는 실행시킬 타겟 컴포넌트를 찾기 위해 사용되기도 하지만 기기의 컴포넌트 세트에 대한 무언가를 찾을 때도 사용된다. 예를 들어, 홈 앱은 ACTION_MAIN 액션과 CATEGORY_LAUNCHER 카테고리를 지정한 인텐트 필터를 가진 모든 액티비티를 찾아서 앱 런쳐에 채워넣는다.


여러분의 앱에서도 이와 비슷한 방법으로 사용할 수 있다. PackageManager는 특정 인텐트를 수용할 수 있는 모든 컴포넌트를 알려주는 query...() 메서드 세트를 가지고 있으며 인텐트에 응답하는 최적의 컴포넌트를 결정해주는 resolve...() 메서드 시리즈도 갖고 있다. 예를 들어, queryIntentActivities() 메서드는 인자로 넘겨주는 인텐트를 수행할 수 있는 모든 액티비티의 목록을 반환해주며 queryIntentServices()는 서비스 목록을 반환해 준다. 이 메서드들은 컴포넌트를 실행시키는 것이 아니다. 단순히 응답가능한 컴포넌트들의 목록만을 제공해 주는 것이다. 비슷한 메서드로 브로드캐스트 리시버를 찾아주는 queryBroadcastReceivers() 메서드가 있다.



오늘 포스트를 다 올리고 자야겠다는 생각에 밤 11시를 넘겨버렸네요.


예전에는 새벽 1시, 2시까지도 깨어 있다가 아침에는 지각할까 걱정하며 허겁지겁 문턱을 나섰습니다.


그런데 언제부터인가 '새벽 1, 2시까지 할 일이 있으면 차라리 아침 5시, 6시부터 시작하자'라는 쪽으로 바람직하게 바뀌었습니다.


바람직하게 바뀌기는 했는데... '나이들면 새벽잠이 없어진다더라....'는 말이 자꾸 머릿속에 멤도는 건...


젠장 그래서 그랬던건가?... ^^;


그래도 아침일찍 일어나는 게 효율적이긴 합니다.


다음 글 >> 일반적인 인텐트(Common Intents) (1)

이전 글 >> 인텐트와 인텐트 필터(Intents and Intent Filters) (1)





posted by 리치크루