Android - Uri, Scheme, SSP(Scheme Specific Part) 정리

Android 개발을 하면서 사용하는 Uri, Scheme, SSP(Scheme Specific PART)에 대해서 정리하였습니다. Intent는 data라는 정보를 갖고 있습니다. data는 Uri로 표현되며, Uri의 구성 요소 중에 scheme, SSP가 있습니다. scheme과 host 등은 어렵게 느껴지지 않지만 SSP(Scheme Specific Part)는 낯설게 느껴질 수 있습니다.

먼저 Intent에 대해서 간단히 설명하고, Uri와 Scheme, SSP를 소개하겠습니다.

Intent에 대한 간단한 설명

Android에서 이벤트를 주고 받을 때 Intent를 사용합니다. Intent에는 Data라는 아이템이 있는데요. Intent가 어떤 데이터를 갖고 있는지 표현하고, Broadcast 등을 전달할 때 어떤 리시버로 전달이 되어야하는지 나타낸다고 생각할 수 있습니다. Activity를 실행할 때도 Data의 내용에 따라서 어떤 Activity를 실행할지 결정됩니다.

Intent는 다음과 같이 생성하고 Data를 설정할 수 있습니다.

val intent = Intent(Intent.ACTION_VIEW)
intent.addCategory(Intent.CATEGORY_DEFAULT)
intent.addCategory(Intent.CATEGORY_BROWSABLE)

val uri: Uri = Uri.parse("http://google.com")
intent.data = uri

위의 코드에서 Intent의 data를 설정할 때 Uri를 입력합니다. 즉, 데이터는 Uri이고 Uri가 어떤 데이터인지 표현합니다.

Uri가 갖고 있는 데이터 정보

Uri는 Uniform Resource Identifier라는 의미로, 리소스를 나타내는 식별자입니다. 인터넷에서 사용하는 Url은 Uri하위 개념입니다.

Uri은 다음과 같은 구조로 이루어져있습니다.

<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]

위의 http://google.com를 예로 들었는데요. 여기서 http는 scheme, google.com은 host가 됩니다. port 및 path 등은 없습니다.

다음과 같이 Uri.parse()를 이용하여 문자열을 Uri 객체로 만들 수 있습니다.

아래 코드와 같이 Uri 객체의 scheme, host 등을 출력해보면 잘 설정된 것을 볼 수 있습니다.

val uri: Uri = Uri.parse("http://google.com")
Log.d(TAG, "scheme: ${uri.scheme}, host: ${uri.host}, " +
        "port: ${uri.port}, path: ${uri.path}")

Output:

MainActivity: scheme: http, host: google.com, port: -1, path:

Uri를 사용하는 이유

Uri는 리소스를 나타내는 식별자입니다.

예를 들어, 사용자가 http://google.com이라는 링크를 눌렀을 때 앱은 이것을 인텐트로 만들고 startActivity()를 호출합니다. 그리고 시스템은 Intent의 데이터를 처리할 수 있는 앱을 찾습니다. 이 때 Uri의 scheme, host 등으로 관련 있는 앱들을 찾습니다.

다음 코드를 실행시키면, 시스템은 이 Intent와 관련있는 앱들을 찾고 실행합니다. 이런 인텐트는 Browser들이 처리할 수 있기 때문에, 코드를 실행하면 Browser가 실행됩니다.

val intent = Intent(Intent.ACTION_VIEW)
intent.addCategory(Intent.CATEGORY_DEFAULT)
intent.addCategory(Intent.CATEGORY_BROWSABLE)

val uri: Uri = Uri.parse("http://google.com")
intent.data = uri

startActivity(intent)

앱이 어떤 종류의 인텐트를 처리할 수 있는지 표현하는 방법 중 하나는 AndroidManifest의 IntentFilter입니다. 시스템은 AndroidManifest.xml을 읽어서 이 앱이 어떤 인텐트를 처리할 수 있는지 분류하고, 인텐트를 보낼 때 이런 정보들을 참조하여 누구에게 보낼지 결정합니다.

Chrome 앱의 Manifest를 보면 아래와 같이 Activity의 IntentFilter를 설정하였습니다. 조금 더 자세히 보시면, 위의 Intent가 갖고 있는 action, category가 동일하다는 것을 볼 수 있습니다. 또한, data 항목에 scheme이 http로 정의되어있습니다. 이 data는 Uri를 의미하고, scheme이 http, https인 Uri는 모두 처리할 수 있다는 의미입니다. 즉, host가 어떤 것이 오든 중요하지 않습니다.

<activity android:name="com.google.android.apps.chrome.IntentDispatcher">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="http"/>
        <data android:scheme="https"/>
    </intent-filter>
</activity>

이런 식으로 App은 Manifest에 처리할 수 있는 Uri를 선언하기 때문에 시스템은 Uri 정보를 비교하여 누구에게 Intent를 전달할지 결정할 수 있습니다.

만약 어떤 Activity는 host가 google.com인 것만 처리할 수 있다면, 다음과 같이 IntentFilter를 설정할 수 있습니다. data 항목을 보시면 host가 설정되었습니다.

<intent-filter>
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <category android:name="android.intent.category.BROWSABLE"/>
    <data android:scheme="http" android:host="google.com"/>
    <data android:scheme="https" android:host="google.com"/>
</intent-filter>

SSP(Scheme Specific Part)

위에서 Uri는 아래와 같은 형식이라고 했습니다.

<scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>]

하지만, 아래와 같은 형식으로도 사용합니다.

<scheme>:<scheme-specific-part>#fragment

첫번째 패턴과 비교했을 때 //는 없습니다. :의 오른쪽 부분을 SSP(Scheme Specific Part)라고 합니다. 그리고 #의 오른쪽은 fragment라고 합니다.

Android에서 SSP 형식의 Uri를 사용할 때도 있습니다.

예를 들어, App이 설치가 완료되면 ACTION_PACKAGE_ADDED 인텐트가 전달됩니다.

Intent { act=android.intent.action.PACKAGE_ADDED dat=package:com.codechacha.myapplication flg=0x4000010 (has extras) }

이 인텐트의 Uri는 package:com.codechacha.myapplication입니다. scheme은 package이고 SSP는 com.codechacha.myapplication입니다. fragment는 없습니다.

다른 예로, 전화를 걸 때 전달되는 Intent의 Uri는 tel:010-1111-1111과 같은 형식입니다. tel이라는 scheme이 있고, 그 scheme에 대한 자세한 정보를 SSP에 저장하고 있습니다. smsmms나 모두 동일하게 SSP로 표현하고 있습니다.

IntentFilter에 SSP 정의

앱이 디바이스에서 삭제되면 PACKAGE_FULLY_REMOVED라는 인텐트를 브로드캐스트로 보냅니다. 이 인텐트는 SSP를 사용하여 Uri를 표현하며 package:com.example.myapplication처럼 SSP에 앱의 패키지 이름을 넣고 전달합니다.

만약 PACKAGE_FULLY_REMOVED를 리시버에서 받고 싶다면 다음과 같이 scheme을 package로 설정해야 합니다. 이렇게 설정하면 어떤 앱이 삭제될 때 그 이벤트를 받게 됩니다.

<receiver android:name=".MyReceiver">
    <intent-filter>
        <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
        <data android:scheme="package"/>
    </intent-filter>
</receiver>

하지만 만약 com.example.myapplication라는 앱이 삭제되었을 때만 인텐트를 받고 싶다면 어떻게 해야 할까요? IntentFilter에 ssp에 com.example.myapplication로 설정하면 됩니다. 이렇게 설정하면 인텐트의 ssp와 인텐트필터의 ssp와 일치할 때만 전달이 됩니다.

<receiver android:name=".MyReceiver">
    <intent-filter>
        <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
        <data android:scheme="package" android:ssp="com.example.myapplication"/>
    </intent-filter>
</receiver>

Manifest가 아닌 Context.registerReceiver()로 리시버를 등록한다면 다음과 같이 IntentFilter 객체를 생성할 수 있습니다. SSP를 설정할 때 addDataSchemeSpecificPart()를 사용하면 됩니다.

val intentFilter = IntentFilter()
intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED)
intentFilter.addDataScheme("package")
intentFilter.addDataSchemeSpecificPart("com.example.myapplication",
        PatternMatcher.PATTERN_LITERAL)

val receiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Log.d("MyReceiver", "onReceive: $intent")
    }
}
context.registerReceiver(receiver, intentFilter)

SSP로 Uri가 정의된 Intent를 전달

위의 예제들은 SSP로 설정된 Intent를 받기 위해 IntentFilter를 등록하는 방법들입니다. 반대로 내 앱에서 Intent를 보낼 수도 있습니다.

Uri에 SSP를 설정하고 싶다면, 다음과 같이 Uri.fromParts(scheme, ssp, fragment)로 ssp가 설정된 Uri 객체를 만들 수 있습니다.

val intent = Intent("com.custom.intent.action")
intent.data = Uri.fromParts("package" /* scheme */,
    "com.example.myapplication" /* ssp */,
    null /* fragment */)

이렇게 인텐트를 보내면 다음과 같이 인텐트 필터를 설정한 앱으로 전달될 수 있습니다.

<intent-filter>
    <action android:name="com.custom.intent.action" />
    <data android:scheme="package" android:ssp="com.example.myapplication"/>
</intent-filter>

물론 scheme만 설정한 앱으로도 전달이 됩니다.

<intent-filter>
    <action android:name="com.custom.intent.action" />
    <data android:scheme="package"/>
</intent-filter>

Fragment가 사용되는 예제

Android 개발을 하면서 fragment가 사용되는 것은 못봤습니다. 혹시 보신분이 있다면 알려주세요.

정리

Intent의 Data를 표현하는 방식은 Uri와 Mimetype이 있습니다. Mimetype은 여기서 소개하지 않았지만 데이터의 타입을 표현합니다. Uri는 <scheme>://<host> 형식으로 사용될 때도 있고 <scheme>:<scheme-specific-part> 형식으로 사용될 때가 있습니다. 각각 IntentFilter와 Intent 객체를 만드는 방법이 조금씩 다릅니다.

이 글을 읽으시면서 Intent를 조금이나마 이해하는데 도움이 되었으면 좋겠습니다.

Intent의 기본적인 내용에 대해서 좀 더 알고 싶으시다면 다음 글도 읽어보시는 것을 추천드립니다.

Loading script...

Related Posts

codechachaCopyright ©2019 codechacha