Android

인텐트 (2) : 보류 인텐트

까망사과 2022. 4. 5. 16:00

펜딩 인텐트는 내장된 인텐트를 외부 앱에서 실행되도록 하기 위해 사용한다.

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

  • 사용자가 알림/앱 위젯으로 액션을 수행할 때 실행되는 인텐트를 선언
  • 향후 지정된 시간에 실행되는 인텐트를 선언

펜딩 인텐트를 생성할 때는 다음 메서드 중 하나를 사용한다.

  • getActivity()
    액티비티를 시작하는 인텐트 생성
  • getService()
    서비스를 시작하는 인텐트 생성
  • getBroadcast()
    브로드캐스트 리시버를 시작하는 인텐트 생성

각 메서드는 현재 앱의 컨텍스트, 실행할 인텐트, 인텐트의 사용 방식을 나타내는 플래그 값을 인자로 받는다.

 


가변성 설정하기

앱이 안드로이드 12 이상 버전을 타겟팅할 경우 각 PendingIntent 객체에 다음 두 플래그로 가변성을 설정해야 한다.

그렇지 않으면 IllegalArgumentException이 발생한다.

  • PendingIntent.FLAG_MUTABLE
    펜딩 인텐트를 가변형으로 설정한다.
  • PendingIntent.FLAG_IMMUTABLE
    펜딩 인텐트를 불변형으로 설정한다. 이렇게 하면 다른 앱에서 인텐트를 수정하지 못한다.
val pendingIntent = PendingIntent.getActivity(
    applicationContext,
    REQUEST_CODE,
    intent,
    /* 플래그 */ PendingIntent.FLAG_IMMUTABLE
)

 

하지만 가변형으로 설정해야 하는 경우가 있다.

  • 알림에서 답장 기능을 지원
    답장 기능은 답장과 관련된 PendingIntent 객체 안의 클립 데이터를 변경해야 한다.
    이런 요청을 하려면 보통 fillIn()FILL_IN_CLIP_DATA 플래그를 전달한다.
  • requestLocationUpdates() 또는 이와 유사한 API를 호출하여 디바이스의 위치 정보를 요청
    가변형 펜딩 인텐트를 사용하면 시스템이 위치 수명 주기 이벤트를 나타내는 엑스트라를 추가할 수 있다.
    이런 이벤트는 위치 변경을 포함하며 프로바이더를 사용할 수 있게 한다.
  • AlarmManager를 사용하여 알람을 예약
    가변형 펜딩 인텐트를 사용하면 시스템이 EXTRA_ALARM_COUNT 엑스트라를 사용할 수 있다.
    이 엑스트라는 반복되는 알람이 발생이 횟수를 나타낸다.
    이를 포함하면 인텐트는 디바이스가 슬립 등의 상태에서 알림이 여러 번 발생했는지 여부를 앱에 알릴 수 있다.

앱에서 가변형 인텐트 객체를 생성한다면 명시적 인텐트를 사용하는 것을 권장한다.
그렇게 하면 다른 앱에서 PendingIntent를 호출하고 제어를 돌려받았을 때 언제든지 앱의 동일한 컴포넌트가 시작된다.

 

외부 앱이 내부 앱의 펜딩 인텐트를 사용할 수 있는 방법을 더 잘 정의하려면 펜딩 인텐트에 명시적 인텐트를 사용해야 한다.

이렇게 하려면 다음 단계를 따라야 한다.

  1. 기초 인텐트에 액션, 패키지, 컴포넌트 필드가 설정되었는지 확인한다.
  2. 펜딩 인텐트를 생성할 때 안드로이드 6.0(API 레벨 23)에 추가된 FLAG_IMMUTABLE을 사용한다.
    이 플래그를 사용하면 PendingIntent를 수신하는 앱이 빈 속성을 추가하지 못한다.
    앱의 minSdkVersion이 22 이하이면  안전성과 호환성을 위해 다음처럼 if문을 사용하여 빌드 버전을 분기해야 한다.
    if (Build.VERSION.SDK_INT >= 23) {
        // FLAG_IMMUTABLE로 펜딩 인텐트 생성
    } else {
        // 펜딩 인텐트를 생성하는 기존 코드
    }

 


인텐트 검사

액티비티를 시작하는 암시적 인텐트를 수신하면 시스템은 해당 인텐트에 가장 적합한 액티비티를 탐색한다.

이때 각 액션, 데이터 , 카테고리 정보와 인텐트 필터를 비교한다.

 

액션 테스트

인텐트 필터는 0개 이상의 <action> 태그를 선언할 수 있다.

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

이 필터를 통과하려면 인텐트 객체에 지정된 액션은 필터에 나열된 것 중 하나와 일치해야 한다.

필터에 액션이 지정되어 있지 않으면 인텐트에는 일치시킬 항목이 없는 것이므로 필터를 통과하지 못한다.

하지만 인텐트에 액션이 지정되어 있지 않으면 필터가 적어도 하나의 액션을 포함하면 통과한다.

 

카테고리 테스트

인텐트 필터는 0개 이상의 <category> 태그를 선언할 수 있다.

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

필터를 통과하려면 인텐트의 모든 카테고리는 필터 안의 것과 일치해야 한다.

반면 인텐트 필터는 인텐트에 지정된 카테고리보다 더 많이 선언할 수 있다.

그러므로 카테고리가 지정되지 않은 인텐트는 인텐트 필터에 어떤 카테고리가 선언되어 있던 항상 필터를 통과할 수 있다.

 

안드로이드는 startActivity()startActivityForResult()에 전달되는 모든 암시적 인텐트에 CATEGORY_DEFAULT를 지정한다.

액티비티가 암시적 인텐트를 수신하도록 하려면 인텐트 필터가 android.intent.category.DEFAULT 카테고리를 포함해야 한다.

 

데이터 테스트

인텐트 데이터를 지정하기 위해 다음처럼 인텐트 필터는 0개 이상의 <data> 태그를 선언할 수 있다.

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

<data> 태그에 URI 구조 및 MIME 타입을 지정할 수 있다.

URI 구조는 다음 4가지 속성으로 나뉘어 있다.

 

<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"이다.

 

이 속성들은 모두 선택 항목이지만 서로 종속적이다.

  • scheme을 지정하지 않으면 host는 무시된다.
  • host를 지정하지 않으면 port는 무시된다.
  • schemehost를 모두 지정하지 않으면 path는 무시된다.

인텐트 안의 URI를 필터와 비교할 때는 필터에 지정된 속성의 부분만 비교한다. 예를 들면 다음과 같다.

  • 필터에 scheme만 지정된 경우 해당 scheme만 일치하면 URI가 테스트를 통과한다.
  • 필터에 scheme이 지정되고 path는 지정되지 않은 경우 동일한 scheme만 포함하면 path에 관계없이 URI가 테스트를 통과한다.
  • 필터에 scheme, path가 지정된 경우 두 부분이 모두 동일한 URI만 테스트를 통과한다.

 

path를 지정할 때 *를 사용하여 경로명의 일부분만 일치하도록 요구할 수 있다.

 

데이터 테스트는 인텐트 안 URI와 MIME 타입을 필터에 지정된 것과 비교한다. 비교 규칙은 다음과 같다.

  1. URI와 MIME 타입 모두 지정되지 않은 경우 필터 역시 URI와 MIME 타입 모두 지정하지 않아야 통과한다.
  2. URI는 지정되고 MIME 타입은 지정되지 않은 경우 인텐트는 URI가 필터의 것과 일치하고 필터가 MIME 타입을 지정하지 않은 경우에만 통과한다.
  3. MIME 타입은 지정되고 URI는 지정되지 않은 경우 필터가 동일한 MIME 타입을 가지지만 URI를 지정하지 않은 경우만 필터를 통과한다.
  4. URI와 MIME 타입 모두 지정된 인텐트는 MIME 타입이 필터의 것과 일치하는 경우 테스트를 부분적으로 통과한다.
    그리고 URI가 필터에 지정된 것과 일치하거나, content:file: URI를 포함하고 필터가 URI를 지정하지 않은 경우도 그렇다.
    바꿔 말하자면 컴포넌트는 필터에 MIME 타입만 나열된 경우만 content:file: 데이터를 지원하는 것으로 간주된다.
    따라서 content:file:scheme으로 지정하지 않고 MIME 타입만 지정해도 컨텐트 프로바이더에서 로컬 데이터를 가져올 수 있다.

 

인텐트가 URI나 MIME 타입을 지정하지만 <intent-filter> 태그 안에 <data> 태그가 없으면 데이터 테스트를 통과하지 못한다.

 

다음 예시는 <data>가 컴포넌트가 컨텐츠 프로바이더로부터 이미지 데이터를 가져온다는 것을 나타낸다.

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

일반적으로 URI는 지정하지 않고 MIME 유형만 지정한다.
사용 가능한 데이터는 대부분 컨텐츠 프로바이더가 제공하기 때문이다.

 

MIME 타입과 scheme을 지정하는 것도 일반적인 사용법이다.

다음 예시는 컴포넌트가 액션을 수행하기 위해 네트워크에서 비디오 데이터를 가져올 수 있다는 것을 나타낸다.

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

 

인텐트 매칭

PackageManager에 있는 일련의 queryXXX() 메서드는 특정 인텐트를 허용하는 모든 컴포넌트를 반환한다.

이와 마찬가지로 resolveXXX() 메서드는 인텐트에 응답할 수 있는 최적의 컴포넌트를 판별한다.

예를 들어 queryIntentActivities()는 인자로 전달받은 인텐트를 수행할 수 있는 모든 액티비티의 리스트를 반환한다.

이 메서드들은 컴포넌트를 반환할 뿐 활성화하지는 않는다.