본문 바로가기

Android(Kotlin) Study A부터 Z까지 등반

안드로이드 Intent의 모든것

Intent 이론

https://www.truiton.com/2013/04/android-sendorderedbroadcast-example-with-priority/

인텐트란?

안드로이드의 애플리케이션 구성은 4대 컴포넌트로 이루어져 잇다.

  • 액티비티(Activity)
  • 서비스(Service)
  • 브로드캐스트 리시버(Broadcast Receiver)
  • 컨텐트 프로바이더(Content Provider)

 💡 위에 해당하는 컴포넌트들과의 통신자의 역할을 가지고 있다고 생각하면 될 것 같다.

 

통신 방법으로는 2가지가 있다. 암시적 명시적 인텐트이다. 암시적, 명시적 인텐트란 무엇일까?

인텐트의 종류new

 💡 간단하게 말했을 때 암시적 : 의도가 불명확하다. 해당 액션에 대해서 정확히 실행할 액티비티에 대한 보장이 없다. 액션 정보를 가지고 있음.(이런 액션을 취할거니깐 너가 정해줘) 명시적 : 의도가 명확하다. 어디 클래스로 보낼지 지정되어있음. 화면간 전환명시적은 거의 애플리케이션 내부에서 사용한다. 암시적은 다른 애플리케이션과 상호작용을 위해서 많이들 사용한다고 생각하면 된다.

 

암시적

val intent = Intent(Intent.ACTION_DIAL) //ACTION_DIAL 액션이 있는 앱과 Uri가 tel:로 가능한 액티비티를 찾아서 실행을 해줘야함,context를 보내지 않았음.
val TEST_DIAL_NUMBER = Uri.fromParts("tel", "5551212", null)
intent.setData(TEST_DIAL_NUMBER)
startActivity(intent)

암시적 인텐트의 액션 종류

명시적

val intent = Intent(this, SubActivity::class.java)
startActivity(intent) //인텐트를 수행할 class를 명시적으로 지정

인텐트를 보낼 때 Uri정보까지 보낼 때가 있다.

 💡 Uri는 URL와 URN이 합쳐진 것. 여러 종류의 데이터라고 생각하면 된다.

암시적 인텐트 실행과정

💡 Android System이 다른 앱의 매니페스트 파일에 있는 인텐트 필터를 비교하여 시작할 액션이 있는 경우의 앱을 시작하고 intent를 전달한다고 함.

 

💡 ActivityManagerPackageManager에 resolve 액티비티 API를 호출하여 적합한 액티비티가 어떤건지 물어본다. 이과정을 Resolving이라고함 여기서의 Resolving이 모든 곳에 통용되는 의미는 아닌듯하다!

 

암시적 인텐트 장점

💡 **PDF 파일을 열거다. 근데 PDF를 읽을 수 있는 앱은 Chrome, adobe등 여러 앱들이 존재한다. 그때 어떤 앱으로 열지 선택하는 창이 나오곤한다. 이미 기존에 어떤 기능을 지원하는 앱들이 있는 경우에 암시적 인텐트를 사용하여 보여주는것이 암시적 인텐트이다.

결국에 앱에서 PDF파일을 열어야하는데 굳이 앱 내부에서 PDF 리더기를 만들어주는 것보다, 다른 좋은 리더기가 있는지 찾아주고 그것이 있다면 실행시켜주면 되기 때문에 효율적으로 사용할 수 있을 것입니다.**

 

  • 하지만 PDF리더기가 하나도 없을 경우에는 오류가 생기기 때문에 Intent.resolveActivity를 호출하여 반환 값이 null이 아닌 경우에 활성화하고, null이 반환되는 경우에 실행할 수 있는 앱이 없다는 분기로 나누면 될 듯합니다.

사용할 수 있는 앱이 여러개 일 때

선택기라는 것을 사용하면 된다고 한다. createChooser()를 통해서 사용자가 선택할 수 있게 해줄 수 있습니다.

val sendIntent = Intent(Intent.ACTION_SEND)
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
val title: String = resources.getString(R.string.chooser_title)
// 다이얼로그 생성
val chooser: Intent = Intent.createChooser(sendIntent, title)

// 실행할 수 있는 앱이 있는 경우에만 실행
if (sendIntent.resolveActivity(packageManager) != null) {
    startActivity(chooser)
}

왜 암시적을 막아두었을까?

 💡 암시적 인텐트의 실행되는 과정, 여러 앱들의 매니페스트에 등록된 액션이 만약에 ACTION_PACKAGE_REPLACED 라고 해보자. 장치의 일부 패키지가 변경 되었다면, 모든 앱에게 패키지가 등록되었음을 알리게 된다. 이는 상당히 비효율적이다.

 

 💡 앱이 브로드캐스트를 수신하도록 등록한 경우, 앱의 수신기는 브로드캐스트가 전송될 때마다 리소스를 소비한다. 이 경우 너무 많은 앱이 시스템 이벤트 기반의 브로드캐스트를 수신하도록 등록하면 문제가 될 수 있. 브로드캐스트를 트리거하는 시스템 이벤트로 인해 모든 앱들이 급속하게 리소스를 소비할 수 있으며 이로 인해 사용자 환경이 손상될 수 있음.

 

💡 Android 8.0(API 레벨 26) 백그라운드 실행 제한의 일환으로 API 레벨 26 이상을 타겟팅하는 앱은 암시적 브로드캐스트의 broadcast receiver를 manifest에 더 이상 등록할 수 없습니다. 하지만 현재 몇몇 브로드캐스트는 이러한 제한에서 제외됩니다. 앱이 타겟팅하는 API 레벨과 관계없이 앱은 다음 브로드캐스트의 리스너를 계속 등록할 수 있습니다. 제한 제외 액션들 https://developer.android.com/guide/components/broadcast-exceptions?hl=ko 이러한 암시적 브로드캐스트가 여전히 백그라운드에서 작동하더라도 리스너 등록을 피해야 한다고한다. ????

 

 💡 앱의 보안을 보장하려면 **[Service]**를 시작할 때에는 항상 명시적 인텐트만 사용하고 서비스에 대한 인텐트 필터는 선언하지 마세요. 암시적 인텐트를 사용하여 서비스를 시작하면 보안 위험을 초래합니다. 인텐트에 어느 서비스가 응답할지 확신할 수 없고, 사용자는 어느 서비스가 시작되는지 볼 수 없기 때문입니다. Android 5.0(API 레벨 21)부터 시스템은 개발자가 암시적 인텐트로 **[bindService()]**를 호출하면 예외를 발생시킵니다.

 

명시적, 암시적에 대해서 더 나올 것 같긴한데 일단 인텐트 빌드하는 법을 알고가자.

구조는 다음과 같다.

Component name

시작할 컴포넌트의 이름이다. 해당 인텐트가 명시적 인텐트인지, 암시적 인텐트인지 구분하는 중요한 요소이다.

명시적인지, 암시적 인지를 구분하는 요소이며 시작하는 컴포넌트 이름.

  • 명시적 인텐트 (Explicit Intent)이름을 지정하여 호출 대상을 알 수 있는 인텐트
  • 암시적 인텐트 (Implicit Intent)?특정 컴포넌트의 클래스명 없이 어떠한 작업을 수행하는 것인지만 선언하는 인텐트이다. 해당 인텐트를 처리할 수 있는 컴포넌트를 시스템이 필터링하여 수행하거나 사용자에게 선택하도록 한다.어떤 작업을 수행하는지를 선언한다. (Action 등으로)

Action

수행 작업을 나타낸다. (클래스 안에 상수로 정의되어 있습니다. ) 일반적으로

  • [ACTION_VIEW] [startActivity()]갤러리 앱에서 볼 사진 또는 지도 앱에서 볼 주소와 같이 활동이 사용자에게 표시할 수 있는 일부 정보가 있는 경우 인텐트에서 이 작업을 사용 합니다.
  • [ACTION_SEND 공유 인텐트 라고도 [startActivity()]하는 이메일 앱이나 소셜 공유 앱과 같은 다른 앱을 통해 사용자가 공유할 수 있는 일부 데이터가 있는 경우 인텐트에서 이를 사용해야 합니다 .

ex)

val intent = Intent(Intent.ACTION_DIAL)

Data

작업 수행 데이터 및 데이터의 MIME를 참조하는 URI 객체

  • 따라서 코드상으로 작성할 때는 Action과 URI객체인 데이터를 함께 전달하곤 합니다.
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:01012341234"));

MimeType이란

💡 이 데이터가 어떤 형식의 데이터인지 설명하는 이름 예) text/plane = 문자만 있는 파일 , image/jpeg = jpeg형식의 이미지 파일 ....

 

Category

수행 액션의 추가적인 정보

암시적 인텐트 액션들과 카테고리

💡 카테고리는 인텐트를 처리해야 하는 구성 요소의 종류에 관한 추가 정보를 담은 문자열입니다. 인텐트 안에는 카테고리 설명이 얼마든지 들어 있을 수 있지만, 대부분의 인텐트에는 카테고리가 없어도 됩니다. 다음은 몇 가지 보편적인 카테고리입니다.

 

[CATEGORY_BROWSABLE]대상 액티비티가 웹브라우저를 통해 시작되도록 허용하고 이미지, 이메일 메시지 등의 링크로 참조된 데이터를 표시하게 합니다.

[CATEGORY_LAUNCHER]이 액티비티가 작업의 최초 액티비티이며, 시스템의 애플리케이션 시작 관리자에 목록으로 게재됩니다.

💡 위에 보여주었던 인텐트 액션중에 Broadcast Actions들이 브로드캐스트 수신자가 받을 수 있는 액션이다.

 

Extra

키/값 구조로 전달하는 정보, 객체를 담으면 에러 날수도 있으니 웬만하면 클래스 객체를 넣지는 말라고 한다.(데이터 클래스 Serializable, Parcelable로 전송하는거)

Parcelable이란

직렬화랑 비슷하게 객체를 전달할 수 있게 해주는 방식이다. Serializable보다 좀 복잡하다. 직렬하는 방법을 직접 선언하는 차이가 있다고 보면 된다.

import kotlinx.parcelize.Parcelize

@Parcelize
data class User(val firstName: String, val lastName: String, val age: Int) : Parcelable {
    private companion object : Parceler<User> {
        override fun User.write(parcel: Parcel, flags: Int) {
            // Custom write implementation
        }

        override fun create(parcel: Parcel): User {
            // Custom read implementation
        }
    }
}

왜 이런걸 만들었는가?

  • Serialzable보다 좀 더 빠르다고한다(얘는 java 인터페이스). Reflection을 사용해 직렬화를 처리하는데 처리 과정 중에 더미 객체가 많이 생성되어 가비지 컬렉터의 과도한 동작을 일으킬 수도 있음.
  • Parcelable은 Reflection을 사용하지 않음.(Android SDK이다) 직렬화에 대한 명시적인 표현을 하기 때문

Flags

인텐트에 대한 메타데이터 같은 기능을 한다. 액티비티를 시작하는 방법에 대해 명시 할 수도있고, 액티비티를 시작한 다음 어떻게 처리해야 할지도 명시 할 수 있다. 백스택과도 연관이 있다.

FLAG_ACTIVITY_*로 시작하는건 Context.startActivity()로 사용하면 되고, FLAG_RECEIVER_*는 Context.sendBroadcast()로 사용하면 된다. 플래그 내부에 뭐가 있는지는 너무많아서 생략하자.. setFlags로 추가하는건데 Intent가 반환된다고 한다. 내 인텐트에 담으면 된당

Intent Filter - 암시적 인텐트 수신

💡 명시적은 인텐트 필터 써도 전혀 상관 없다고 한다.

 

  • <intent-filter> 태그 안에 선언하고 내부에 들어가는 요소는 다음과 같다
  1. [action]

💡 name 특성에서 허용된 인텐트 작업을 선언합니다. 이 값은 어떤 작업의 리터럴 문자열 값이어야 하며, 클래스 상수가 아닙니다.

 

  1. [data]

💡 허용된 데이터 유형을 선언합니다. 이때 데이터 URI(scheme, host, port, path)와 MIME 유형의 여러 가지 측면을 나타내는 하나 이상의 특성을 사용합니다.

 

  1. [category]

💡 name 특성에서 허용된 인텐트 카테고리를 선언합니다. 이 값은 어떤 작업의 리터럴 문자열 값이어야 하며, 클래스 상수가 아닙니다.

 

간단한 예시

런처가 최상위 목록을 채우는 데 사용하는 실제 인텐트.
<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
암시적 인텐트를 쓸 때는 꼭 category.DEFAULT를 써야한다. 그래야 취급을 할 수 있다고 한다.
<intent-filter>
    <action android:name="android.intent.action.SEND"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain"/>
</intent-filter>

공통 인텐트

문자 메시지 -첨부 파일이 있는 SMS/MMS 메시지 작성

ACTION_SENDTO -> 보낼 사람 정해져있음
ACTION_SEND -> 대상 지정 X 수신자한테 정하게 한다.
ACTION_SEND_MULTIPLE -> 여러 데이터를 다른 사람에게

URI

sms:*<phone_number>*

smsto:*<phone_number>*

mms:*<phone_number>*

mmsto:*<phone_number>*

데이터에 넣을 수 있는 MIME 유형

 💡 text/plain, image/, video/ 해당 첨부파일이 가능

 

엑스트라

"subject"메시지 제목 문자열 (대개 MMS에 한함). "sms_body"문자 메시지 문자열. [EXTRA_STREAM]첨부할 이미지나 동영상을 가리키는 [Uri]

[ACTION_SEND_MULTIPLE] 작업을 사용하는 경우, 이 엑스트라는 첨부할 이미지/동영상을 가리키는 [Uri]의 [ArrayList]여야 합니다. → 데이터가 여러개일테니깐!

예시 - 메세지 보내기, 문자 메시지 앱에서만 취급되게 하려면 SENDTO로 대상을 지정해라.

fun composeMmsMessage(message: String, attachment: Uri) {
    val intent = Intent(Intent.ACTION_SEND).apply {
        data = Uri.parse("smsto:")  // This ensures only SMS apps respond
        putExtra("sms_body", message)
        putExtra(Intent.EXTRA_STREAM, attachment)
    }
    if (intent.resolveActivity(packageManager) != null) {
        startActivity(intent)
    }
}
<activity ...>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <data android:type="text/plain" />
        <data android:type="image/*" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

카카오톡 메세지 보내기

     String message = "보낼 내용";
     Intent intent = new Intent(Intent.ACTION_SEND);
     intent.setType("text/plain");
     intent.putExtra(Intent.EXTRA_TEXT, message );
     intent.setPackage("com.kakao.talk");
     startActivity(intent);

카카오톡 이미지 보내기

private void sendKaKao(Uri uri) {
try {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setPackage("com.kakao.talk");
startActivityForResult(intent,REQUEST_IMG_SEND);
} catch (ActivityNotFoundException e) {
        Uri uriMarket = Uri.parse("market://deatils?id=com.kakao.talk");
        Intent intent = new Intent(Intent.ACTION_VIEW, uriMarket);
        startActivity(intent);
    }
}

웹브라우저 -웹 URL 로드

웹페이지를 열려면 [ACTION_VIEW]작업을 사용하고 인텐트 데이터에서 웹 URL을 지정합니다

ACTION_VIEW 를 사용하면 되는데 이게 뷰를 띄워준다 느낌으로 생각하면 됩니당.

💡 데이터 URI로는 프로토콜을 명시해주면 되겠죠 http:*<URL>*https:*<URL>*

 

MIME 유형

"text/plain"

"text/html"

"application/xhtml+xml"

"application/vnd.wap.xhtml+xml"

  • 웹 페이지 Uri+Action_VIEW를 더해 startActivity에 띄워주면 된다.
fun openWebPage(url: String) {
    val webpage: Uri = Uri.parse(url)
    val intent = Intent(Intent.ACTION_VIEW, webpage)
    if (intent.resolveActivity(packageManager) != null) {
        startActivity(intent)
    }
}
<activity ...>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <!-- Include the host attribute if you want your app to respond
             only to URLs with your app's domain. -->
        <data android:scheme="http" android:host="www.example.com" /> 띄울 웹 사이트
        <category android:name="android.intent.category.DEFAULT" />
        <!-- The BROWSABLE category is required to get links from web pages. -->
        <category android:name="android.intent.category.BROWSABLE" />
    </intent-filter>
</activity>

💡 웹뷰가 더 낫지 않나 싶다

 

Android 디버그 브리지로 인텐트 확인

앱이 지원하려는 인텐트에 응답하는지 확인하려면 [adb]도구를 사용하여 특정 인텐트를 내보낼 수 있습니다.

  1. Android 기기를 개발용으로 설정하거나, 가상 기기를 사용하세요.
  2. 지원하고자 하는 인텐트를 처리하는 앱 버전을 설치하세요.
  3. adb를 사용하여 인텐트를 내보냅니다.예:
  4. adb shell am start -a android.intent.action.DIAL \\ -d tel:555-5555 -n org.example.MyApp/.MyActivity
  5. adb shell am start -a <ACTION> -t <MIME_TYPE> -d <DATA> \\ -e <EXTRA_NAME> <EXTRA_VALUE> -n <ACTIVITY>
  6. 필요한 인텐트 필터를 정의했다면 앱이 인텐트를 처리해야 합니다.

자세한 내용은 ADB 셸 명령어를 참조하세요.

adb 명령어 실행

개발 머신의 명령줄에서 또는 스크립트에서 adb 명령어를 실행할 수 있습니다. 사용법:

adb [-d | -e | -sserial_number]command

실행 중인 에뮬레이터가 하나뿐이거나 기기가 하나만 연결된 경우 기본적으로 adb 명령어는 연결된 기기로 전송됩니다. 여러 에뮬레이터가 실행 중이거나 여러 기기가 연결되어 있다면 -d, -e 또는 -s 옵션을 사용하여 명령어가 전송되어야 하는 대상 기기를 지정해야 합니다.

다음 명령어를 사용하여 지원되는 모든 adb 명령어의 상세 목록을 확인할 수 있습니다.