안드로이드/Deep in the AOS

Intent :: 컴포넌트간의 통신을 맡은 중대한 녀석 [ Deep in the AOS ]

Intent는 메시징 객체로 다른 앱 구성 요소로부터 작업을 요청하는 데 사용할 수 있습니다.

Intent가 구성 요소 사이의 통신을 하는 데 여러 방식이 있지만 기본적인 사용은 크게 세 가지로 나뉩니다.

  1. 액티비티 시작
    • Activity에서 새로운 Instance를 시작하려면 Intent를 startActivity()로 전달하면 됩니다.
      Intent는 시작할 Activity에 모든 필요한 데이터를 담습니다.
      Activity가 완료되었을 때 결과를 수신하려면 startActivityForResult를 사용하면 되는데 이는 Deprecated되었습니다.

      🧐 왜 startActivityForResult를 Deprecated ?
      1. AndroidX Activity와 Fragment에 도입된 Activity Result API 사용을 적극 권장하며 결과를 얻는 Activity를 실행하는 로직을 사용할 때 메모리 부족으로 인해 프로세스와 Activity가 사라질 수 있다고 합니다. (특히 카메라 같은 메모리 많이 사용하는 작업은 소멸 확률이 매우 높음)
      그래서 ! 저는 registerForActivityResult() 메서드를 사용합니다.

      😠 근데 그래서 왜 startActivityForResult가 Deprecated 된거야?
      startActivityForResult 메서드를 사용하면 항상 onActivityResult에서 콜백을 처리해야 합니다.
      두 개가 같은 곳에서 구현이 되는데, 앞서 설명했던 것 처럼 메모리가 부족해 프로세스와 Activity가 사라질 수 있습니다.
      Activity가 종료되고 다시 만들어질 때 Activity에 Result를 기다리는 상황을 알려야합니다.
      이를 위해 Activity의 실행과 Result를 받는 Callback을 분리해 만들어주는 것입니다.
      이것을 ActivityResultLauncher 객체가 해주며 registerForActivityResult 메서드는 콜백을 등록하는 역할을 합니다.
      이렇게 구현했을 때 메모리가 부족해 결과를 받을 Activity가 소멸되더라도 registerForActivityResult가 다시 콜백을 해주기 때문에 결과값을 받아올 수 있습니다.
  2. 서비스 시작
    • Service는 사용자 인터페이스 없이 백그라운드에서 작업을 수행하는 요소입니다.
      API 21 이상 부터는 JobScheduler로 서비스를 시작할 수 있고, 21 이하는 Service 클래스의 메서드를 사용해 Service를 시작합니다. Intent를 startService()에 전달하면 되며 Intent에는 시작할 서비스의 필요한 데이터를 담고 있습니다.
      서비스가 Client-Servce 인터페이스로 되어 있으면 다른 구성 요소로부터 서비스에 bindinggkfuaus Intent를 bindServce()에 전달하면 됩니다.
  3. 브로드캐스트 전달
    • Broadcast는 모든 앱이 수신할 수 있는 메시지입니다.
      시스템이 부팅될 때, 기기가 충전을 시작할 때, 기기가 배터리가 부족할 때 다양한 시스템 이벤트에 대한 다양한 Broadcast를 전달합니다. Intent를 sendBroadcast() 또는 sendOrderedBroadcast()에 전달하면 다른 앱에 Broadcast를 전달할 수 있습니다.

Intent와 앱 구성 요소 4대 컴포넌트의 상호작용 그림

이런 Intent에는 두 가지 유형이 있습니다.

  1. 명시적 인텐트
    • 인텐트를 충족하는 앱이 무엇인지 지정하며 이를 위해 대상 앱의 패키지 이름 또는 완전한 클래스 이름을 제공합니다.
      명시적 인텐트는 일반적으로 앱 안에서 구성 요소를 시작할 때 사용합니다.시작하고자 하는 Activity 또는 Service의 클래스 이름을 알고 있기 때문입니다.
  2. 암시적 인텐트
    • 특정 구성 요소의 이름을 대지 않지만, 그 대신 수행할 일반적인 작업을 선언해 다른 앱의 구성 요소가 이를 처리할 수 있도록 합니다. 예를 들어 카메라를 사용하는 기능이 필요할 때 암시적 인텐트를 사용해 해당 기능을 갖춘 다른 앱을 알려주며 요청할 수 있습니다.
    • Android 시스템에서 시작할 적절한 구성 요소를 찾으며, Intent의 내용을 기기에 있는 다른 여러 앱의 Manifest에서 선언된 Intent-Filter와 비교하며 해당 Intent와 일치하는 Intent-Filter가 있으면 시스템에서 해당 구성 요소를 시작하고 이를 Intent 객체로 전달합니다.
      Intent-Filter는 해당 구성 요소가 수신하고자 하는 Intent의 유형을 나타냅니다.

암시적 인텐트가 다른 액티비티를 시작하는 방법

 

코드를 통해 명시적 인텐트와 암시적 인텐트에 대해 확실하게 알아보겠습니다.

Android Developer에선 이런 코드를 통해 설명합니다.

// 명시적 인텐트
val downloadIntent = Intent(this, DownloadService::class.java).apply {
    data = Uri.parse(fileUrl)
}
startService(downloadIntent)

DownloadService가 제 프로젝트 안에 있으며 정확한 이름과 Class를 알기에 명시적 인텐트를 사용할 수 있습니다.

앱에 Context를 제공하고 구성 요소에 Class 객체를 전달해 앱 내의 DownloadService Class를 명시적으로 시작합니다.

 

// 암시적 인텐트
val sendIntent = Intent().apply {
    action = Intent.ACTION_SEND
    putExtra(Intent.EXTRA_TEXT, textMessage)
    type = "text/plain"
}

if (sendIntent.resolveActivity(packageManager) != null) {
    startActivity(sendIntent)
}

startActivity를 호출할 때 시스템이 설치된 앱을 모두 살펴보고 해당 Intent를 처리할 수 있는 앱이 어느 것인지 알아봅니다.

이 코드에선 ACTION_SEND 작업이 있는 Intent 이며 text/plain 데이터가 담겨있는 것입니다.

이것을 처리할 수 있는 앱이 하나면, 해당 앱이 즉시 열리고 이 앱에 정보가 담긴 Intent가 주어집니다.

만약 여러 개면, 시스템은 대화상자를 표시하며 사용자에게 앱을 선택할 수 있게 제공합니다.

앱 선택을 강제할 수 있는 방법도 있습니다.

createChooser()를 사용해 Intent를 만들고 StartActivity()에 전달하여 해당 Intent에 응답하는 앱의 목록을 대화상자에 표시할 수 있습니다.

 

마지막으로 Pending Intent에 대해 알아보겠습니다.

Pending Intent는 Intent 객체 주변을 감싸는 Wrapper입니다.

Pending Intent의 목적은 외부 앱에 권한을 허가해 안에 들어 있는 Intent를 마치 본인 앱의 자체 프로세스에서 실행하는 것처럼 사용하는 것입니다.

컴포넌트에서 다른 컴포넌트에게 작업을 요청하는 Intent를 미리 생성시키고 만든다는 점과 특정 시점에 자신이 아닌 다른 컴포넌트들이 PendingIntent를 사용해 다른 컴포넌트에게 작업을 요청시키는 데 사용됩니다.

  ex) 사용자가 Notification으로 어떤 작업을 수행할 때 Intent가 실행되도록 선언 (Notification Manager가 Intent 실행)

  ex) 사용자가 앱 위젯으로 어떤 작업을 수행할 때 Intent가 실행되도록 선언 (Home Screen이 Intent 실행)

  ex) 지정된 시간에 Intent가 실행되도록 선언 (AlramManager가 Intent 실행)

이 Pending Intent는 앱에 push message를 송수신할 때 많이 사용하게 될 것입니다.