Intent

Android의 기본 Application component에는 다음 네 가지가 있다.

Components
Description
Activity사용자 인터페이스가 있는 화면 하나를 뜻한다.
ServiceBackground에서 실행되는 component이다.
오랫동안 실행되는 작업이나 원격 프로세스를 위한 작업이 주로 이루어지며 사용자 인터페이스를 제공하지 않는다.
(ex. 음악 재생, 파일 다운로드)
Content ProviderFile system, Database 등의 저장소를 통합 관리한다.
권한이 허가된 경우 다른 application에서 접근 및 수정까지도 가능하다.
Broadcast RecieverSystem이나 application은 특정한 상태나 data에 대해 broadcast를 날릴 수 있는데 이를 수신하여 처리한다.

위 기본 요소 중 Activity, Service, Broadcast RecieverIntent 라는 비동기 메시지가 전달되어 활성화된다.


Intent

Message Object의 일종으로 intent를 사용하여 다른 component들에게 작업을 요청할 수 있다.

기본적으로는 다음 세 가지 목적으로 사용한다.

  • Activity의 시작
  • Service의 시작
  • Broadcast의 전달

Activity의 시작

  • Intent를 startActivity()로 전달하면 새 activity가 실행된다.
  • Activity의 실행 결과를 돌려받고 싶다면 startActivityForResult()를 호출한다.
    • 돌려받은 결과는 onActivityResult()를 구현하여 처리할 수 있다. 결과 또한 intent로 수신한다.

Service의 시작

  • Intent를 startService()로 전달하면 일회성 작업이 수행된다.
  • Client-Server interface로 설계된 service라면 intentbindService()로 전달하면 바인딩하여 사용할 수 있다.

Broadcast의 전달

  • Intent를 sendBroadcast(), sendOrderedBroadcast(), sendStickyBroadcast() 중 하나에 담아서 전달한다.
  • 모든 application들에 message를 전달할 때 사용한다. (system에서 전달할 때는 특정 application을 지정해서 전달할 수도 있다.)

Intent의 기본 요소

ComponentName, Action, Data, Category, Extra, Flags 가 있다.

ComponentName

Optional 항목으로 Implicit intent로 사용할 경우 반드시 이름을 명시해야 한다.

Service를 시작하는 경우에는 무조건 이 항목을 지정해야 한다. 그렇지 않으면 해당 intent에 어느 service가 응답할지 확신할 수 없고 사용자고 어떤 service가 시작되는지 알 수 없게 된다.

ComponentName으로는 application의 패키지명이 포함된 Full-Qualified class name을 사용해야 한다. (ex. com.example.Example.Activity).

Intent 생성자를 사용하거나 setComponent(), setClass(), setClassName()을 사용하여 ComponentName을 설정할 수 있다.

Action

수행할 작업을 나타내며, 특정 application에서 커스터마이징한 action name을 사용할 수도 있지만 일반적으로는 Intent class나 다른 Framework class가 정의한 Action 상수를 사용한다.

커스터마이징할 경우 패키지명을 Prefix(접두사)에 포함시켜야 한다.

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

Activity를 시작할 때 아래 두 가지 ACTION을 사용할 수 있다.

  • ACTION_VIEW
    • startActivity()를 사용하며, 해당 Activity가 사용자에게 표시할 정보를 가지고 있을 때(ex. 갤러리) 사용한다.
  • ACTION_SEND
    • Shared intent라고도 하며, startActivity()를 사용한다. 사용자가 다른 application을 통해 공유할 수 있는 data를 가지고 있을 때(ex. 이메일) 사용한다.

Intent 생성자를 사용하거나 setAction()을 사용하여 Action을 설정할 수 있다.

Data

Action을 수행할 data 또는 해당 data의 MIME type을 참조하는 URI Object이다. 일반적으로 action명을 보면 data를 추측할 수 있는데 예를 들어 action이 ACTION_EDIT라면 data에는 편집할 문서의 URI가 들어있어야 한다.

Intent 생성 시 URI와 함께 Data type(MIME type)의 지정이 중요하다. 만약 이미지 처리용 application이 있을 때, URI가 비슷하다 할지라도 이 application에서는 오디오 처리를 할 수는 엇다.따라서 MIME type을 지정해주는 것이 좋다.

Data URI만 설정하려면 setData()를 사용하고, MIME type만 설정하려면 setType()을 사용하면 된다.

두 가지 모두 사용할 경우 setData()setType()은 서로의 값을 덮어버리는 특성이 있으므로 반드시 setDataAndType()을 사용해야 한다.

일반적으로 Action과 Data는 다음과 같은 짝을 갖는 경우가 많다.

ActionURIDescription
ACTION_VIEWcontent://contacts/people/11번 사람에 대한 정보를 표시한다.
ACTION_DIALcontent://contacts/people/1Dialer에 1번 사람의 번호를 채워서 보여준다.
ACTION_VIEWtel:123Dialer에 ‘123’을 채워서 보여준다.
ACTION_DIALtel:123Dialer에 ‘123’을 채워서 보여준다.
ACTION_EDITcontent://contacts/people/11번 사용자에 대한 정보를 수줭한다.
ACTION_VIEWcontent://contacts/people/주소록 리스트를 띄운다. 이 리스트에서 특정 사용자를 선택할 경우 ACTION_VIEW content://contacts/N이 시작된다.

위의 두 가지 주 속성에 더하여, 아래에 설명할 몇 가지의 부 속성을 사용할 수 있다.

Category

Optional 항목이며 Intent를 처리해야 하는 component에 대한 추가 정보를 담고있다.

몇 가지 보편적인 Category의 예이다.

  • CATEGORY_LAUNCHER
    • Application 진입 시 최초의 Activity임을 의미한다.
  • CATEGORY_ALTERNATIVE
    • Data의 일부에 대해 사용자가 사용할 수 있는 대체 action들에 대한 목록을 포함할 것을 의미한다.
  • CATEGORY_BROWSABLE
    • 대상 Activity가 스스로 Web browser에게 자신을 시작할 권한을 주며, link를 통해 참조된 data를 표시하게 한다. 이미지, 이메일, 메시지 등이 해당된다.

Category 지정은 addCategory()를 통해서 설정한다.

Type

  • Intent data의 명시적인 type(MIME type)을 정의한다. 일반적인 경우 type은 data 자신으로부터 추측할 수 있다.

Component

  • Intent에 사용하기 위한 component class의 명시적인 이름을 정의한다. 일반적으로 이 항목은 intent 내의 다른 정보(action, data/type, category)에 의해 정의되고 이를 다룰 수 있는 component에 매칭된다. 만약 이 속성이 세팅될 경우 아무 동작도 수행하지 않으며 이 component는 그 자체로만 사용된다. 이 속성을 정의함으로 인해 모든 다른 Intent 속성들은 optional 항목이 된다.

Extras

요청한 작업을 수행하기 위한 추가 정보를 담고 있다. Key-Value pair로 이루어져 있다.

putExtras() 메소드를 사용하며, 모든 extra data를 갖는 Bundle을 생성하여 직접 putExtras()로 삽입할 수도 있다.

예를 들어, ACTION_SEND로 이메일을 전송할 경우, “받는 사람”을 EXTRA_EMAIL로 지정하고, “제목”을 EXTRA_SUBJECT로 할 수 있다.

Intent class는 표준화된 data type들에 대해 많은 EXTRA_*를 지원하고 있다. 자신의 application만의 특정 extra key를 사용해야 할 경우 Package name을 prefix(접두사)로 포함시켜야 한다.

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

Flags

Intent class에서 정의하고 있으며, Meta-data 역할을 수행한다. Android system에 activity를 시작할 방법을 알려줄 수도 있고, activity를 시작한 후 어떻게 처리해야 하는 지도 알려줄 수 있다.

지정은 setFlags() 로 하면 된다.

Intent의 type

Intent type은 Explicit, Implicit 두 가지가 있다.

Explicit intent무엇을 할지, Implicit intent누구에게 던질지가 중요하다.

application의 보안을 위해 Service의 시작 시에는 항상 Explicit intent만 사용하고 Intent filter는 선언하지 않도록 하라.

Explicit(명시적) intent

새 activity를 시작하거나 service를 시작할 때 사용한다. (ex. File download)

일반적으로 application 안에서 component를 시작할 때 사용한다.

시작할 component의 이름을 Fully-Qualified class name (ex. Abc.Class)으로 지정한다.

Explicit intent를 사용할 경우 system이 즉시 지정된 component를 시작한다.

아래 예시는 웹에서 파일을 다운로드 하도록 한 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);

Implicit(암시적) intent

특정 component가 뭔지는 모르지만 현재 application이 수행할 수 없는 일반적인 작업을 다른 application의 component가 처리할 수 있도록 한다.

Implicit intent를 사용하면 system이 시작시킬 적절한 component를 찾게 된다. 이 때, intent의 내용을 다른 application들의 Manifest file에 선언된 Intent filter와 대조하는 작업을 거치고, 해당 기능을 수행할 수 있는 application이 여러개가 있다면, 사용자가 선택할 수 있도록 화면을 띄워준다.

Intent filterManifest file에 선언한 해당 component가 수신하고자 하는 Intent type에 대한 내용이다. 다른 application들이 여기에 선언한 내용을 기반으로 내 application의 기능을 사용할 수 있게 된다. Intent filter에 아무 것도 선언하지 않는다면 explicit intent로만 수행할 수 있다.

아래 예시는 URI를 사용하지 않고 “text/plain” 정보를 통해 extra 정보를 지정한 후 ACTION_SEND를 통해 implicit intent를 날리는 것이다.

// Create the text message with a string
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");
// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}

Implicit intent 사용 시 주의할 점은 startActivity()를 통해 날려도 처리할 application이 전혀 표시되지 않을 수 있다. 이 경우 호출 실패는 물론 application이 죽는다.

어떤 Activity에서라도 해당 intent를 확실히 수신할 수 있도록 하려면 위의 코드처럼 resolveActivity()를 호출하여 미리 확인하자. 결과가 null이 아닌 경우 해당 intent를 처리할 수 있는 application이 최소 하나는 있다는 것을 의미한다.

Implicit intent에 응답하는 application이 하나 이상일 경우, 사용자가 수행될 application을 선택할 수 있는데 이 때 띄워주는 메뉴를 app chooser(앱 선택기)라 한다.

Android는 사용자에게 항상 같은 application을 사용할 수 있는 옵션을 제공한다.

하지만 특정한 케이스에서, 사용자가 항상 다른 application을 사용해야 한다면 앱 선택기를 명시적으로 표시할 필요가 있다.

앱 선택기를 항상 표시하기 위해 아래와 같이 chooser intent생성 후 createChooser()를 사용하면 된다.

Intent sendIntent = 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 the chooser dialog
Intent chooser = Intent.createChooser(sendIntent, title);
// Verify the original intent will resolve to at least one activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}

Intent filter

내 application이 수신할 수 있는 Implicit intent가 어떤 것이 있는지 알리려면 application Component에 대한 하나 이상의 intent filterManifest file에 선언해야 한다.

Explicit intent는 Component가 어떤 intent filter를 선언했든 무관하게 항상 정의해둔 곳으로 전달된다.

각 Intent filter는 다음과 같은 세 가지 요소 중 하나 이상을 사용하여 허용할 intent type을 정의할 수 있다.

  • <action>
    • 허용된 intent 작업을 name 속성에서 선언한다. Literal string이며 Class 상수가 아니다.
  • <data>
    • 허용된 data type을 선언한다. Data URI(scheme, host, port, path 등)와 MIME type 의 여러가지 내용 중 하나 이상의 속성을 사용한다.
  • <category>
    • 허용된 intent category를 name 속성에서 선언한다. Literal string이며 Class 상수가 아니다.

Explicit intent를 수신하기 위해서는 Intent filter 내에 반드시 CATEGORY_DEFAULT를 포 함시켜야 한다. startActivity(), startActivityForResult() 메소드들은 모든 intent를 CATEGORY_DEFAULT를 선언한 것처럼 취급하기 때문에 이 Category를 intent filter에 선언하지 않으면 Activity의 어떤 암시적 intent도 확인되지 않는다.

아래 예시는 Data typetextACTION_SEND intent를 수신하겠다는 뜻이다. Intent filter 내에 선언한 것들과 하나라도 맞지 않으면 intent가 application으로 전달되지 않는다.

<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>

Intent filter에 등록하지 않았다고 하더라도 다른 application에서 내 application Component의 경로를 알아챌 경우 내 component를 수행할 수 있게 된다. 이 경우를 방어하기 위해 Manifest의 안에 android:exported = false를 설정하면 된다.

아래는 Android developer 사이트에서 제공하는 소셜 공유 application의 Manifest file이다.

<activity android:name="MainActivity"> // MainActivity 가 application의 Entry point(진입 지점)이다.
<!-- This activity is the main entry, should appear in app launcher -->
<intent-filter>
<action android:name="android.intent.action.MAIN" /> // 여기가 주요 진입 지점이며, 어떠한 intent도 바라지 않는 다는 것을 의미한다.
<category android:name="android.intent.category.LAUNCHER" /> // 이 Activity의 icon이 system의 application 시작 관리자에 배치되어야 한다는 것을 나타낸다.
// 여기서는 icon을 지정하지 않았으므로 system은 <application> 에서 가져온 아이콘을 사용한다.
</intent-filter>
</activity>
<activity android:name="ShareActivity"> // 컨텐츠 공유를 목적으로 만든 Activity이다.
<!-- This activity handles "SEND" actions with text data -->
<intent-filter>
<action android:name="android.intent.action.SEND"/> // 다른 application에서 이 action과
<category android:name="android.intent.category.DEFAULT"/> // type이 text/plain인 category를 이 application으로 던졌을 때 이 activity를 수행한다.
<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>

Pending Intent

PendingIntentIntentwrapper이다.

다른 application에 권한을 위임하여 그 안에 들어있는 intent를 마치 본인 application의 자체 프로세스에서 실행하는 것처럼 사용하는 것이다.

뭔 말인가 싶다.

현재의 application A가 PendingIntent를 만들어서 다른 application이나 컴포넌트에 “내가 너에게 이 intent를 전달할 수 있는 권한을 줄테니 이따가 나 대신 좀 보내줘.” 라고 하는 것이다.

주요 사용 사례는 다음과 같다.

  • 사용자가 이 application의 notification을 통해 task를 수행할 때 intent가 실행되도록 한다(Android system의 NotificationManager가 Intent를 실행한다.).
  • 사용자가 이 application의 Application Widget으로 task를 수행할 때 intent가 실행되도록 한다(Mainscreen application에 Intent를 실행한다.).
  • 지정된 시간에 intent가 실행되도록 선언한다(Android system의 AlarmManager가 Intent를 실행한다.).

예를 들어, 아래 코드는 실행하면 Notification bar에 새 notification이 등록되고 사용자가 이 notification을 터치했을 때 MainActivity로 진입한다.

NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
PendingIntent pIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder mBuilder = new Notification.Builder(this);
mBuilder.setSmallIcon(R.mipmap.ic_launcher);
mBuilder.setTicker("Notification.Builder");
mBuilder.setWhen(System.currentTimeMillis());
mBuilder.setNumber(1);
mBuilder.setContentTitle("Notification.Builder Title");
mBuilder.setContentText("Notification.Builder Massage");
mBuilder.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
mBuilder.setContentIntent(pIntent);
mBuilder.setAutoCancel(true);
mBuilder.setPriority(Notification.PRIORITY_MAX);
nm.notify(1, mBuilder.build());

각 Intent object는 특정한 유형의 component(Activity, Service, BroadcastReceiver)가 처리하도록 설계되어 있다. 따라서 PendingIntent도 이러한 사항을 염두에 두고 생성해야 한다.

PendingIntent를 사용하는 경우 직접적으로 startActivity() 등의 호출을 사용하지 않으므로 적절하게 세팅해줘야 한다.

  • Activity 시작 Intent: PendingIntent.getActivitiy()
  • Service 시작 Intent: PendingIntent.getService()
  • BroadcastReceiver 시작 Intent: PendingIntent.getBroadcast()

Intent 살펴보기

system이 Activity를 시작하라는 implicit intent를 수신하면 system은 해당 intent에 대한 최선의 activity를 검색한다. 이 때 판단 근거가 아래 세 가지이다.

  • Intent Action
  • Intent Data (URI와 data type)
  • Intent Category

Action test

이 Filter를 통과하려면 아래 나열된 작업 중 하나와 일치해야 한다.

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

Category test

Intent 내의 모든 Category가 filter 내의 category와 일치해야 한다. Filter 내의 category 수가 더 많은 것은 상관 없다.

Intent의 category가 아무 것도 선언되어 있지 않으면 모두 통과할 수 있다.

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

Android는 CATEGORY_DEFAULT category를 startActivity()startActivityForResult()에 전달된 모든 implicit intent에 적용한다. 따라서 Activity가 implicit intent를 수신하기 위해서는 intent filter 내에 “android.intent.category.DEFAULT” category 선언이 반드시 포함되어 있어야 한다.

Data test

URI 구조 및 Data type(MIME type)에 대해 나타내는데, URI의 각 부분에 대해서는 별도의 속성 (scheme, host, port, path)이 사용될 수 있다.

<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http" ... />
<data android:mimeType="audio/mpeg" android:scheme="http" ... />
...
</intent-filter>
  • content://com.example.project:200/folder/subfolder/etc
    • scheme: content
    • host: com.example.project
    • port: 200
    • path: folder/subfolder/etc

위의 네 가지 속성에는 linear한 종속 관계가 존재한다.

  • scheme이 지정되지 않으면 host를 무시한다.
  • host가 지정되지 않으면 port를 무시한다.
  • scheme과 host 둘 다 지정되지 않으면 path를 무시한다.

Intent의 URI이 Filter의 URI와 비교될 때에는 filter 내에 포함된 URI와 부분적으로 비교된다.

  • filter가 scheme만 지정한 경우, 해당 scheme을 가지는 모든 URI는 filter와 매칭된다.
  • filter가 scheme과 authority를 지정하고 path를 지정하지 않는 경우, 같은 scheme과 authority를 갖는 모든 URI는 path와 관계 없이 filter를 통과한다.
  • filter가 scheme, authority, path를 모두 지정할 경우, 같은 scheme, authority, path를 가진 URI만이 filter를 통과할 수 있다.

    path에는 * 을 사용할 수 있다.

Data의 유효성을 판별하기 위해서는 intent의 URI, MIME type과 filter의 URI, MIME type을 모두 비교해야 한다.

a. URI와 MIME type을 모두 갖지 않는 intent는 URI와 MIME type을 아무 것도 정의하지 않은 filter만을 통과할 수 있다.
b. URI를 갖고 MIME type을 갖지 않는 intent는 filter의 URI와 일치하고 filter가 MIME type을 지정하지 않은 경우 통과할 수 있다.
c. URI가 없고 MIME type만 갖는 intent는 filter가 URI를 지정하지 않고 MIME type을 가지고 있을 때 통과할 수 있다.
d. URI와 MIME type을 모두 갖는 intent는 filter에 나열된 type과 매치되는 경우에만 통과한다. Intent의 URI가 filter의 URI와 일치하거나 content: 또는 file:을 가지고 있는 경우, 그리고 filter가 URI를 정의하지 않는 경우 통과할 수 있다. 다른 말로, filter가 MIME type만 가지고 있을 경우 component는 content:file:을 당연히 지원하는 것으로 여겨진다.

규칙 d는 component가 file 또는 content provider로부터 local file을 가지고 올 수 있다는 기대를 가지고 반영된다. 따라서, 이러한 filter는 data type만 나열해도 되고 content:file: scheme을 명시적으로 작성하지 않아도 된다.

아래 예시는 Content provider로부터 image data를 가지고 와서 표시할 수 있다는 의미를 갖는다.

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

아래 예시는 Network에서 Video data를 검색할 수 있다는 의미를 갖는다.

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

Intent matching

Intent를 Intent filter와 비교를 해보면 target component를 활성화 시킬 수 있을 뿐만 아니라 단말의 component set에 대한 정보를 발견할 수 있다. 예를 들어, Home application이 application 런쳐를 채우기 위해 ACTION_MAIN action과 CATEGORY_LAUNCH category를 갖는 intent filter들을 찾아볼 수 있다.

개발한 application에서 비슷한 방법을 사용할 수 있는데, Packagemanager는 query...() 메소드들을 가지고 있고 이는 특정 intent로 접근할 수 있는 모든 component를 return한다. 이와 비슷한 것들로 resolve...() 메소드들이 있다. 이는 intent를 응답하기 위한 최적의 component를 정의한다.

예를 들어, queryIntentActivities()는 intent가 argument로써 통과할 수 있는 모든 activity의 list를 return하고, queryIntentServices()는 service에 대한 list를 return한다. 두 가지 메소드 모두 component를 활성화 시키지는 않고 단지 list만 나열할 뿐이다. BroadcastReceiver에서 사용하는 것은 queryBroadcastReceivers()가 있다.

출처

Share