안드로이드/개념

[Android/Kotlin] 브로드캐스트 리시버(Broadcast Receiver) (1) 개념 , 리시버 생성, 리시버 선언

란서 2022. 11. 1. 16:50

브로드캐스트 리시버 (Broadcast Receiver)


브로드캐스트 리시버란?
시스템이 정기적인 사용자의 플로우 밖 (= 사용중인 앱 바깥)에서 발생하는 event (=Broadcast)
앱에 전달하도록 지원하는 안드로이드 앱 구성요소이다.

 

즉, 브로드캐스트 리시버는
시스템이 보내는 브로드 캐스트(=여러 암시적 브로드캐스트) 및  다른 안드로이드 앱이 보내는 브로드캐스트
앱이 응답할 수 있게 한다.

(현재 실행하지 않고 있는 앱의 브로드캐스트 리시버 또한 브로드 캐스트에 반응할 수 있다.)

 


- 예시

브로드캐스트 리시버가 사용될 수 있는 상황은 다음과 같다.

 

  1. 기기가 켜질 때, 앱이 시작되게 하고 싶은 경우.
    기기가 켜질 때 (=부팅할 때) 안드로이드 시스템은
    ACTION_BOOT_COMPLETED 라는 암시적 브로드 캐스트를 전송한다.

    앱에서는 해당 브로드캐스트에 응답할 수 있는
    브로드캐스트 리시버를 만들어 해야할 작업을 처리할 수 있다.


  2. 내 앱에서 브로드캐스트를 발생시키고, 해당 브로드캐스트에 응답하여 특정 작업을 처리하고 싶을 때.

    만약 앱이 백그라운드 환경에서 작업을 완료하고, 이를 사용자에게 알리고 싶을 때
    작업이 끝날 때 명시적 브로드캐스트를 전송한다.

    앱에서는 해당 브로드캐스트에 응답할 수 있는
    브로드캐스트 리시버를 만들어 해야할 작업을 처리할 수 있다.

 

시스템은 여러 상황(블루투스 연결 시, 부팅 시, 인터넷 연결 등)에서 많은 브로드 캐스트를 전송하며, 
이는 안드로이드 암시적 브로드캐스트 예외 에서 확인할 수 있다.

 

 


- 특징

브로드캐스트 리시버는 사용자 인터페이스(UI)를 표시하지 않는다.
단, 상태표시줄 알림(Notification)을 생성하여 사용자에게 event (=브로드캐스트)가 발생했음을 알릴 수도 있다.

 

브로드캐스트 리시버는 앱의 다른 구성 요소(활동, 서비스 등)로 이동하는 관문역할을 수행하는 것이 보편적이다.

따라서 최소한의 작업만 수행하도록 브로드캐스트 리시버를 구현하는 것이 권장된다.

만약 시간이 오래 걸리는 작업을 해야할 때 백그라운드에서 작업을 실행해야 한다.
하지만 브로드캐스트 리시버는 onReceive()를 완료하고 나면 해당 프로세스를 종료할 수 있기 때문에
goAsync() 또는 WorkManager를 사용하여 이를 해결할 수 있다. 좀 더 자세한 이야기는 뒤에.

 

 


 

- 브로드캐스트(Broadcast) 개요

 

Android 앱은 'Android System' 및 다른 'Android 앱'으로부터 브로드 캐스트 메시지 받거나 보낼 수 있다.

이 때 브로드캐스트브로드캐스트 리시버의 관계는 "게시 : Publication" - "구독 : Subscribe" 디자인 패턴과 유사하다.

게시 : Publication - 구독 : Subscribe  디자인 패턴
하나의 게시물에 여러명의 구독자.
자료가 게시되면 구독자들은 이에 대해 알 수 있다.

 

  • 암시적 브로드캐스트

    Android System은 System 부팅 및 기기 충전 시작과 같은 다양한 시스템 이벤트가 발생할 때 브로드 캐스트를 전송.
    Android App은 다른 앱이 관심을 가질만한 사항을 관련 앱에 알리기 위하여 "맞춤 브로드 캐스트"를 전송.

  • 명시적 브로드캐스트

    주로 앱 내부에서 브로드캐스트를 사용할 때 Intent에 브로드캐스트 리시버의 클래스 네임을
    명시하여 브로드 캐스트를 시작할 수 있다.

암시적 브로드캐스트의 경우, 여러 앱이 이에 응답해 백그라운드 작업이 실행되면서 system 성능 저하의 원인이 된다.
이를 방지하기 위해 Android 8.0 부터 시스템은 manifest에 선언된 receiver에 추가 제한을 부과한다. manifest를 사용하여 대다수 암시적 브로드 캐스트의 수신자선언할 수 없다. (대신 'Context에 등록된 수신자'를 사용해야 한다.)

 

 

 


브로드캐스트 리시버 생성 및 선언

 

- 브로드캐스트  리시버 생성

BroadcastReceiver를 확장한 클래스는 onReceive(Context, Intent?) 메서드를 재정의 하여 작업을 수행한다.

class AlarmReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if(intent != null && context !=null) {
            Intent(context, AlarmActivity::class.java).also {
                context.startActivity(it)
            }
        }
    }
}

onReceive() 메서드 내에서 최소한의 코드만을 구현하는 것이 권장된다. 

장기 실행 작업을 위한 백그라운드에서의 실행은 다음과 같은 이유로 지양된다.

 

※Broadcast Receiver에서 장기 실행 작업을 지양하는 이유
= Broadcast Receiver의 상태가 프로세스 상태에 미치는 영향 때문.

onReceive()메서드가 호출되고 완료되면 Broadcast Receiver상태활성 상태에서 비활성 상태가 된다.
해당 브로드캐스트 리시버를 호스팅한 프로세스가 브로드캐스트 리시버 외에 메모리를 사용하지 않는다면
안드로이드 시스템은 우선순위가 낮아진 해당 프로세스를 종료하고 다른 프로세스에 리소스를 할당하기 위해
메모리를 회수할 것이다.

이 과정에서 브로드캐스트 리시버에서 시작한 백그라운드 스레드는 언제든지 종료될 수 있다.

 

이러한 문제를 방지하려면 다음과 같은 방법이 있다.

 

  • Broadcast Receiver의 goAsync() 메서드를 호출

goAsync()를 호출하면 onReceive() 반환 이후에도 Broadcast Receiver를 
활성상태로 유지시켜 시간이 조금 더 걸리는 작업을 수행할 수 있다.

기본적으로 메인 스레드에서 실행되는 Broadcast Receiver는
goAsync()를 호출하면 다른 스레드에서 이동하여 실행된다.

goAsync() 메서드는 Broadcast Receiver.PendingResult 타입의 값을 반환한다.
이 값을 가지고 Broadcast Receiver를 다시 메인 스레드에서 종료시킬수도 있고, data값을 set/get 할 수도 있다.

    private const val TAG = "MyBroadcastReceiver"

    class MyBroadcastReceiver : BroadcastReceiver() {
		
        private lateinit var coroutineScope: CoroutineScope
        private lateinit var job : Job
        
        override fun onReceive(context: Context, intent: Intent) {
            val pendingResult: PendingResult = goAsync()
            job = SuperviserJob()
        	coroutineScope = CoroutineScope(job + Dispatchers.IO)
            
            doTask(pendingResult)
            
        }

        private suspend fun doTask(pendingResult:PendingResult) {
        	try {
            	delay(5000)
            } finally {
            	pendingResult.finish()
            }
        }
    }

 

일반적으로 브로드캐스트 리시버는 최대 10초 동안 실행하고,
이후에는 시스템이 해당 어플리케이션이 응답하지 않는 것으로 간주하여 ANR을 할 수 있다.

브로드캐스트 리시버의 제한시간은 goAsync() 를 사용더라도 그대로 적용된다.

브로드캐스트 리시버가가 10초이상 지속되어야 하는 긴 작업을 수행하기에는

goAsync() 메서드 또한 적절하지 않은 방법이라 할 수 있다.

 

특정 상황에서는 사용 가능한 시간이 더 길어질수 있다.
예를 들어 Broadcast를 전송한 측에서 Intent#FLAG_RECEIVER_FOREGROUND 를 사용하지 않은 경우.
최대 30초까지도 실행될 수 있다.

 

  • WorkManager를 사용하여 WorkRequest를 예약하기.

그럼에도 브로드캐스트 리시버에서 긴 작업을 수행하고 싶다면 WorkManager를 사용하여 WorkRequest를 예약하는 것도

방법일 수 있다. 

 

WorkManager와 관련한 내용은 곧 포스팅할 예정.

 

 


- 브로드캐스트 리시버 선언

브로드 캐스트를 수신하는 방법에는 2가지가 있다.

위의 방법으로 Broadcast Receiver를 만들었다면 다음과 같은 방법으로 리시버를 선언할 수 있다.

  • manifest 파일에 리시버를 선언

다음과 같이 AndroidManifest파일에 요소를 지정하여 수신자를 선언할 수 있다.

//AndroidManifest.xml

<application>
	...
    
	<receiver android:name=".receiver.AlarmReceiver"
    	android:enabled="true"
	    android:exported="false"/>
	...
    
</application>

exported 값을 false로 설정한 경우에는 외부의 비시스템 소스에서 메시지를 수신하지 않고, 안드로이드 시스템, 동일한 애플리케이션의 구성요소, 사용자 ID가 같은 애플리케이션에서 보낸 메시지만 수신할 수 있다.


또한 다음과 같이 intent filter를 선언하여 암시적 브로드캐스트에 응답하기 위한 리시버 요소를 선언할 수도 있다.

<receiver android:name=".receiver.BluetoothReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"/>
        <action android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"/>
        <action android:name="android.bluetooth.device.action.ACL_CONNECTED"/>
        <action android:name="android.bluetooth.device.action.ACL_DISCONNECTED"/>
    </intent-filter>
</receiver>


하지만 Android 8.0 부터는 몇몇 예외를 제외한 manifest파일에 암시적 브로드캐스트에 응답할 수 있는

브로드캐스트 리시버를 선언하는 것을 제한하고 있다. 

 

만약 암시적 브로드캐스트를 수신해야 하는 경우 "context에 리시버를 등록" 하는 방법을 사용할 수 있다.

 

  • context에 리시버를 등록

다음과 같이 애플리케이션 코드 내에서 context를 사용하여 리시버를 등록할 수 있다.

//1.Broadcast Receiver 클래스의 인스턴스 생성
val broadcastReceiver = BluetoothReceiver()

//2.Intent filter 생성
val intentFilter = IntentFilter().also {
	with(it) {
                addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
                addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
                addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
                addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
                addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
                addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
                addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
                addAction(BluetoothDevice.ACTION_FOUND)
                addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)
	}
}

//3.registerReceiver() 메서드 호출하여 리시버 등록
context.registerReceiver(broadcastReceiver, intentFilter)

//4.unregisterReceiver() 메서드 호출하여 리시버 등록 해제
context.unregisterReceiver(broadcastReceiver)

1. BroadCastReceiver 클래스의 인스턴스를 만든다.

2. Intent Filter를 생성하고, 수신하고자 하는 Action을 추가한다.

3. Context의 registerReceiver(BroadcastReceiver, IntentFilter) 메서드를 호출하여 리시버를 등록한다.

4. unregisterReceiver(BroadcastReceiver) 메서드를 호출하여 더 이상 리시버가 필요하지 않은 시점에 등록을 취소한다.

 

 


 

브로드캐스트 리시버와 브로드캐스트의 개념에 대해 정리하고

브로드 캐스트 리시버를 생성하고 이를 애플리케이션에 선언 및 등록하는 방법을 알아보았다.

 

어플리케이션은 이렇게 시스템이나 다른 앱에서 발생시키는 이벤트를 수신하고 적절한 작업을 수행할 수 있다.

그렇다면 애플리케이션이 어떻게 브로드캐스트를 발생시킬 수 있는지에 대해 다음 포스팅에서 알아보고자 한다.

 

상황에 따라 브로드캐스트를 적절하게 전송하고 ,이를 전송하는 코드를 구현한다면

프로세스 간에 메시지를 주고 받을 수도 있을 것이다.