안드로이드 앱은 publish-subscribe 패턴과 비슷하게 안드로이드 시스템이나 다른 앱에서 브로드캐스트 메시지를 전송하고 수신함, 예시로 안드로이드 시스템은 시스템 부팅 또는 기기 충전과 같은 다양한 시스템 이벤트가 발생할 때 브로드캐스트를 전송함, 앱도 또한 사용자 지정 브로드캐스트를 전송해 새 데이터 다운로드와 같이 다른 앱이 관심을 가질만한 상황을 해당 앱에 알릴 수 있음
앱은 특정 브로드캐스트를 받을 수 있게 등록할 수 있음, 브로드캐스트가 전송될 때 시스템이 특정 브로드캐스트 타입을 등록해놓은 앱에 자동적으로 전송될 수 있게 경로를 만듬
브로드캐스트는 일반 사용자 플로우 외부의 앱 간 메시지 시스템으로 사용될 수 있지만, 시스템 성능을 저하시키지 않도록 백그라운드에서 브로드캐스트에 응답하고 작업을 시작하는 것을 남용하지 않아야 함
주의: 시스템은 최적의 시스템 상태를 유지하기 위해 브로드캐스트의 전송을 최적화합니다. 그러므로 브로드캐스트의 전송 시간은 보장되지 않습니다. 저지연
IPC가 필요한 앱은 연결된 서비스를 고려해보세요.
시스템은 비행기 모드를 키고 끄는 것과 같이 다양한 시스템 이벤트가 발생하면 자동적으로 브로드캐스트를 전송함, 등록된 모든 앱은 이런 브로드캐스트를 수신함
Intent 객체가 브로드캐스트 메시지를 감쌈, android.intent.action.AIRPLANE_MODE와 같이 action 문자열이 발생한 이벤트를 식별함, 인텐트는 엑스트라 필드로 추가 정보를 저장함, 예시로 비행기 모드 인텐트는 비행기 모드가 켜진것인지 꺼진것인지를 나타내는 불리언 엑스트라를 포함함
BROADCAST_ACTIONS.TXT파일에 시스템 브로드캐스트 액션의 전체 리스트가 있음, 각 브로드캐스트 액션은 관련된 상수가 있음, 예시로 ACTION_AIRPLANE_MODE_CHANGED 상수의 값은 android.intent.action.AIRPLANE_MODE임, 각 브로드캐스트 액션에 대한 문서는 관련된 상수 필드에서 확인 가능android:priority속성이나 IntentFilter.setPriority()를 사용해 다른 프로세스간 브로드캐스트 전송 순서를 정렬하는 것이 보장되지 않음, 브로드캐스트 우선순위는 모든 프로세스간이 아닌 동일한 앱 프로세스내에서만 적용됨
브로드캐스트 우선순위는 자동적으로 (SYSTEM_LOW_PRIORITY + 1, SYSTEM_HIGH_PRIORITY - 1)의 범위로 설정되기 때문에, 시스템 컴포넌트만 SYSTEM_LOW_PRIORITY, SYSTEM_HIGH_PRIORITY로 브로드캐스트 우선순위를 설정할 수 있음
ACTION_SCREEN_ON과 같이 덜 중요한 시스템 브로드캐스트를 지연시킴, 앱이 캐시된 상태에서 활성화 상태가 되면 시스템이 지연된 브로드캐스트를 전송함, 매니페스트에 선언된 중요한 브로드캐스트는 전달을 위해 앱을 캐시된 상태에서 임의로 제거함안드로이드 9(API 레벨 28)부터 NETWORK_STATE_CHANGED_ACTION 브로드캐스트는 사용자의 지역이나 개인 식별 데이터에 관한 정보를 수신하지 않음
앱이 안드로이드 9(API 레벨 28)이상인 기기에 설치되어 있다면, 시스템은 WI-FI 브로드캐스트에 SSID, BSSID, 연결 정보를 포함하지 않음, getConnectionInfo()을 대신 사용해 이런 정보를 얻을 수 있음
안드로이드 8(API 레벨 26)부터 시스템은 매니페스트에 선언된 리시버에 추가 제한을 부과함
앱이 안드로이드 8(API 레벨 26)이상을 타겟팅한다면, 대부분의 암시적 브로드캐스트(당신의 앱을 구체적으로 타겟팅하지 않는 브로드캐스트)를 수신하는 리시버를 매니페스트에 선언할 수 없음, 사용자가 앱을 적극적으로 사용할 때 아직 컨텍스트에 등록된 리시버를 사용할 수 있음
안드로이드 7(API 레벨 24)이상에서는 다음과 같은 시스템 브로드캐스트를 전송하지 않음
ACTION_NEW_PICTUREACTION_NEW_VIDEO앱이 안드로이드 7(API 레벨 24)이상을 타겟팅한다면, registerReceiver(BroadcastReceiver, IntentFilter)를 사용해 CONNECTIVITY_ACTION을 등록해야 함, 매니페스트에 리시버를 선언하는 것은 동작하지 않음
registerReceiver 와 unregisterReceiver 사이에 일어나는 일임, 등록한 컨텍스트는 해당 컨텍스트가 시스템에 의해 파괴될 때도 유효하지 않게 됨, 예를 들어 액티비티 컨텍스트에 등록하면 액티비티가 유효할 동안 브로드캐스트를 수신할 수 있고, 앱 컨텍스트에 등록하면 앱이 실행될 동안 브로드캐스트를 수신할 수 있음리시버를 컨텍스트에 등록하는 단계
AndroidX Core 라이브러리의 1.9.0 또는 이상의 버전을 포함dependencies {
def core_version = "1.16.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.1.0"
// To use the Animator APIs
implementation "androidx.core:core-animation:1.0.0"
// To test the Animator APIs
androidTestImplementation "androidx.core:core-animation-testing:1.0.0"
// 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"
// Optional - APIs for SplashScreen, including compatibility helpers on devices prior Android 12
implementation "androidx.core:core-splashscreen:1.2.0-beta02"
}
BroadcastReceiver의 인스턴스 생성val myBroadcastReceiver = MyBroadcastReceiver()
IntentFilter의 인스턴스 생성val filter = IntentFilter("com.example.snippets.ACTION_UPDATE_DATA")
RECEIVER_EXPORTED 플래그를 사용함, 리시버가 본인 앱에서 전송되는 브로드캐스트만 수신한다면 RECEIVER_NOT_EXPORTED 플래그를 사용함일부 시스템 브로드캐스트는 안드로이드 프레임워크의 일부이지만 시스템의 고유 프로세스 ID(UID)에서 실행되지 않는 블루투스나 전화와 같은 권한이 높은 앱에서 발생합니다. 권한이 높은 앱에서 발생하는 브로드캐스트를 포함한 모든 시스템 브로드캐스트를 수신하려면 리시버에
RECEIVER_EXPORTED플래그를 사용하십시오.리시버에
RECEIVER_NOT_EXPORTED플래그를 사용한다면, 리시버는 일부 시스템 브로드캐스트를 수신할 수 있지만, 권한이 높은 앱에서 발생하는 브로드캐스트를 수신할 수 없습니다.앱이 다양한 브로드캐스트를 수신하지만 일부는
RECEIVER_NOT_EXPORTED플래그를 사용되어야 하고 일부는RECEIVER_EXPORTED를 사용할 때는 브로드캐스트가 브로드캐스트 리시버들 간에 분할됩니다.
val listenToBroadcastsFromOtherApps = false
val receiverFlags = if (listenToBroadcastsFromOtherApps) {
ContextCompat.RECEIVER_EXPORTED
} else {
ContextCompat.RECEIVER_NOT_EXPORTED
}
주의: 브로드캐스트 리시버를 내보낸다면 다른 앱이 당신의 앱에 보호되지 않는 브로드캐스트를 전송할 수 있습니다.
registerReceiver()를 호출해 리시버를 등록ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, receiverFlags)
unregisterReceiver(android.content.BroadcastReceiver)를 호출해야 함, 컨텍스트가 더 이상 유효하지 않거나 리시버가 더이상 필요하지 않을 때 리시버를 등록 해제해야 함class MyActivity : ComponentActivity() {
private val myBroadcastReceiver = MyBroadcastReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
ContextCompat.registerReceiver(this, myBroadcastReceiver, filter, receiverFlags)
setContent { MyApp() }
}
override fun onDestroy() {
super.onDestroy()
// 여기서 리시버를 등록 해제하는 것을 까먹는다면, 메모리 누수가 발생합니다!
this.unregisterReceiver(myBroadcastReceiver)
}
}
브로드캐스트 리시버는 결과에 관심이 있을때만 등록되어야 함, 가능하면 리시버 범위를 가장 작은 단위로 선택해야 함
LifecycleResumeEffect 또는 액티비티의 onResume/onPause 수명주기 메소드: 브로드캐스트 리시버는 앱이 resumed 상태에만 수신을 받음LifecycleStartEffect 또는 액티비티의 onStart/onStop 수명주기 메소드: 브로드캐스트 리시버는 앱이 resumed 상태에만 수신을 받음DisaposableEffect: 브로드캐스트 리시버는 컴포저블이 컴포지션 트리에 있을 동안만 수신을 받음, 이 범위는 액티비티의 수명주기 범위에 연결되지 않음, 컴포저블이 액티비티의 수명주기 범위를 벗어날 가능성이 있으므로 액티비티가 누수될 수 있기 때문에 앱 컨텍스트에 리시버를 등록하는 것을 고려해야 함onCreate/onDestroy: 브로드캐스트 리시버는 액티비티가 created 상태에 있을 동안 수신을 받음, onSaveInstanceState(Bundle)은 호출되지 않을 수도 있기 떄문에 onDestroy()에서 등록 해제를 해야 함ViewModel 범위에 등록해 액티비티가 재생성될 동안에도 브로드캐스트 리시버를 사용할 수 있음, 이 때 리시버가 액티비티 수명주기 범위를 벗어나 액티비티를 누수할 수 있으므로 리시버를 앱 컨텍스트에 등록해야 함Stateful과 Stateless 컴포저블 생성컴포즈(Jetpack Compose)는 stateful과 stateless 컴포저블이 있음, 리시버를 컴포저블에 등록 및 등록 해제하는 것은 컴포저블을 stateful하게 만듬, 등록된 브로드캐스트 리시버 호출에 따라 내부 상태가 변할 수 있기 때문에 컴포저블은 동일한 매개변수를 받을 때 동일한 결과를 출력하는 결정 함수가 아니게 됨
컴포즈에서는 컴포저블을 stateful 과 stateless 한 버전으로 컴포저블을 나누는 것을 추천함, 그러므로 컴포저블을 stateless하게 만들기 위해 아래 예시처럼 브로드캐스트 리시버의 생성을 hoist 하는 것을 추천함
@Composable
fun MyStatefulScreen() {
val myBroadcastReceiver = remember { MyBroadcastReceiver() }
val context = LocalContext.current
LifecycleStartEffect(true) {
// ...
ContextCompat.registerReceiver(context, myBroadcastReceiver, filter, flags)
onStopOrDispose { context.unregisterReceiver(myBroadcastReceiver) }
}
MyStatelessScreen()
}
@Composable
fun MyStatelessScreen() {
// 화면 구현
}
노트: 앱이
API레벨 26 이상을 타겟팅한다면 암시적 브로드캐스트를 수신하는 리시버를 매니페스트를 이용해 선언할 수 없습니다. 몇몇 암시적 브로드캐스트는 예외입니다. 암시적 브로드캐스트는 당신의 앱을 특정하지 않는 브로드캐스트를 뜻합니다. 이는 대부분의 경우 예약된 작업을 사용해 대신할 수 있습니다.
브로드캐스트 리시버를 매니페스트에 선언하는 방법
<receiver> 속성을 지정<!-- 이 리시버가 시스템 또는 다른 앱에서 보낸 브로드캐스트를
수신하는 경우 android:exported의 값을 true로 설정하세요. -->
<receiver android:name=".MyBroadcastReceiver" android:exported="false">
<intent-filter>
<action android:name="com.example.snippets.ACTION_UPDATE_DATA" />
</intent-filter>
</receiver>
onReceive(Context, Intent)를 구현해 BroadcastReceiver를 구현class MyBroadcastReceiver : BroadcastReceiver() {
@Inject
lateinit var dataRepository: DataRepository
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == "com.example.snippets.ACTION_UPDATE_DATA") {
val data = intent.getStringExtra("com.example.snippets.DATA") ?: "No data"
// 데이터로 작업 수행, 예를 들면 여기선 데이터 리포지토리에 전달:
dataRepository.updateData(data)
}
}
}
시스템 패키지 매니저는 앱이 설치되었을 때 리시버를 등록함, 리시버는 앱이 실행중이지 않을 때 시스템이 앱을 시작해 브로드캐스트를 전달할 수 있는 진입점이 됨
시스템은 각 브로드캐스트마다 BroadcastReceiver 컴포넌트 객체를 새로 생성함, 이 객체는 onReceive(Context, Intent)가 호출되는 기간동안만 유효함, 이 메소드가 반환되면 시스템은 컴포넌트가 더 이상 활성화 상태가 아니라고 판단함
브로드캐스트 리시버가 작동하는 것은 프로세스에 영향을 끼침, 이는 시스템이 프로세스를 종료시킬 가능성을 바꿀 수 있음, 포그라운드 프로세스가 리시버의 onReceive()메소드를 실행시키면 시스템은 극심한 메모리 압박이 아니라면 프로세스를 실행시킴
시스템은 onReceive() 이후에 브로드캐스트를 비활성화시킴, 리시버의 호스트 프로세스의 중요성은 프로세스의 앱 컴포넌트에 따라 다름, 만약 프로세스가 매니페스트에 선언된 리시버만 호스팅한다면 시스템은 onReceive() 이후에 다른 더 중요한 프로세스를 위해 자원을 할당하기 위해 프로세스를 종료할 수 있음, 이는 사용자가 최근에 상호작용하지 않았거나 상호작용한 적이 없는 앱에서 흔히 발생함
브로드캐스트 리시버는 장기간 작업을 해야하는 스레드를 시작해서는 안 됨, onReceive() 이후에 메모리를 확보하기 위해 시스템이 언제든 프로세스를 중단할 수 있고, 이 때 생성된 스레드도 종료되기 때문임, 프로세스를 계속 활성화되게 하려면 리시버에서 JobScheduler를 사용해 JobService를 예약해야 함, 그러면 시스템이 프로세스가 아직 작업 중이라는 것을 알게 됨
sendOrderedBroascast(Intent, String) 메소드는 한번에 한 리시버에게만 브로드캐스트를 전송함, 각 리시버는 차례대로 실행되며 결과를 다음 리시버에게 전파할 수 있음, 다른 리시버가 전달받지 못하도록 브로드캐스트를 완전히 중지시킬 수도 있음, 같은 앱의 프로세스에 있는 리시버의 전달 순서를 인텐트 필터가 맞는 리시버의 android:priority 속성을 사용해 제어할 수 있음, 리시버가 같은 우선순위(priority)에 있다면 임의의 순서로 실행됨sendBroadcast(Intent) 메소드는 리시버에게 순서를 정의하지 않고 브로드캐스트를 전송함, 이를 일반 브로드캐스트라고 함, 이는 더 효율적이지만, 리시버가 다른 리시버의 결과를 읽을 수 없고, 브로드캐스트의 정보를 전파하거나, 브로드캐스트를 중단할 수 없음sendBroadcast()의 사용 예제
val intent = Intent("com.example.snippets.ACTION_UPDATE_DATA").apply {
putExtra("com.example.snippets.DATA", newData)
setPackage("com.example.snippets")
}
context.sendBroadcast(intent)
Intent 객체 안에 감싸여 있음, 인텐트의 action 문자열은 반드시 앱의 패키지 이름을 제공해야 하고, 브로드캐스트 이벤트를 고유하게 식별해야 함, putExtra(String, Bundle)을 이용해 인텐트에 추가 정보를 담을 수 있음, 인텐트의 setPackage(String)를 사용해 동일한 조직의 앱들로 브로드캐스트를 제한할 수 있음노트: 비록 브로드캐스트를 전송하는 것과
startActivity(Intent)를 사용해 액티비티를 시작하는 것이 모두 인텐트를 사용하지만, 이 동작들은 완전히 관계가 없습니다. 브로드캐스트 리시버는 액티비티를 시작하기 위해 사용되는 인텐트를 보거나 캡처하지 못합니다. 마찬가지로 브로드캐스트를 전송할 때 액티비티를 시작하거나 찾을 수 없습니다.
sendBroadcast(Intent, String) 또는 sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)를 호출할 때 권한 매개변수를 지정할 수 있음, 이 경우 매니페스트에 <uses-permission> 태그를 사용해 권한을 요청한 리시버만 해당 브로드캐스트를 전달받을 수 있음, 만약 권한이 위험하다면 반드시 리시버가 브로드캐스트를 전달받기 전에 권한을 부여받아야 함권한을 사용해 브로드캐스트를 전송하는 예시
context.sendBroadcast(intent, android.Manifest.permission.ACCESS_COARSE_LOCATION)
해당 브로드캐스트를 전달받으려면 전달받을 앱은 반드시 다음과 같이 권한을 요청해야 함
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
BLUETOOTH_CONNECT와 같이 존재하는 시스템 권한이나 <permission> 요소를 사용해 정의하는 사용자 지정 권한을 특정할 수 있음노트: 사용자 지정 권한은 앱이 설치될 때 등록됩니다. 사용자 지정 권한이 사용되기 전에 반드시 사용자 지정 권한을 정의하는 앱이 설치되어야 합니다.
registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)나 매니페스트의 <receiver> 태그를 사용해 브로드캐스트 리시버를 등록할 때 권한 매개 변수를 지정했다면, 매니페스트의 <uses-permission> 태그를 사용해 권한을 요청한 브로드캐스터만 리시버에 인텐트를 전송할 수 있음, 위험한 권한을 요청한 경우, 브로드캐스터는 반드시 권한을 승인 받아야 함권한 매개 변수가 있는 매니페스트에서 선언한 리시버 예시
<!-- 이 리시버가 시스템 또는 다른 앱(심지어 본인이 소유한 다른 앱)에서
보낸 브로드캐스트를 수신하는 경우, android:exported를 "true"로 설정하세요. -->
<receiver
android:name=".MyBroadcastReceiverWithPermission"
android:permission="android.permission.ACCESS_COARSE_LOCATION"
android:exported="true">
<intent-filter>
<action android:name="com.example.snippets.ACTION_UPDATE_DATA" />
</intent-filter>
</receiver>
권한 매개 변수가 있는 컨텍스트에 등록된 리시버 예시
ContextCompat.registerReceiver(
context, myBroadcastReceiver, filter,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
null, // 스레드를 정의하는 스케줄러, null은 메인 스레드에서 실행을 의미함
receiverFlags
)
위와 같은 리시버에 브로드캐스트를 전송하려면 아래와 같이 권한을 요청해야 함
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
같은 브로드캐스트를 전송받는 매니페스트에 선언된 브로드캐스트 리시버가 많은 앱에 있다면 시스템이 많은 앱을 실행해야하기 때문에 기기 성능과 사용자 경험에 상당한 영향을 끼침, 이를 피하려면 브로드캐스트 리시버를 매니페스트에 선언하기보다 컨텍스트에 등록하는 것을 더 우선으로 해야 함, CONNECTIVIY_ACTION과 같이 몇몇의 브로드캐스트는 컨텍스트에서 등록된 브로드캐스트 리시버만 수신할 수 있음
브로드캐스트를 수신한다고 등록한 어떤 앱이든 정보를 읽을 수 있기 때문에 암시적 인텐트를 사용해 민감한 정보를 브로드캐스트로 전송하지 말아야 함, 이를 막으려면 브로드캐스트를 전송할 때 권한을 지정하거나, 안드로이드 4(API 레벨 14) 이상에서 setPackage(String)을 사용해 브로드캐스트를 전송할 패키지를 지정할 수 있음
브로드캐스트 리시버를 등록하면 어떤 앱이든 해당 리시버에 잠재적으로 악의적인 브로드캐스트를 보낼 수 있으므로 브로드캐스트 리시버를 등록할 때 권한을 지정하거나 매니페스트에 선언된 리시버라면 android:exported 속성을 false로 설정해 다른 앱의 브로드캐스트를 수신하지 않는 방법이 있음
브로드캐스트 액션의 네임스페이스는 전역이므로 자신의 네임스페이스에 액션 이름과 나머지 문자열이 작성했는지 확인해야 함, 그렇지 않으면 다른 앱과 충돌할 수 있음
리시버의 onReceive(Context, Intent) 메소드가 메인 스레드에서 실행되기 때문에 실행 후 반환하는 것이 빨라야 함, onReceive() 가 반환되면 시스템이 프로세스를 중단시킬 수 있기 때문에 스레드를 생성하거나 백그라운드 서비스를 시작하는 것은 조심해야 함, 이런 상황을 피하려면 다음과 같은 방법이 있음
리시버의 onReceive() 메소드에서 goAsync()를 호출하고 BroadcastReceiver.PendingResult를 백그라운드 스레드에 전달하면 onReceive() 를 반환해도 브로드캐스트가 활성화 상태를 유지할 수 있음, 하지만 이 상황에서도 시스템은 브로드캐스트가 굉장히 일찍 끝날 것(10초 이내)으로 예상함, 이 방법을 사용하면 작업을 다른 스레드로 옮겨 메인 스레드에 문제가 발생하지 않게 할 수 있음
JobScheduler를 사용해 작업을 예약
리시버가 둘 이상일 때 브로드캐스트 리시버에서 액티비티를 시작하면 사용자 경험이 나빠질 수 있기 때문에 이 상황에서는 알림을 표시하는 것을 고려해볼 것
원본: https://developer.android.com/develop/background-work/background-tasks/broadcasts