브로드캐스트 개요

Android 앱은 Android 시스템 및 기타 Android 앱에서 게시-구독 디자인 패턴과 유사한 브로드캐스트 메시지를 받거나 보낼 수 있습니다. 관심 있는 이벤트가 발생할 때 이러한 브로드캐스트가 전송됩니다. 예를 들어 Android 시스템은 시스템 부팅 또는 기기 충전 시작과 같은 다양한 시스템 이벤트가 발생할 때 브로드캐스트를 전송합니다. 또한 앱은 맞춤 브로드캐스트를 전송하여 다른 앱이 관심을 가질만한 사항(예: 일부 새로운 데이터가 다운로드됨)을 관련 앱에 알릴 수 있습니다.

앱은 특정 브로드캐스트를 수신하도록 등록할 수 있습니다. 브로드캐스트가 전송되면 시스템은 특정 유형의 브로드캐스트를 수신하도록 신청한 앱에 브로드캐스트를 자동으로 라우팅합니다.

일반적으로 말하면 브로드캐스트는 앱 전체에 걸쳐 그리고 일반 사용자 플로우 외부에서 메시징 시스템으로 사용될 수 있습니다. 그러나 다음 동영상에 설명된 것처럼 브로드캐스트에 응답하고 백그라운드에서 시스템 성능 저하의 원인이 될 수 있는 작업을 실행하는 기회를 남용하지 않도록 주의해야 합니다.

시스템 브로드캐스트 정보

시스템이 비행기 모드로 전환하고 비행기 모드를 해제할 때와 같이 다양한 시스템 이벤트가 발생하면 시스템은 자동으로 브로드캐스트를 전송합니다. 시스템 브로드캐스트는 이벤트를 수신하도록 신청한 모든 앱에 전송됩니다.

브로드캐스트 메시지 자체는 작업 문자열이 발생된 이벤트를 식별하는 Intent 객체에서 래핑됩니다(예: android.intent.action.AIRPLANE_MODE). 또한 인텐트에는 특별 필드에 번들로 제공되는 추가 정보가 포함될 수도 있습니다. 예를 들어 비행기 모드 인텐트에는 비행기 모드가 켜져 있는지 여부를 나타내는 부울 특별 필드가 포함됩니다.

인텐트를 읽고 인텐트에서 작업 문자열을 가져오는 방법에 관한 자세한 내용은 인텐트 및 인텐트 필터를 참조하세요.

시스템 브로드캐스트 작업의 전체 목록을 보려면 Android SDK의 BROADCAST_ACTIONS.TXT 파일을 참조하세요. 각 브로드캐스트 작업에는 연결된 상수 필드가 있습니다. 예를 들어 상수 ACTION_AIRPLANE_MODE_CHANGED의 값은 android.intent.action.AIRPLANE_MODE입니다. 각 브로드캐스트 작업에 관한 문서는 연결된 상수 필드에서 확인할 수 있습니다.

시스템 브로드캐스트 변경사항

Android 플랫폼이 발전함에 따라 시스템 브로드캐스트의 작동 방식이 주기적으로 변경됩니다. 앱이 Android 7.0(API 레벨 24) 이상을 타겟팅하거나 Android 7.0 이상을 실행하는 기기에 설치된다면 다음 변경사항에 유의하세요.

Android 9

Android 9(API 레벨 28)부터 NETWORK_STATE_CHANGED_ACTION 브로드캐스트는 사용자의 위치 또는 개인 식별 데이터에 관한 정보를 수신하지 않습니다.

또한 앱이 Android 9 이상을 실행하는 기기에 설치되면 Wi-Fi의 시스템 브로드캐스트에는 SSID, BSSID, 연결 정보 또는 검색 결과가 포함되지 않습니다. 이 정보를 얻으려면 대신 getConnectionInfo()를 호출해야 합니다.

Android 8.0

Android 8.0(API 레벨 26)부터 시스템은 manifest에 선언된 수신자에 추가 제한을 부과합니다.

앱이 Android 8.0 이상을 타겟팅하면 manifest를 사용하여 대다수 암시적 브로드캐스트(앱을 구체적으로 타겟팅하지 않는 브로드캐스트)의 수신자를 선언할 수 없습니다. 사용자가 앱을 적극적으로 사용할 때 개발자는 컨텍스트에 등록된 수신자를 계속 사용할 수 있습니다.

Android 7.0

Android 7.0(API 레벨 24) 이상에서는 다음과 같은 시스템 브로드캐스트를 전송하지 않습니다.

또한 Android 7.0 이상을 타겟팅하는 앱은 registerReceiver(BroadcastReceiver, IntentFilter)를 사용하여 CONNECTIVITY_ACTION 브로드캐스트를 등록해야 합니다. manifest에 수신자를 선언해도 작동하지 않습니다.

브로드캐스트 수신

앱은 두 가지 방식으로, 즉 manifest에 선언된 수신자 및 컨텍스트에 등록된 수신자를 통해 브로드캐스트를 수신할 수 있습니다.

manifest에 선언된 수신자

manifest에 broadcast receiver를 선언하면 브로드캐스트가 전송될 때 앱이 아직 실행 중이 아니라면 시스템에서 앱을 실행합니다.

manifest에서 broadcast receiver를 선언하려면 다음 단계를 따르세요.

  1. 앱의 manifest에서 <receiver> 요소를 지정합니다.

        <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>
        

    인텐트 필터는 수신자가 구독할 브로드캐스트 작업을 지정합니다.

  2. BroadcastReceiver 서브클래스를 선언하고 onReceive(Context, Intent)를 구현합니다. 다음 예의 broadcast receiver는 브로드캐스트의 콘텐츠를 기록하고 표시합니다.

    Kotlin

        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()
                    }
                }
            }
        }
        

    자바

        public class MyBroadcastReceiver extends BroadcastReceiver {
                private static final String TAG = "MyBroadcastReceiver";
                @Override
                public void onReceive(Context context, Intent intent) {
                    StringBuilder sb = new StringBuilder();
                    sb.append("Action: " + intent.getAction() + "\n");
                    sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
                    String log = sb.toString();
                    Log.d(TAG, log);
                    Toast.makeText(context, log, Toast.LENGTH_LONG).show();
                }
            }
        

앱이 설치될 때 시스템 패키지 관리자가 수신자를 등록합니다. 그러면 수신자가 앱으로 향하는 별도의 진입점이 됩니다. 이는 앱이 현재 실행되고 있지 않으면 시스템이 앱을 시작하고 브로드캐스트를 전달할 수 있다는 의미입니다.

시스템은 수신하는 각 브로드캐스트를 처리하기 위해 새 BroadcastReceiver 구성요소 객체를 생성합니다. 이 객체는 onReceive(Context, Intent) 호출이 지속되는 동안만 유효합니다. 코드가 이 메서드에서 반환되면 시스템은 구성요소가 더 이상 활성 상태가 아닌 것으로 간주합니다.

컨텍스트에 등록된 수신자

수신자를 컨텍스트에 등록하려면 다음 단계를 따르세요.

  1. BroadcastReceiver 인스턴스를 생성합니다.

    Kotlin

        val br: BroadcastReceiver = MyBroadcastReceiver()
        

    자바

        BroadcastReceiver br = new MyBroadcastReceiver();
        

  2. 다음과 같이 IntentFilter를 생성하고 registerReceiver(BroadcastReceiver, IntentFilter)를 호출하여 수신자를 등록합니다.

    Kotlin

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

    자바

        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
            filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
            this.registerReceiver(br, filter);
        

    컨텍스트에 등록된 수신자는 등록 컨텍스트가 유효한 동안 브로드캐스트를 수신합니다. 예를 들어 Activity 컨텍스트 내에 등록하면 활동이 제거되지 않는 한 브로드캐스트를 수신합니다. Application 컨텍스트에 등록하면 앱이 실행되는 동안 브로드캐스트를 수신합니다.

  3. 브로드캐스트 수신을 중지하려면 unregisterReceiver(android.content.BroadcastReceiver)를 호출합니다. 수신자가 더 이상 필요하지 않거나 컨텍스트가 더 이상 유효하지 않으면 수신자의 등록을 취소해야 합니다.

    수신자의 등록 및 등록 취소 위치에 유의해야 합니다. 예를 들어 활동의 컨텍스트를 사용하여 onCreate(Bundle)에 수신자를 등록했으면 활동 컨텍스트에서 수신자가 유출되지 않도록 onDestroy()에서 수신자를 등록 취소해야 합니다. onResume()에 수신자를 등록했으면 onPause()에서 수신자를 등록 취소하여 수신자가 여러 번 등록되지 않도록 해야 합니다(일시중지되었을 때 브로드캐스트를 수신하지 않으려면). 이렇게 하면 불필요한 시스템 오버헤드를 줄일 수 있습니다. 그리고 onSaveInstanceState(Bundle)에서 등록을 취소해서는 안 됩니다. 사용자가 기록 스택으로 되돌아가면 이 메서드가 호출되지 않기 때문입니다.

프로세스 상태에 미치는 영향

BroadcastReceiver의 상태(실행 중인지 아닌지 여부)는 포함된 프로세스의 상태에 영향을 주며, 결과적으로 시스템에 의해 종료될 가능성에 영향을 줄 수 있습니다. 예를 들어 프로세스가 수신자를 실행할 때(즉, 현재 onReceive() 메서드의 코드를 실행 중일 때) 이는 포그라운드 프로세스로 간주됩니다. 메모리 부족이 심한 상황을 제외하고 시스템은 프로세스를 계속 실행합니다.

하지만 코드가 onReceive()에서 반환되면 BroadcastReceiver는 더 이상 활성 상태가 아닙니다. 수신자의 호스트 프로세스는 프로세스 내에서 실행 중인 다른 앱 구성요소만큼만 중요합니다. 이 프로세스가 manifest에 선언된 수신자만 호스팅한다면(사용자가 최근에 상호작용한 적이 없거나 상호작용하지 않은 앱의 일반적인 사례) onReceive()에서 반환 시 시스템은 이 프로세스를 우선순위가 낮은 프로세스로 간주하여 더 중요한 다른 프로세스에서 리소스를 사용할 수 있도록 이 프로세스를 종료할 수 있습니다.

이러한 이유로 broadcast receiver에서 장기 실행 백그라운드 스레드를 시작해서는 안 됩니다. onReceive() 이후 시스템은 언제든지 프로세스를 종료하여 메모리를 회수할 수 있으며 이 과정에서 프로세스에서 생성되어 실행 중인 스레드를 종료할 수 있습니다. 이 문제를 방지하려면 goAsync()를 호출하거나(백그라운드 스레드의 브로드캐스트를 처리하는 데 시간이 좀 더 필요한 상황에서) JobScheduler를 사용하여 수신자의 JobService를 예약해야 합니다. 그러면 시스템은 프로세스가 계속 활성 작업을 실행하고 있음을 알게 됩니다. 자세한 내용은 프로세스 및 애플리케이션 수명 주기를 참조하세요.

다음 스니펫은 goAsync()를 사용하여 onReceive()가 완료된 후 끝내는 데 시간이 더 필요하다는 플래그를 지정하는 BroadcastReceiver를 보여줍니다. 이는 onReceive()에서 완료하려는 작업이 오래 걸리므로 UI 스레드가 프레임(>16ms)을 놓칠 정도여서 이 작업이 백그라운드 스레드에 더 적합할 때 특히 유용합니다.

Kotlin

    private const val TAG = "MyBroadcastReceiver"

    class MyBroadcastReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            val pendingResult: PendingResult = goAsync()
            val asyncTask = Task(pendingResult, intent)
            asyncTask.execute()
        }

        private class Task(
                private val pendingResult: PendingResult,
                private val intent: Intent
        ) : AsyncTask<String, Int, String>() {

            override fun doInBackground(vararg params: String?): String {
                val sb = StringBuilder()
                sb.append("Action: ${intent.action}\n")
                sb.append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
                return toString().also { log ->
                    Log.d(TAG, log)
                }
            }

            override fun onPostExecute(result: String?) {
                super.onPostExecute(result)
                // Must call finish() so the BroadcastReceiver can be recycled.
                pendingResult.finish()
            }
        }
    }
    

자바

    public class MyBroadcastReceiver extends BroadcastReceiver {
        private static final String TAG = "MyBroadcastReceiver";

        @Override
        public void onReceive(Context context, Intent intent) {
            final PendingResult pendingResult = goAsync();
            Task asyncTask = new Task(pendingResult, intent);
            asyncTask.execute();
        }

        private static class Task extends AsyncTask<String, Integer, String> {

            private final PendingResult pendingResult;
            private final Intent intent;

            private Task(PendingResult pendingResult, Intent intent) {
                this.pendingResult = pendingResult;
                this.intent = intent;
            }

            @Override
            protected String doInBackground(String... strings) {
                StringBuilder sb = new StringBuilder();
                sb.append("Action: " + intent.getAction() + "\n");
                sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
                String log = sb.toString();
                Log.d(TAG, log);
                return log;
            }

            @Override
            protected void onPostExecute(String s) {
                super.onPostExecute(s);
                // Must call finish() so the BroadcastReceiver can be recycled.
                pendingResult.finish();
            }
        }
    }
    

브로드캐스트 전송

Android는 다음과 같이 앱에서 브로드캐스트를 전송하는 방법을 세 가지 제공합니다.

  • sendOrderedBroadcast(Intent, String) 메서드는 한 번에 하나의 수신자에 브로드캐스트를 전송합니다. 각 수신자는 차례로 실행되기 때문에 결과를 다음 수신자로 전파하거나 브로드캐스트를 완전히 중단하여 브로드캐스트가 다른 수신자로 전달되지 않도록 할 수 있습니다. 수신자가 실행되는 순서는 일치하는 인텐트-필터의 android:priority 속성으로 제어할 수 있습니다. 우선순위가 동일한 수신자는 임의의 순서로 실행됩니다.
  • sendBroadcast(Intent) 메서드는 정의되지 않은 순서로 모든 수신자에 브로드캐스트를 전송합니다. 이를 일반 브로드캐스트라고 합니다. 일반 브로드캐스트는 상당히 효율적이지만 수신자가 다른 수신자의 결과를 읽거나 브로드캐스트로부터 수신한 데이터를 전파하거나 브로드캐스트를 중단할 수 없음을 의미합니다.
  • LocalBroadcastManager.sendBroadcast 메서드는 발신자와 동일한 앱에 있는 수신자에 브로드캐스트를 전송합니다. 앱 간에 브로드캐스트를 전송할 필요가 없다면 로컬 브로드캐스트를 사용합니다. 구현이 훨씬 더 효율적이며(프로세스 간 통신이 필요 없음) 다른 앱이 브로드캐스트를 수신하거나 전송할 수 있는 측면과 관련된 보안 문제에 관해 걱정할 필요가 없습니다.

다음 코드 스니펫은 인텐트를 생성하고 sendBroadcast(Intent)를 호출함으로써 브로드캐스트를 전송하는 방법을 보여줍니다.

Kotlin

    Intent().also { intent ->
        intent.setAction("com.example.broadcast.MY_NOTIFICATION")
        intent.putExtra("data", "Notice me senpai!")
        sendBroadcast(intent)
    }
    

자바

    Intent intent = new Intent();
    intent.setAction("com.example.broadcast.MY_NOTIFICATION");
    intent.putExtra("data","Notice me senpai!");
    sendBroadcast(intent);
    

브로드캐스트 메시지는 Intent 객체에서 래핑됩니다. 인텐트의 작업 문자열은 앱의 자바 패키지 이름 구문을 제공하고 브로드캐스트 이벤트를 고유하게 식별해야 합니다. putExtra(String, Bundle)를 사용하여 인텐트에 추가 정보를 첨부할 수 있습니다. 또한 인텐트에서 setPackage(String)를 호출하여 동일한 조직의 앱 세트로 브로드캐스트를 제한할 수도 있습니다.

권한으로 브로드캐스트 제한

권한을 통해 특정 권한을 보유한 앱 세트로 브로드캐스트를 제한할 수 있습니다. 브로드캐스트의 발신자 또는 수신자에 제한사항을 적용할 수 있습니다.

권한을 사용하여 전송

sendBroadcast(Intent, String) 또는 sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)를 호출할 때 권한 매개변수를 지정할 수 있습니다. manifest에 태그를 사용하여 권한을 요청한 수신자(및 위험하다면 나중에 권한을 부여받은 수신자)만 브로드캐스트를 수신할 수 있습니다. 예를 들어 다음 코드는 브로드캐스트를 전송합니다.

Kotlin

    sendBroadcast(Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS)
    

자바

    sendBroadcast(new Intent("com.example.NOTIFY"),
                  Manifest.permission.SEND_SMS);
    

브로드캐스트를 수신하려면 다음과 같이 수신 앱에서 권한을 요청해야 합니다.

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

SEND_SMS와 같은 기존 시스템 권한을 지정하거나 <permission> 요소를 사용하여 맞춤 권한을 정의할 수 있습니다. 권한 및 보안에 관한 전반적인 내용은 시스템 권한을 참조하세요.

권한을 사용하여 수신

broadcast receiver를 등록할 때(registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)를 사용하거나 manifest의 <receiver> 태그를 통해) 권한 매개변수를 지정하면 manifest에 <uses-permission> 태그를 사용하여 권한을 요청한 브로드캐스터(및 위험하다면 나중에 권한을 부여받은 브로드캐스터)만 수신자에 인텐트를 전송할 수 있습니다.

예를 들어 아래와 같이 수신 앱에 manifest에 선언된 수신자가 있다고 가정합니다.

<receiver android:name=".MyBroadcastReceiver"
              android:permission="android.permission.SEND_SMS">
        <intent-filter>
            <action android:name="android.intent.action.AIRPLANE_MODE"/>
        </intent-filter>
    </receiver>
    

또는 아래와 같이 컨텍스트에 등록된 수신자가 수신 앱에 있습니다.

Kotlin

    var filter = IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
    registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null )
    

자바

    IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null );
    

아래와 같이 발신 앱이 권한을 요청해야 수신자에 브로드캐스트를 전송할 수 있습니다.

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

보안 고려사항 및 권장사항

다음은 브로드캐스트 전송 및 수신에 관한 몇 가지 보안 고려사항 및 권장사항입니다.

  • 앱 외부의 구성요소에 브로드캐스트를 전송할 필요가 없다면 지원 라이브러리에서 제공하는 LocalBroadcastManager를 사용하여 로컬 브로드캐스트를 전송 및 수신합니다. LocalBroadcastManager는 훨씬 더 효율적이며(프로세스 간 통신이 필요 없음) LocalBroadcastManager를 사용하면 다른 앱이 브로드캐스트를 수신하거나 전송할 수 있는 측면과 관련된 보안 문제에 관해 신경 쓰지 않아도 됩니다. 로컬 브로드캐스트는 시스템 전체 브로드캐스트의 오버헤드 없이 앱의 범용 Pub/Sub 이벤트 버스로 사용할 수 있습니다.

  • 여러 앱이 자체 manifest에서 동일한 브로드캐스트를 수신하도록 등록하면 시스템에서 많은 앱을 실행하게 되며 결국 기기 성능과 사용자 환경 모두에 상당한 영향을 줄 수 있습니다. 이런 상황을 방지하려면 manifest 선언보다 컨텍스트 등록을 사용하는 것이 좋습니다. Android 시스템 자체가 컨텍스트에 등록된 수신자의 사용을 적용하는 상황도 있습니다. 예를 들어 CONNECTIVITY_ACTION 브로드캐스트는 컨텍스트에 등록된 수신자에만 전달됩니다.

  • 암시적 인텐트를 사용하여 민감한 정보를 브로드캐스트하지 않아야 합니다. 브로드캐스트를 수신하도록 등록한 앱에서 정보를 읽을 수 있기 때문입니다. 브로드캐스트를 수신할 수 있는 대상을 제어할 수 있는 방법은 다음과 같이 세 가지가 있습니다.

    • 브로드캐스트를 전송할 때 권한을 지정할 수 있습니다.
    • Android 4.0 이상에서는 브로드캐스트를 전송할 때 setPackage(String)를 사용하여 패키지를 지정할 수 있습니다. 시스템은 패키지와 일치하는 앱 세트로 브로드캐스트를 제한합니다.
    • LocalBroadcastManager를 사용하여 로컬 브로드캐스트를 전송할 수 있습니다.
  • 수신자를 등록하면 임의의 앱이 잠재적 악성 브로드캐스트를 앱의 수신자에 전송할 수 있습니다. 앱이 수신하는 브로드캐스트를 제한하는 방법은 다음과 같이 세 가지가 있습니다.

    • broadcast receiver를 등록할 때 권한을 지정할 수 있습니다.
    • manifest에 선언된 수신자라면 manifest에서 android:exported 속성을 'false'로 설정할 수 있습니다. 그러면 수신자는 앱 외부 소스의 브로드캐스트를 수신하지 않습니다.
    • LocalBroadcastManager를 사용하여 수신을 로컬 브로드캐스트만으로 제한할 수 있습니다.
  • 브로드캐스트 작업의 네임스페이스는 전역입니다. 작업 이름 및 기타 문자열이 고유한 네임스페이스에 작성되었는지 확인해야 합니다. 작성되지 않았으면 의도하지 않게 다른 앱과 충돌할 수 있습니다.

  • 수신자의 onReceive(Context, Intent) 메서드는 기본 스레드에서 실행되므로 이 메서드의 실행 및 반환은 신속해야 합니다. 장기 실행 작업을 할 필요가 있다면 스레드 생성 또는 백그라운드 서비스 시작에 주의해야 합니다. onReceive() 반환 후 시스템이 프로세스 전체를 종료할 수 있기 때문입니다. 자세한 내용은 프로세스 상태에 미치는 영향을 참조하세요. 장기 실행 작업을 하려면 다음 방법을 사용하는 것이 좋습니다.

    • 수신자의 onReceive() 메서드에서 goAsync()를 호출하고 BroadcastReceiver.PendingResult를 백그라운드 스레드에 전달합니다. 이렇게 하면 onReceive()의 반환 후 브로드캐스트가 활성 상태로 유지됩니다. 그러나 이 방법을 사용하더라도 시스템은 브로드캐스트가 매우 빠르게(10초 미만에) 완료될 것으로 예상합니다. 따라서 작업을 또 다른 스레드로 이동하여 기본 스레드에 문제가 발생하지 않도록 할 수 있습니다.
    • JobScheduler를 사용하여 작업을 예약합니다. 자세한 내용은 지능형 작업 예약을 참조하세요.
  • 특히 둘 이상의 수신자가 있다면 사용자 환경에 충돌이 발생하기 때문에 broadcast receiver에서 활동을 시작하지 않아야 합니다. 대신 알림 표시를 고려해야 합니다.