브로드캐스트 개요

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 버전을 지원하려면 다음 변경사항에 유의하세요.

Android 14

앱이 캐시된 상태에 있는 동안 브로드캐스트 전송은 시스템 상태에 최적화됩니다. 예를 들어 ACTION_SCREEN_ON와 같이 덜 중요한 시스템 브로드캐스트는 앱이 캐시된 상태에 있는 동안 지연됩니다. 앱이 캐시된 상태에서 활성 프로세스 수명 주기로 전환되면 시스템은 지연된 브로드캐스트를 전송합니다.

매니페스트에 선언된 중요한 브로드캐스트는 전송을 위해 캐시된 상태에서 앱을 일시적으로 삭제합니다.

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)부터 시스템은 매니페스트 선언 수신기에 추가 제한사항을 적용합니다.

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

Android 7.0

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

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

브로드캐스트 수신

앱은 두 가지 방법, 즉 매니페스트 선언 수신기와 컨텍스트에 등록된 수신기를 통해 브로드캐스트를 수신할 수 있습니다.

manifest에 선언된 수신자

매니페스트에서 broadcast receiver를 선언하면 브로드캐스트가 전송될 때 앱이 아직 실행되고 있지 않은 경우 시스템에서 앱을 시작합니다.

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

  1. 앱의 매니페스트에서 <receiver> 요소를 지정합니다.

    <!-- If this receiver listens for broadcasts sent from the system or from
         other apps, even other apps that you own, set android:exported to "true". -->
    <receiver android:name=".MyBroadcastReceiver" android:exported="false">
        <intent-filter>
            <action android:name="APP_SPECIFIC_BROADCAST" />
        </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)
    
                    val binding = ActivityNameBinding.inflate(layoutInflater)
                    val view = binding.root
                    setContentView(view)
    
                    Snackbar.make(view, log, Snackbar.LENGTH_LONG).show()
                }
            }
        }
    }
    

    Java

    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);
    
                ActivityNameBinding binding =
                        ActivityNameBinding.inflate(layoutInflater);
                val view = binding.root;
                setContentView(view);
    
                Snackbar.make(view, log, Snackbar.LENGTH_LONG).show();
            }
        }
    

    뷰 결합을 사용 설정하려면 모듈 수준 build.gradle 파일에 viewBinding을 구성하세요.

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

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

컨텍스트에 등록된 수신자

컨텍스트에 등록된 수신자는 등록 컨텍스트가 유효한 동안 브로드캐스트를 수신합니다. 예를 들어 Activity 컨텍스트 내에서 등록하면 활동이 소멸되지 않는 한 브로드캐스트를 수신합니다. 애플리케이션 컨텍스트에 등록하면 앱이 실행되는 동안 브로드캐스트를 수신합니다.

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

  1. 앱의 모듈 수준 빌드 파일에 AndroidX Core 라이브러리 버전 1.9.0 이상을 포함합니다.

    Groovy

    dependencies {
        def core_version = "1.12.0"
    
        // Java language implementation
        implementation "androidx.core:core:$core_version"
        // Kotlin
        implementation "androidx.core:core-ktx:$core_version"
    
        // To use RoleManagerCompat
        implementation "androidx.core:core-role:1.0.0"
    
        // To use the Animator APIs
        implementation "androidx.core:core-animation:1.0.0-rc01"
        // To test the Animator APIs
        androidTestImplementation "androidx.core:core-animation-testing:1.0.0-rc01"
    
        // Optional - To enable APIs that query the performance characteristics of GMS devices.
        implementation "androidx.core:core-performance:1.0.0"
    
        // Optional - to use ShortcutManagerCompat to donate shortcuts to be used by Google
        implementation "androidx.core:core-google-shortcuts:1.1.0"
    
        // Optional - to support backwards compatibility of RemoteViews
        implementation "androidx.core:core-remoteviews:1.1.0-alpha01"
    
        // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
        implementation "androidx.core:core-splashscreen:1.1.0-alpha02"
    }
    

    Kotlin

    dependencies {
        val core_version = "1.12.0"
    
        // Java language implementation
        implementation("androidx.core:core:$core_version")
        // Kotlin
        implementation("androidx.core:core-ktx:$core_version")
    
        // To use RoleManagerCompat
        implementation("androidx.core:core-role:1.0.0")
    
        // To use the Animator APIs
        implementation("androidx.core:core-animation:1.0.0-rc01")
        // To test the Animator APIs
        androidTestImplementation("androidx.core:core-animation-testing:1.0.0-rc01")
    
        // Optional - To enable APIs that query the performance characteristics of GMS devices.
        implementation("androidx.core:core-performance:1.0.0")
    
        // Optional - to use ShortcutManagerCompat to donate shortcuts to be used by Google
        implementation("androidx.core:core-google-shortcuts:1.1.0")
    
        // Optional - to support backwards compatibility of RemoteViews
        implementation("androidx.core:core-remoteviews:1.1.0-alpha01")
    
        // Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
        implementation("androidx.core:core-splashscreen:1.1.0-alpha02")
    }
    
  2. BroadcastReceiver의 인스턴스를 만듭니다.

    Kotlin

    val br: BroadcastReceiver = MyBroadcastReceiver()
    

    Java

    BroadcastReceiver br = new MyBroadcastReceiver();
    
  3. IntentFilter의 인스턴스를 만듭니다.

    Kotlin

    val filter = IntentFilter(APP_SPECIFIC_BROADCAST)
    

    Java

    IntentFilter filter = new IntentFilter(APP_SPECIFIC_BROADCAST);
    
  4. broadcast receiver를 내보내 기기의 다른 앱에 표시할지 선택합니다. 이 broadcast receiver가 시스템이나 다른 앱(내가 소유한 다른 앱 포함)에서 전송된 브로드캐스트를 수신 대기하는 경우 RECEIVER_EXPORTED 플래그를 사용합니다. 대신 이 broadcast receiver가 앱에서 보낸 브로드캐스트만 수신 대기하는 경우 RECEIVER_NOT_EXPORTED 플래그를 사용합니다.

    Kotlin

    val listenToBroadcastsFromOtherApps = false
    val receiverFlags = if (listenToBroadcastsFromOtherApps) {
        ContextCompat.RECEIVER_EXPORTED
    } else {
        ContextCompat.RECEIVER_NOT_EXPORTED
    }
    

    Java

    boolean listenToBroadcastsFromOtherApps = false;
    if (listenToBroadcastsFromOtherApps) {
        receiverFlags = ContextCompat.RECEIVER_EXPORTED;
    } else {
        receiverFlags = ContextCompat.RECEIVER_NOT_EXPORTED;
    }
    
  5. registerReceiver()를 호출하여 수신자를 등록합니다.

    Kotlin

    ContextCompat.registerReceiver(context, br, filter, receiverFlags)
    

    Java

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

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

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

BroadcastReceiver의 작동 여부는 포함된 프로세스에 영향을 미치므로 시스템 종료 가능성이 변경될 수 있습니다. 포그라운드 프로세스는 수신자의 onReceive() 메서드를 실행합니다. 시스템은 메모리 부족이 심한 경우를 제외하고 프로세스를 실행합니다.

BroadcastReceiver는 onReceive() 후에 비활성화됩니다. 수신자의 호스트 프로세스는 앱 구성요소만큼만 중요합니다. 프로세스가 매니페스트 선언 수신자만 호스팅하는 경우 (사용자가 최근에 상호작용한 적이 없거나 최근에 상호작용하지 않은 앱에서 자주 발생함) 시스템은 다른 더 중요한 프로세스에 리소스를 사용할 수 있도록 onReceive() 후에 이 수신자를 종료할 수 있습니다.

따라서 broadcast receiver는 장기 실행 백그라운드 스레드를 시작해서는 안 됩니다. 시스템은 onReceive() 이후 언제든지 프로세스를 중지하여 메모리를 회수하고 생성된 스레드를 종료할 수 있습니다. 프로세스를 활성 상태로 유지하려면 시스템에서 프로세스가 계속 작동 중임을 알 수 있도록 JobScheduler를 사용하여 수신기에서 JobService를 예약합니다. 백그라운드 작업 개요에서 자세한 내용을 확인할 수 있습니다.

브로드캐스트 전송

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

  • sendOrderedBroadcast(Intent, String) 메서드는 한 번에 하나의 수신기로 브로드캐스트를 전송합니다. 각 수신기는 차례로 실행되므로 결과를 다음 수신기로 전파하거나 브로드캐스트를 완전히 취소하여 다른 수신기로 전달되지 않도록 할 수 있습니다. 수신기가 실행되는 순서는 일치하는 인텐트 필터의 android:priority 속성으로 제어할 수 있습니다. 우선순위가 동일한 수신자는 임의의 순서로 실행됩니다.
  • sendBroadcast(Intent) 메서드는 정의되지 않은 순서로 모든 수신기에 브로드캐스트를 전송합니다. 이를 일반 브로드캐스트라고 합니다. 이는 더 효율적이지만 broadcast receiver가 다른 broadcast receiver에서 결과를 읽거나 브로드캐스트에서 수신된 데이터를 전파하거나 브로드캐스트를 취소할 수 없음을 의미합니다.

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

Kotlin

Intent().also { intent ->
    intent.setAction("com.example.broadcast.MY_NOTIFICATION")
    intent.putExtra("data", "Nothing to see here, move along.")
    sendBroadcast(intent)
}

Java

Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data", "Nothing to see here, move along.");
sendBroadcast(intent);

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

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

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

권한을 사용하여 전송

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

Kotlin

sendBroadcast(Intent(BluetoothDevice.ACTION_FOUND),
              Manifest.permission.BLUETOOTH_CONNECT)

Java

sendBroadcast(new Intent(BluetoothDevice.ACTION_FOUND),
              Manifest.permission.BLUETOOTH_CONNECT)

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

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

BLUETOOTH_CONNECT와 같은 기존 시스템 권한을 지정하거나 <permission> 요소로 맞춤 권한을 정의할 수 있습니다. 권한과 보안에 관한 일반적인 정보는 시스템 권한을 참고하세요.

권한을 사용하여 수신

broadcast receiver를 등록할 때(매니페스트의 registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) 또는 <receiver> 태그에) 권한 매개변수를 지정하면 매니페스트에서 <uses-permission> 태그를 사용하여 권한을 요청한 (그리고 이후에 위험한 경우 권한이 부여된) 브로드캐스터만 수신기에 인텐트를 전송할 수 있습니다.

예를 들어 아래와 같이 수신 앱에 매니페스트에 선언된 수신기가 있다고 가정해 보겠습니다.

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

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

Kotlin

var filter = IntentFilter(Intent.ACTION_FOUND)
registerReceiver(receiver, filter, Manifest.permission.BLUETOOTH_CONNECT, null )

Java

IntentFilter filter = new IntentFilter(Intent.ACTION_FOUND);
registerReceiver(receiver, filter, Manifest.permission.BLUETOOTH_CONNECT, null );

그런 다음 이러한 수신자에게 브로드캐스트를 보내려면 아래와 같이 전송 앱에서 권한을 요청해야 합니다.

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

보안 고려사항 및 권장사항

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

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

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

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

    • broadcast receiver를 등록할 때 권한을 지정할 수 있습니다.
    • 매니페스트 선언 수신기의 경우 매니페스트에서 android:exported 속성을 'false'로 설정할 수 있습니다. 수신자는 앱 외부 소스에서 브로드캐스트를 수신하지 않습니다.
  • 브로드캐스트 작업의 네임스페이스는 전역입니다. 작업 이름 및 기타 문자열은 소유하고 있는 네임스페이스에 작성되어야 합니다. 그러지 않으면 실수로 다른 앱과 충돌할 수 있습니다.

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

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