브로드캐스트 개요
- Android 앱은, Android 시스템 및 기타 Android 앱으로부터 브로드캐스트 메시지를 받거나 보낼 수 있다.
- 브로드캐스트 메시지 수신
- 관심 있는 이벤트가 발생할 때 이러한 브로드캐스트가 수신된다
( Observer 패턴과 유사)
- ex) Android 시스템은 시스템 부팅 또는 기기 충전 시작과 같은 다양한 시스템 이벤트가 발생할 때 브로드캐스트를 전송한다.
- 앱은 특정 브로드캐스트를 수신하도록 등록할 수 있다.
- 브로드캐스트가 전송되면 시스템은 특정 유형의 브로드캐스트를 수신하도록 신청한 앱에 브로드캐스트를 자동으로 라우팅한다.
- 브로드캐스트 메시지 발신
- 앱은 특정 브로드캐스트를 전송하여, 다른 앱이 관심을 가질만한 사항을 관련 앱에 알릴 수 있다
- 일반적으로 브로드캐스트는 앱 전체에 걸쳐, 외부에서 메시징 시스템으로 사용될 수 있다.
- 시스템 성능 저하의 원인이 될 수 있어, 작업을 남용하지 않도록 주의해야함
시스템 브로드캐스트 변경사항
Android 9
- NETWORK_STATE_CHANGED_ACTION 브로드캐스트는 사용자의 위치 또는 개인 식별 데이터에 관한 정보를 수신하지 않음
- Wi-Fi의 시스템 브로드캐스트에는 SSID, BSSID, 연결 정보 또는 검색 결과가 포함되지 않음. (정보를 얻으려면 대신 getConnectionInfo()를 호출)
Android 8.0
- 시스템이 manifest에 선언된 수신자에 추가 제한을 부과
- manifest를 사용하여 대다수 암시적 브로드캐스트(앱을 구체적으로 타겟팅하지 않는 브로드캐스트)의 수신자를 선언할 수 없음
- 사용자가 앱을 적극적으로 사용할 때 개발자는 컨텍스트에 등록된 수신자를 계속 사용할 수 있음.
Android 7.0
- 다음과 같은 시스템 브로드캐스트를 전송하지 않음
ACTION_NEW_PICTURE
ACTION_NEW_VIDEO
- registerReceiver(BroadcastReceiver, IntentFilter)를 사용하여 CONNECTIVITY_ACTION 브로드캐스트를 등록해야 함
(manifest에 수신자를 선언해도 작동하지 않음)
두 가지 방식, manifest에 선언된 수신자 혹은 컨텍스트에 등록된 수신자를 통해 브로드캐스트를 수신할 수 있음
manifest에 선언된 수신자
- manifest에 broadcast receiver를 선언하면 브로드캐스트가 전송될 때 앱이 아직 실행 중이 아니라면 시스템에서 앱을 실행
manifest에서 broadcast receiver 등록
- 앱의 manifest에서 를 지정
<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>
- 인텐트 필터로 수신자가 구독할 브로드캐스트 작업을 지정
- BroadcastReceiver 서브클래스를 선언
- onReceive(Context, Intent)를 구현
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() } } } }
- 설명
- 시스템 패키지 관리자가 앱이 설치될 때 수신자를 등록
- 앱이 현재 실행되고 있지 않으면, 시스템이 앱을 시작하고 브로드캐스트를 전달할 수 있게 됨
- 수신하는 각 브로드캐스트를 처리할 BroadcastReceiver 객체 생성
- 이 객체는 onReceive이 지속되는 동안만 유효(메서드에서 반환되면 활성 상태가 아님)
컨텍스트에 Receiver 등록
- BroadcastReceiver 인스턴스를 생성
val br: BroadcastReceiver = MyBroadcastReceiver()
- IntentFilter를 생성, registerReceiver(BroadcastReceiver, IntentFilter)를 호출하여 수신자 등록
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply { addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED) } registerReceiver(br, filter)참고. 로컬 브로드캐스트에 등록 LocalBroadcastManager.registerReceiver(BroadcastReceiver, IntentFilter) 호출
3. 설명
- Context에 등록된 수신자는, Context가 유효한 동안 브로드캐스트를 수신
- Activity Context에 등록하면 활동이 제거되지 않는 한 브로드캐스트를 수신
- Application Context에 등록하면 앱이 실행되는 동안 브로드캐스트를 수신
- unregisterReceiver(android.content.BroadcastReceiver)를 호출
- 수신자가 더 이상 필요하지 않음
- 컨텍스트가 더 이상 유효하지 않음
- 수신자의 등록 및 등록 취소 위치 유의
1. Activity Context를 사용하여 onCreate(Bundle)에 수신자를 등록했으면 onDestroy()에서 수신자를 등록 취소해야 함
2. onResume()에 수신자를 등록했으면 onPause()에서 수신자를 등록 취소하여 수신자가 여러 번 등록되지 않도록 해야 함
3. onSaveInstanceState(Bundle)에서 등록을 취소해서는 안 됨.
- BroadcastReceiver의 상태는 프로세스의 상태에 영향을 주기에, 시스템에 의해 종료될 가능성이 있음
- 프로세스가 수신자를 실행할 때 포그라운드 프로세스로 간주된다. 메모리 부족이 심한 상황을 제외하고 시스템은 프로세스를 계속 실행한다.
- 코드가 onReceive()에서 반환되면 BroadcastReceiver는 더 이상 활성 상태가 아니게 됨
- 프로세스가 manifest에 선언된 수신자만 호스팅하고, 사용자가 최근에 상호작용한 적이 없거나 상호작용하지 않은 앱인 경우 onReceive()에서 반환 시 시스템은 이 프로세스를 우선순위가 낮은 프로세스로 간주하여 더 중요한 다른 프로세스에서 리소스를 사용할 수 있도록 이 프로세스를 종료할 수 있음.
- 따라서 broadcast receiver에서 장기적인 백그라운드 스레드를 시작하면 안됨. onReceive() 이후 시스템은 언제든지 프로세스를 종료하여 메모리를 회수할 수 있으며 이 과정에서 프로세스에서 생성되어 실행 중인 스레드를 종료할 수 있음.
- 이 문제를 방지하려면 goAsync()를 호출하거나(백그라운드 스레드의 브로드캐스트를 처리하는 데 시간이 좀 더 필요한 상황에서) JobScheduler를 사용하여 수신자의 JobService를 예약해야 함. 그러면 시스템은 프로세스가 계속 활성 작업을 실행하고 있음을 알고 종료하지 않음.
- goAsync()를 사용하여 onReceive()가 완료된 후 끝내는 데 시간이 더 필요하다는 플래그를 지정하는 BroadcastReceiver를 예제. 이는 onReceive()에서 완료하려는 작업이 오래 걸리므로 UI 스레드가 프레임(>16ms)을 놓칠 정도여서 이 작업이 백그라운드 스레드에 더 적합할 때 특히 유용.
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) pendingResult.finish() } } }
세 가지 방법을 제공
- sendOrderedBroadcast(Intent, String)
- 한 번에 하나의 Receiver에 브로드캐스트를 전송
- 각 수신자는 차례로 실행
- 결과를 다음 Receiver로 전파하거나, 브로드캐스트를 완전히 중단하여 브로드캐스트가 다른 수신자로 전달되지 않도록 할 수 있음
- 수신자가 실행되는 순서는 일치하는 인텐트-필터의 android:priority 속성으로 제어할 수 있음. 우선순위가 동일한 수신자는 임의의 순서로 실행
- sendBroadcast(Intent)
- 정의되지 않은 순서로 모든 수신자에 브로드캐스트를 전송.
- 일반 브로드캐스트는 상당히 효율적이지만, 수신자가 다른 수신자의 결과를 읽거나 브로드캐스트로부터 수신한 데이터를 전파하거나 브로드캐스트를 중단할 수 없음
- LocalBroadcastManager.sendBroadcast
- 발신자와 동일한 앱에 있는 수신자에 브로드캐스트를 전송
- 앱 간에 브로드캐스트를 전송할 필요가 없다면 로컬 브로드캐스트를 사용
- 구현이 훨씬 더 효율적(프로세스 간 통신이 필요 없음)
- 다른 앱이 브로드캐스트를 수신하거나 전송할 수 있는 측면과 관련된 보안 문제에 관해 걱정할 필요가 없음
- Intent와 브로드캐스트 메시지
- 브로드캐스트 메시지는 Intent 객체에서 래핑됨
- 인텐트의 작업 문자열은 앱의 자바 패키지 이름 구문을 제공하고 브로드캐스트 이벤트를 고유하게 식별해야 함
- putExtra(String, Bundle)를 사용하여 인텐트에 추가 정보를 추가할 수 있음
- 인텐트에서 setPackage(String)를 호출하여 동일한 조직의 앱 세트로 브로드캐스트를 제한할 수도 있습니다.
권한을 통해 특정 권한을 보유한 앱 세트로 브로드캐스트를 제한할 수 있다
브로드캐스트의 발신자 또는 수신자에 제한사항을 적용할 수 있다
권한을 사용하여 전송
- sendBroadcast(), sendOrderedBroadcast()를 호출할 때 권한 매개변수를 지정
- manifest에 태그를 사용하여 권한을 요청한 수신자만 브로드캐스트를 수신할 수 있다.
sendBroadcast(Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS)
- 브로드캐스트를 수신하려면 다음과 같이 수신 앱에서 권한을 요청해야 함
<uses-permission android:name="android.permission.SEND_SMS"/>
- SEND_SMS와 같은 기존 시스템 권한을 지정하거나 요소를 사용하여 맞춤 권한을 정의할 수도 있음
- 참고 - 앱이 설치될 때 맞춤 권한이 등록되기 때문에, 맞춤 권한을 정의하는 앱은 권한을 사용하는 앱보다 먼저 설치해야 한다
권한을 사용하여 수신
- broadcast receiver를 등록할 때(registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)를 사용하거나, manifest의 태그를 통해) 권한 매개변수를 지정하면 manifest에 태그를 사용하여 권한을 요청한 브로드캐스터만 수신자에 인텐트를 전송할 수 있다
- 수신자 등록 예제 코드
<receiver android:name=".MyBroadcastReceiver" android:permission="android.permission.SEND_SMS"> <intent-filter> <action android:name="android.intent.action.AIRPLANE_MODE"/> </intent-filter> </receiver>
- 또는 컨텍스트에 등록된 수신자
var filter = 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 선언보다 컨텍스트 등록을 사용하는 것이 좋다
- 암시적 인텐트를 사용하여 민감한 정보를 브로드캐스트하지 않아야 한다. 브로드캐스트를 수신하도록 등록한 앱에서 정보를 읽을 수 있기 때문.
- 브로드캐스트를 수신할 수 있는 대상을 제어할 수 있는 방법은 세 가지가 있다
1. 브로드캐스트를 전송할 때 권한을 지정할 수 있다
2. Android 4.0 이상에서는 브로드캐스트를 전송할 때 setPackage(String)를 사용하여 패키지를 지정할 수 있다. 시스템은 패키지와 일치하는 앱 세트로 브로드캐스트를 제한
3. LocalBroadcastManager를 사용하여 로컬 브로드캐스트를 전송
- 수신자를 등록하면 임의의 앱이 잠재적 악성 브로드캐스트를 앱의 수신자에 전송할 수 있다
- 앱이 수신하는 브로드캐스트를 제한하는 방법 세 가지
1. broadcast receiver를 등록할 때 권한을 지정
2. manifest에 선언된 수신자라면 manifest에서 android:exported 속성을 'false'로 설정. 그러면 수신자는 앱 외부 소스의 브로드캐스트를 수신하지 않음.
3. LocalBroadcastManager를 사용하여 수신을 로컬 브로드캐스트만으로 제한