사전 브로드캐스트 리시버
인터페이스

브로드캐스트 리시버

gabury1고친 사람 github-actions[bot]

브로드캐스트 리시버는 안드로이드 앱이 기기에서 일어난 일을 전해 듣고 반응하게 해 주는 부품입니다. 충전을 시작하거나 기기가 켜지면 운영체제가 그 소식을 널리 알립니다. 그 소식을 받겠다고 등록해 둔 앱의 리시버가 깨어나 짧은 일을 합니다. 화면은 없습니다.

쉽고 빠른 이해

무슨 일을 하는 물건인가 — 기기에서 생긴 일을 앱에 알려 줍니다. 기기가 켜졌다는 소식을 받은 알람 앱이 꺼지기 전에 맞춰 둔 알람을 다시 거는 식입니다.

왜 이렇게 하나 — 앱이 「충전 시작했나」를 계속 물어보며 돌면 전지가 닳습니다. 일이 생긴 쪽이 한 번만 알립니다. 관심 있는 앱만 그때 깨어나니 전지를 덜 씁니다.

어떻게 도나

  1. 앱이 「이런 소식을 받겠다」고 운영체제에 등록해 둡니다
  2. 그 일이 생기면 운영체제가 등록한 앱마다 소식을 전합니다
  3. 리시버의 메서드 하나가 불려 짧은 일을 하고 끝납니다

대가 — 받는 메서드는 화면을 그리는 흐름 위에서 돕니다. 오래 붙잡으면 앱이 멈춥니다. 긴 일은 다른 곳에 넘겨야 합니다. 앱이 꺼져 있을 때 받을 수 있는 소식도 몇 개 안 남았습니다.

상세

이 절은 방송이 무엇인지부터 잡습니다. 이어서 리시버를 쓰는 코드와 등록하는 두 방법을 봅니다. 그다음 받는 메서드가 짧아야 하는 까닭과 운영체제가 방송을 줄여 온 까닭을 봅니다. 끝에서는 백엔드의 발행-구독과 나란히 놓아 봅니다.

방송과 리시버

안드로이드에서 방송(broadcast)은 받을 사람을 정하지 않고 보내는 알림입니다. 보내는 쪽은 누가 받는지 모릅니다. 「기기가 켜졌다」, 「비행기 모드가 바뀌었다」 같은 소식이 이렇게 퍼집니다.

브로드캐스트 리시버는 그 방송을 받는 쪽입니다. 앱은 어떤 방송을 받고 싶은지 미리 운영체제에 알려 둡니다. 방송이 나가면 운영체제가 조건이 맞는 리시버를 골라 불러 줍니다.

알려 주는 쪽이 없으면 앱이 직접 물어봐야 합니다. 충전기가 꽂혔는지 알려고 몇 초마다 상태를 확인하며 계속 돌아야 합니다. 이렇게 되풀이해 묻는 방식이 폴링입니다. 도는 동안 전지가 닳습니다. 방송을 쓰면 앱은 가만히 있다가 일이 생겼을 때만 깨어납니다.

방송을 보내는 쪽은 대개 운영체제입니다. 기기가 켜질 때, 충전기를 꽂을 때, 언어 설정이 바뀔 때 운영체제가 방송을 냅니다. 앱도 방송을 보낼 수 있습니다.

네트워크에서 말하는 브로드캐스트와는 다른 이야기입니다. 네트워크의 브로드캐스트는 한 망의 모든 기기에 패킷을 보내는 일입니다. 이 문서의 방송은 기기 하나 안에서 앱들 사이로 전해지는 알림입니다.

네 가지 앱 컴포넌트 중 하나

안드로이드 앱은 운영체제가 알아보고 직접 띄우는 부품 몇 가지로 이루어집니다. 이 부품들이 앱 컴포넌트입니다. 화면을 맡는 액티비티, 뒤에서 작업을 이어 가는 서비스, 데이터를 다른 앱에 내주는 콘텐츠 프로바이더, 그리고 브로드캐스트 리시버가 있습니다.

앱 컴포넌트는 개발자가 객체를 직접 만들지 않는 부품입니다. 개발자는 클래스를 써 두기만 합니다. 객체를 만들고 메서드를 부르는 것은 운영체제입니다.

이렇게 운영체제가 개발자 코드를 불러 주는 방식이 제어의 역전입니다. 서블릿 컨테이너가 요청이 올 때마다 서블릿의 메서드를 부르는 것과 같은 꼴입니다. 리시버에서는 요청 대신 방송이 옵니다.

방송을 싣는 인텐트

방송 하나는 인텐트 객체 하나에 실려 갑니다. 인텐트는 안드로이드에서 「이런 일이 있다」, 「이 일을 해 달라」를 담아 운영체제에 건네는 메시지입니다.

인텐트에서 방송의 종류를 가르는 것은 액션(action)입니다. 액션은 android.intent.action.BOOT_COMPLETED 같은 문자열 하나입니다. 이 값은 「기기가 켜졌다」는 뜻입니다.

리시버는 받고 싶은 액션을 인텐트 필터에 적습니다. 인텐트 필터는 「이런 인텐트면 나에게 보내 달라」는 조건입니다. 운영체제는 방송의 액션과 필터를 맞춰 보고 맞는 리시버만 부릅니다.

리시버를 쓰는 코드

아래는 코틀린으로 쓴 가장 작은 리시버입니다. 기기가 켜지면 알람을 다시 거는 앱을 떠올리면 됩니다. 안드로이드는 기기가 꺼지면 앱이 걸어 둔 알람을 지우기 때문입니다.

Kotlin
class BootReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        AlarmScheduler.restore(context)
    }
}

BroadcastReceiver 를 물려받아 클래스를 만듭니다. 메서드는 onReceive 하나만 채웁니다. 방송이 오면 운영체제가 이 메서드를 부릅니다. 이렇게 운영체제가 불러 주는 메서드를 콜백이라고 합니다.

인자 intent 에는 방송의 인텐트가 들어옵니다. AlarmScheduler.restore 는 예로 든 앱 코드입니다. 저장해 둔 알람을 읽어 다시 거는 함수라고 보면 됩니다.

등록하는 두 방법

클래스를 써 두기만 해서는 운영체제가 이 리시버를 모릅니다. 어떤 방송을 받을지 등록해야 합니다. 등록하는 방법은 둘입니다. 둘은 방송을 받는 기간이 다릅니다.

첫째 방법은 매니페스트에 적는 것입니다. 매니페스트는 앱 꾸러미 안에 든 설정 파일입니다. 앱이 가진 부품과 필요한 권한을 여기에 적습니다. XML(Extensible Markup Language, 확장 가능한 마크업 언어)로 씁니다.

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

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

uses-permission 은 기기가 켜졌다는 방송을 받을 권한을 달라는 줄입니다. receiver 는 앞의 BootReceiver 를 리시버로 올립니다. 그 안의 intent-filter 가 받을 액션을 적습니다. exported 는 뒤의 「누가 보낼 수 있나」 소절에서 봅니다.

이렇게 적으면 앱이 꺼져 있어도 방송을 받습니다. 운영체제가 앱의 프로세스를 새로 띄우고 리시버를 부르기 때문입니다. 프로세스는 운영체제가 앱 하나를 돌리려고 내주는 실행 단위입니다.

sequenceDiagram
    participant 운영체제
    participant 앱 프로세스
    participant 리시버
    Note over 운영체제: 기기가 켜졌다는 방송을 낸다
    운영체제->>앱 프로세스: 매니페스트에 적힌 앱이라 띄운다
    운영체제->>리시버: 앱 프로세스 안에 객체를 새로 만든다
    운영체제->>리시버: onReceive 를 부른다
    Note over 리시버: 돌아오면 객체는 버려진다

그림은 앱이 꺼져 있던 경우입니다. 매니페스트에 적은 리시버는 방송이 올 때마다 객체가 새로 만들어집니다. 메서드가 끝나면 운영체제는 그 객체를 더 쓰지 않습니다.

둘째 방법은 앱이 떠 있는 동안 코드로 거는 것입니다. 화면이 보이는 동안만 충전 상태를 알고 싶을 때 씁니다.

Kotlin
val powerReceiver = PowerReceiver()
val filter = IntentFilter(Intent.ACTION_POWER_CONNECTED)
registerReceiver(powerReceiver, filter)

unregisterReceiver(powerReceiver)

PowerReceiver 는 앞의 BootReceiver 처럼 BroadcastReceiver 를 물려받아 써 둔 클래스입니다. 첫 줄에서 앱이 그 객체를 직접 만듭니다. 매니페스트 방법과 다른 점이 이것입니다. 운영체제가 방송마다 객체를 새로 만들지 않고, 앱이 넘긴 이 객체 하나를 뗄 때까지 계속 부릅니다.

둘째 줄은 「충전기가 꽂혔다」는 액션을 받는 필터를 만듭니다. 셋째 줄이 리시버를 겁니다. 마지막 줄은 리시버를 뗍니다. 거는 줄은 화면이 보일 때, 떼는 줄은 화면이 가려질 때 부르는 식으로 짝을 맞춥니다.

이 코드는 대개 화면을 맡는 액티비티 안에서 부릅니다. 운영체제는 리시버를 그 화면에 딸린 것으로 기억하고, 뗄 때까지 붙들고 있습니다. 떼는 것을 잊으면 화면이 닫혀도 리시버와 그 화면 객체가 함께 남습니다. 화면은 닫혔는데 메모리에서 안 풀리는 메모리 누수가 됩니다.

두 방법을 표로 모으면 이렇습니다.

매니페스트에 적는다 실행 중에 코드로 건다
언제 등록되나 앱을 설치할 때 registerReceiver 를 부를 때
언제까지 받나 앱이 설치돼 있는 동안 unregisterReceiver 를 부를 때까지
앱이 안 떠 있을 때 운영체제가 앱을 띄워 전한다 못 받는다

표의 마지막 줄이 둘을 고르는 기준입니다. 앱이 꺼져 있을 때도 받아야 하면 매니페스트에 적습니다. 화면이 떠 있는 동안만 필요하면 코드로 겁니다.

onReceive 는 짧게 끝낸다

onReceive 는 메인 스레드에서 돕니다. 메인 스레드는 화면을 그리고 터치를 받는 스레드입니다. 앱마다 하나뿐입니다.

그래서 onReceive 가 오래 걸리면 그동안 화면이 멈춥니다. 너무 오래 붙잡으면 운영체제가 앱이 멈췄다고 보고 「응답 없음」 창을 띄웁니다. 이것을 ANR(Application Not Responding, 앱 응답 없음)이라고 합니다.

또 하나의 까닭은 프로세스의 수명입니다. 운영체제는 onReceive 가 돌아오면 그 리시버를 끝난 것으로 봅니다. 그 프로세스에 화면이나 다른 부품이 없으면 메모리를 돌려받으려고 곧 끝낼 수 있습니다.

그러니 onReceive 안에서 스레드를 새로 띄워 긴 일을 맡기면 위험합니다. 메서드는 돌아왔으니 운영체제는 할 일이 끝났다고 봅니다. 프로세스가 끝나면 그 스레드도 함께 사라집니다.

긴 일은 작업 예약으로 넘깁니다. WorkManager는 작업 예약을 맡는 라이브러리입니다. 「이 일을 해 달라」를 등록해 두면 앱이 꺼져 있어도 알맞은 때에 그 일을 돌려 줍니다. 리시버는 작업을 등록만 하고 바로 돌아옵니다.

조금만 더 붙잡고 싶으면 goAsync 를 부릅니다. 이 메서드는 onReceive 가 돌아온 뒤에도 방송을 끝나지 않은 것으로 잡아 둡니다. 이때도 운영체제는 10초 안에 끝내기를 기대합니다.

운영체제가 방송을 줄여 온 까닭

방송은 받는 앱을 정해서 보낼 수도 있고, 안 정하고 보낼 수도 있습니다. 받는 앱을 정해서 보내면 명시적 방송입니다. 정하지 않고 액션만 적어 보내면 암시적 방송입니다. 운영체제가 내는 방송은 대개 암시적입니다.

암시적 방송은 매니페스트에 적은 리시버를 전부 깨웁니다. 예전에는 네트워크 연결이 바뀌었다는 방송(android.net.conn.CONNECTIVITY_CHANGE)을 받겠다고 매니페스트에 적어 둔 앱이 많았습니다. 신호가 오락가락하면 그때마다 그 앱들이 한꺼번에 떠올랐습니다.

앱이 뜰 때마다 메모리와 전지를 씁니다. 그래서 안드로이드 8.0부터는 매니페스트에 적은 리시버로 암시적 방송을 대부분 받지 못하게 막습니다. 기기가 켜졌다는 방송처럼 몇 가지만 예외로 남겨 두었습니다.

막힌 방송을 받고 싶으면 두 길이 있습니다. 앱이 떠 있는 동안 코드로 등록해서 받거나, 조건을 걸어 작업을 예약합니다. 「네트워크가 붙으면 동기화한다」는 방송을 기다릴 일이 아니라 WorkManager 에 조건으로 거는 일입니다.

누가 보낼 수 있나

리시버는 앱 밖에서 들어오는 입구입니다. 다른 앱의 방송도 받게 열어 두면, 아무 앱이나 그 액션을 적어 가짜 방송을 보낼 수 있습니다.

매니페스트의 exported 가 이 문을 여닫습니다. true 면 다른 앱과 운영체제의 방송을 받습니다. false 면 내 앱 안에서 보낸 방송만 받습니다. 코드로 등록할 때도 같은 뜻의 표시를 붙입니다.

권한으로 좁힐 수도 있습니다. 받는 쪽이 「이 권한을 가진 앱의 방송만 받겠다」고 적으면 그 권한이 없는 앱은 보내도 닿지 않습니다. 보내는 쪽도 「이 권한을 가진 앱만 받으라」고 적어 보낼 수 있습니다.

차례로 전하는 방송

보통의 방송은 조건이 맞는 리시버 모두에게 순서 없이 한꺼번에 갑니다. 리시버끼리는 서로의 결과를 볼 수 없습니다. 대신 품이 덜 들어서 가장 흔히 씁니다.

순서 있는 방송(ordered broadcast)은 리시버에게 하나씩 차례로 갑니다. 앞 리시버가 결과를 적어 다음 리시버에게 넘길 수 있습니다. 앞에서 방송을 끊으면 뒤의 리시버에게는 가지 않습니다.

차례는 리시버가 인텐트 필터에 적은 우선순위(priority) 값으로 정합니다. 값이 큰 리시버가 먼저 받습니다.

보통 방송 순서 있는 방송
보내는 메서드 sendBroadcast sendOrderedBroadcast
전하는 순서 정해지지 않는다 하나씩 차례로
결과 넘기기 · 끊기 못 한다 한다

백엔드의 발행-구독과 나란히

백엔드 개발자에게 방송은 발행-구독과 닮아 보입니다. 발행-구독은 보내는 쪽이 받는 쪽을 모른 채 주제에 메시지를 내는 방식입니다. 그 주제를 구독해 둔 쪽이 메시지를 받습니다. 방송의 액션이 주제, 인텐트 필터가 구독에 해당합니다.

차이는 메시지를 쌓아 두는지에 있습니다. 메시지 브로커는 받는 쪽이 잠깐 죽어 있어도 메시지를 들고 있다가 나중에 건넵니다. 방송은 쌓지 않습니다. 그 순간 등록된 리시버가 없으면 그 방송은 아무에게도 안 가고 사라집니다.

다시 보내 주는 장치도 없습니다. 리시버가 도중에 죽으면 그 방송은 거기서 끝납니다. 그래서 놓치면 안 되는 일에는 방송을 「무언가 바뀌었다」는 신호로만 씁니다. 바뀐 상태는 저장소에서 다시 읽습니다.

한 앱 안에서 부품끼리 알리는 데는 방송을 쓰지 않는 쪽이 흔합니다. 방송은 운영체제를 거쳐 가는 만큼 품이 듭니다. 앱 안에서는 방송 대신 옵저버 패턴을 씁니다.

옵저버 패턴은 값을 가진 객체가 값이 바뀔 때마다 구독한 쪽에 알려 주는 방식입니다. 안드로이드에서는 LiveData 가 그런 객체의 예입니다. 코틀린의 Flow 도 같은 일을 합니다.

관련 항목

브로드캐스트 리시버와 함께 안드로이드 앱을 이루는 컴포넌트

Android · 앱 컴포넌트 · 액티비티 · 서비스 (Android) · 콘텐츠 프로바이더 · 프래그먼트

브로드캐스트 리시버를 등록하고 방송을 전하는 장치

매니페스트 · 인텐트 · 인텐트 필터 · 암시적 방송 · 명시적 인텐트 · 암시적 인텐트 · 권한 · Binder

브로드캐스트 리시버가 도는 실행 환경

메인 스레드 · 프로세스 · 생명주기 · ANR · 콜백 · 메모리 누수 · Kotlin

브로드캐스트 리시버 대신 긴 일을 맡는 수단

WorkManager · 작업 예약 · 폴링 · 포그라운드 서비스 · 알람 매니저 · 백그라운드 실행 제한

앱 안에서 방송 대신 쓰는 관찰 수단

옵저버 패턴 · LiveData · Kotlin Flow · 이벤트 버스

브로드캐스트 리시버와 같은 설계 원리를 쓰는 서버 쪽 구조

발행-구독 · 메시지 브로커 · 이벤트 주도 · 웹훅 · 제어의 역전 · 서블릿 컨테이너

브로드캐스트라는 이름이 겹치는 네트워크 용어

브로드캐스트 주소 · 브로드캐스트 · 멀티캐스트 · 유니캐스트

다른 이름: BroadcastReceiver · broadcast receiver · 안드로이드 브로드캐스트 리시버