[안드로이드] 컴포넌트 - 브로드캐스트 리시버

hee09·2021년 11월 5일
0

안드로이드

목록 보기
18/20
post-thumbnail
post-custom-banner

브로드캐스트 리시버

1. 브로드캐스트 개념

안드로이드 앱은 시스템이나 다른 앱으로부터 오는 브로드캐스트 message를 받거나 보낼 수 있습니다. 이것은 publish-subscribe 디자인 패턴과 유사합니다.

예를 들어, 휴대폰이 부팅되거나 충전 상태가 바뀌는 등의 다양한 시스템 이벤트가 발생할 때 시스템은 브로드캐스트를 전송합니다. 앱들은 또한 다른 앱이 자신의 앱에 관심 있는 내용을 알리기 위해서 custom 브로드캐스트를 전송할 수 있습니다.

앱은 특정한 브로드캐스트를 받도록 등록할 수 있습니다. 그리고 브로드캐스트가 전송되면, 시스템은 이 특정한 브로드캐스트를 받도록 등록한 앱으로 브로드캐스트를 자동적으로 전송합니다.

일반적으로, broadcast는 일반적인 사용자의 흐름 밖에서 전반적인 앱의 메세지 시스템으로 사용될 수 있습니다. 그러나 시스템 성능이 저하되도록 백그라운드에서 브로드캐스트에 응답하고 작업을 실행하는 것을 남용하면 안됩니다.


2. 브로드캐스트 리시버 이해

이러한 broadcast를 받기 위해서는 브로드캐스트 리시버가 필요합니다.

브로드캐스트 리시버를 흔히 "이벤트 모델로 수행되는 컴포넌트"라고 부릅니다.
이벤트 모델로 수행되는 컴포넌트를 이해하기 위해서는 인텐트에 대해서 알아야합니다. 안드로이드 컴포넌트 중 액티비티, 서비스, 브로드캐스트 리시버는 이 인텐트에 의해 실행이 됩니다.

참조
지난 포스팅 - 인텐트

여기서 브로드캐스트 리시버 인텐트를 이해하기 위해서 액티비티 인텐트의 동작과 비교하겠습니다.

액티비티 인텐트

액티비티의 경우 1번 처럼 인텐트가 발생하고, 시스템에 이 인텐트로 인해 실행할 액티비티가 하나일 때는 해당 액티비티가 정상적으로 실행됩니다. 하지만 2번 예처럼 인텐트 정보와 맞는 액티비티가 없다면 인텐트가 발생한 곳에서 에러가 발생합니다. 또한, 3번 예처럼 인텐트가 발생하였는데 실행할 액티비티가 두 개 이상이라면 사용자 선택으로 하나만 실행됩니다.

브로드캐스트 리시버 인텐트

브로드캐스트 리시버는 인텐트가 발생하여 실행될 브로드캐스트 리시버가 없더라도 에러가 발생하지 않고, 실행될 브로드캐스트 리시버가 여러 개라면 모두 실행됩니다. 따라서 흔히 브로드캐스트 리시버를 "이벤트 모델로 수행되는 컴포넌트"라고 부릅니다. "없으면 말고, 있으면 모두 실행" 한다고 이해하면 됩니다.


3. 브로드캐스트 리시버의 이용

안드로이드의 컴포넌트에 해당하는 액티비티는 화면 출력이 목적이며, 서비스는 백그라운드 업무 로직 구현이 목적이고, 콘텐츠 프로바이더는 앱 간의 데이터 공유가 목적입니다. 하지만 브로드캐스트 리시버는 특정한 목적의 업무가 없습니다.

또한, 브로드캐스트 리시버는 특정 업무를 처리하기에도 부적절합니다. 인텐트로 인해 한번 생성되어 실행된 브로드캐스트 리시버는 10초 이내에 업무 처리가 종료되어야 한다는 규칙이 있습니다.

단지, 브로드캐스트 리시버는 이벤트 모델(없으면 말고, 있으면 모두 실행되고)로 수행되는 컴포넌트가 필요해서 만들어 졌습니다. 따라서 대부분 간단한 업무를 처리하거나 아니면 브로드캐스트 리시버에서 다른 액티비티나 서비스를 실행하는 역할로 사용합니다.


4. 브로드캐스트 리시버 작성 방법

브로드캐스트 리시버는 BroadcastReceiver를 상속받아 작성하는 클래스이며, 이 클래스 내에 onReceive(Context context, Intent intent 함수를 구현하면 됩니다. onReceive() 함수는 브로드캐스트 리시버가 인텐트로 인해 수행될 때 자동으로 호출되는 함수입니다.

private const val TAG = "MyBroadcastReceiver"

class MyBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        StringBuilder().apply {
            append("Action: ${intent.action}\n")
            append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
            toString().also { log ->
                Log.d(TAG, log)
                Toast.makeText(context, log, Toast.LENGTH_LONG).show()
            }
        }
    }
}

이제 이 브로드캐스트 리시버를 AndroidManifest.xml에 등록하거나 코드에서 동적으로 등록하고 해제하여 사용할 수 있습니다.


4.1 AndroidManifest.xml

브로드캐스트 리시버도 안드로이드 컴포넌트 클래스이므로 다음과 같이 AndroidManifest.xml에 등록하여 사용할 수 있습니다.

<receiver android:name=".MyBroadcastReceiver"  android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
    </intent-filter>
</receiver>

태그는 <receiver>를 사용하는데 위의 예제에서는 <intent-filter>에 action을 설정하여 MyBroadcastReceiver가 시스템에서 실행하는 broadcast 인텐트에 반응하여 실행되도록 만든 것입니다.


4.2 코드에서 동적으로 등록 - 해제

우선 BroadcastReceiver 객체를 만듭니다. 그 후 IntentFilter를 생성한 후 registerReceiver(BroadcastReceiver, IntentFilter) 메서드의 인자로 넣어 해당 메서드를 호출하면 됩니다.

val br: BroadcastReceiver = MyBroadcastReceiver()

val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
    addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
}
registerReceiver(br, filter)

위와 같이 동적으로 등록하는 브로드캐스트 리시버는 등록되는 곳의 context가 유효할 때까지 broadcast를 받습니다. 예를 들어서 activity에서 등록하였다면 activity가 destroyed 되기 전까지 broadcast를 받습니다.

더 이상 필요없거나 context가 더 이상 유효하지 않다면 unregisterReceiver(BroadcastReceiver) 메서드를 사용하여 broadcast의 수신을 중지해야합니다.

receiver를 등록했다면 해제하는 것도 중요한데, 만약 activity의 onCreate()에서 등록하였다면 activity의 context 밖으로 receiver가 유출되지 않도록 onDestroy()에서 해제해야 합니다. 그리고 만약 onResume()에서 receiver를 등록하였다면, 여러번 등록되는 것을 방지하기 위하여 onPause()에서 해제해야 합니다. 사용자가 history stack을 통해 다시 되돌아올 경우 호출되지 않으므로 onSaveInstanceState(Bundle)에서 등록을 해제하면 안됩니다.


5. 시스템 브로드캐스트 인텐트

브로드캐스트 리시버는 앱 내부에서 사용하려고 자주 만들지만, 시스템에서 실행하는 인텐트에 반응하여 각종 시스템 상황을 감지하려고도 자주 사용합니다. 이 중 몇 가지를 보겠습니다.

5.1 부팅 완료

스마트폰의 부팅 완료 시점에 앱이 동작하여 간단한 업무를 수행하거나 서비스 등을 구동해야 할 때가 있는데, 이는 부팅 완료 시점에 시스템에서 발생시키는 브로드캐스트 인텐트에 반응할 브로드캐스트 리시버만 정의해주면 쉽게 구현할 수 있습니다.

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<receiver android:name=".MyBroadcastReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

AndroidManifest.xml 파일에 등록할 때 시스템에서 띄우는 인텐트의 Action 문자열과 같은 문자열로 intent-filter를 구성하여 등록하면 됩니다. 이 때 action 문자열은 BOOT_COMPLETED 입니다. 부팅 완료 시점에 브로드캐스트 리시버가 동작하게 하려면 퍼미션이 필요하여 uses-permission을 사용하였습니다.


5.2 화면 On/Off

화면이 On/Off 되는 상황에 시스템에서 브로드캐스트 인텐트를 발생해 주며 앱에서 브로드캐스트 리시버를 이용하여 이를 감지할 수 있습니다. AndroidManifest.xml 파일에 <receiver> 태그를 등록하면 실행되지 않고, 코드에서 동적으로 등록해야만 실행됩니다.

val broadCastScreenOn = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        // 화면이 켜지면 수행될 코드 작성
    }

}

val screenOnIntentFilter = IntentFilter(Intent.ACTION_SCREEN_ON)
registerReceiver(broadCastScreenOn, screenOnIntentFilter)

코드에서 동적으로 등록하고 해제하는 예제와 같고, 단지 IntentFilter의 Action 문자열로 Intent.ACTION_SCREEN_ON(화면 켜짐) 또는 Intent.ACTION_SCREEN_OFF(화면 꺼짐)을 사용하면 registerReceiver의 인자로 주면 됩니다. 그리고 등록했으면 반드시 unregisterReceiver() 함수를 사용하여 등록을 해제해야합니다.


5.3 배터리

배터리와 관련된 각종 상황이 발생할 때 시스템에서는 앱에서 이 상황을 인지할 수 있도록 브로드캐스트 인텐트를 발생시킵니다. 배터리와 관련된 브로드캐스트 인텐트의 action 문자열은 다음과 같습니다.

  • android.intent.action.BATTERY_LOW : 스마트폰의 배터리가 낮은 상태가 되었을 때
  • android.intent.action.BATTERY_OKAY : 스마트폰의 배터리가 낮은 상태에서 벗어날 때
  • android.intent.action.BATTERY_CHANGED : 스마트폰 배터리의 충전 상태가 변경되었을 때
  • android.intent.action.ACTION_POWER_CONNECTED : 케이블 등으로 외부 전원공급이 연결되었을 때
  • android.intent.action.ACTION_POWER_DISCONNECTED : 외부 전원공급이 끊어질 때

여러 action 문자열을 등록하여 실행되는 브로드캐스트 리시버에서 onReceiver() 함수에서 action 문자열을 추출하여 어떤 인텐트 정보로 실행된 것인지 구분할 수 있습니다.

const val TAG: String = "BatteryBroadcastReceiver"

class BatteryBroadcastReceiver: BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        val intentAction = intent?.action
        
        when(intentAction) {
            Intent.ACTION_POWER_CONNECTED -> {
                // 충전 시 실행할 코드
                Log.d(TAG, "충전 중")
            }
            Intent.ACTION_POWER_DISCONNECTED -> {
                // 충전 해제 시 실행할 코드
                Log.d(TAG, "충전 해제")
            }
        }
        
    }
}

그 후 액티비티나 서비스에서 registerReceiver, unregisterReceiver()를 적절히 사용하면 됩니다.

val batteryBroadcastReceiver = BatteryBroadcastReceiver()

val powerConnectedIntentFilter = IntentFilter(Intent.ACTION_POWER_CONNECTED)
val powerDisconnectedIntentFilter = IntentFilter(Intent.ACTION_POWER_DISCONNECTED)

registerReceiver(batteryBroadcastReceiver, powerConnectedIntentFilter)
registerReceiver(batteryBroadcastReceiver, powerDisconnectedIntentFilter)

6. Broadcast 보내기

브로드캐스트를 전송하기 위해서 다음과 같은 세 가지 방법이 존재합니다.
인텐트 부분은 다른 컴포넌트와 같고, putExtra를 이용하여 데이터를 넘길 수도 있습니다.

  1. sendOrderedBroadcast(Intent, String)
    한번에 하나의 브로드캐스트 리시버에게 브로드캐스트를 전송합니다. 각 리시버가 차례로 실행될 때, 다음 리시버로 결과를 전파하거나 다른 리시버로 전달되지 않도록 브로드캐스트를 완전히 중단할 수 있습니다.

  2. sendBroadcast(Intent)
    정의되지 않은 순서로 브로드캐스트를 모든 리시버에게 전송합니다. 일반적으로 이것을 사용합니다. 리시버가 다른 리시버로부터의 결과를 얻을 수 없고, 브로드캐스트로부터 수신된 데이터를 전파하거나, 브로드캐스트를 중단할 수 없다는 것을 의미합니다.

  3. LocalBroadcastManager.sendBroadcast
    이 메서드는 송신자와 동일한 앱에 있는 receiver들에게 브로드캐스트를 전송합니다. 다른 앱으로 브로드캐스르틑 전송할 필요가 없으면 이것을 사용하면 됩니다. 구현이 더 효율적이고 브로드캐스트를 수신하거나 전송할 수 있는 다른 앱과 관련된 보안 문제를 신경쓰지 않아도 됩니다.


7. 버전별 변경

안드로이드 플랫폼이 발전하며 시스템 브로드캐스트에 점진적으로 변화가 생겼습니다.

Android 9

  • 안드로이드 9(API Level 28)부터 NETWORK_STATE_CHANGED_ACTION broadcast는 사용자의 위치 또는 개인 식별 데이터에 대한 정보를 수신하지 않습니다.

  • 추가적으로 Wifi 시스템 브로드캐스트는 SSID, BSSID, 연결 정보등을 포함하지 않습니다. 이러한 정보를 얻기 사용하기 위해서는 getConnectionInfo()를 사용해야 합니다.

Android 8

  • 안드로이드 8(API Level 26)부터는 시스템은 AndroidManifest에 정의된 브로드캐스트 리시버에 제한을 가합니다. 암시적 인텐트에 의한 브로드캐스트 리시버의 실행 제한입니다. 모두 제한된 것이 아니고, 앱에서 암시적 방법으로 BroadcastReceiver를 실행하는 부분만 제한되었습니다. 이를 위해 코드에서 동적으로 등록하는 방법(registerReceiver)을 사용해야 합니다.
    (백그라운드에서 암시적인 방법으로 실행하는 것을 제한한 것으로 코드에서 동적으로 등록하는 것은 액티비티나 서비스와 같은 컴포넌트가 실행되면서 등록되는 것이기에 백그라운드에서 실행되는 것이 아닙니다.)

    • 예를 들자면, 시스템의 특정 상황(부팅 완료 등)을 파악하기 위해 AndroidManifest.xml에 등록해 놓은 것은 전혀 제한이 없고 위의 예제(5번)처럼 사용하면 됩니다.

    • 제한된 것은 아래와 같이 AndroidManifest.xml에 BroadcastReceiver를 등록하고 코드에서 암시적인 방법으로 실행하는 것을 제한한 것입니다.

AndroidManifest.xml

<receiver android:name=".MyBroadcastReceiver">
    <intent-filter>
        <action android:name="kr.co.lee.ACTION_MY_RECEIVER"/>
    </intent-filter>
</receiver>

Activty 파일

val intent = Intent("kr.co.lee.ACTION_MY_RECEIVER")
sendBroadcast(intent)

참조
이 글은 깡쌤의 안드로이드 프로그래밍을 보며 작성하였습니다.
안드로이드 developer - Broadcasts overview

틀린 부분을 댓글로 남겨주시면 수정하겠습니다..!!

profile
되새기기 위해 기록
post-custom-banner

0개의 댓글