BroadcastReceiver 부팅 시 실행하기
AndroidManifest.xml에 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> 권한을 추가하고, 해당 액션을 브로드캐스트 리시버에 등록한 뒤, 앱을 삭제 후 재설치 합니다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- ... -->
<application
<receiver android:name=".service.CustomBroadcastReceiver"
android:exported="false"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<!-- ... -->
</application>
</manifest>
안드로이드에서 권한은 크게 Install-time permissions, Runtime permissions 두 종류로 나뉘며, Install-time permissions에는 일반 권한, 서명 권한이, Runtime permissions에는 특별 권한이 포함됩니다. 이름 그대로 Install-time permissions는 어플리케이션 설치 시에 권한을 획득하고, 개발 과정에 Install-time permissions을 추가했다면 재설치를 통해 권한을 획득해야 합니다.
Runtime permissions은 어플리케이션 실행 시 추가적으로 사용자에게 권한 요청 메시지를 표시합니다. Runtime permissions에는 제한된 데이터에 액세스하고 시스템과 다른 앱에 큰 영향을 미치는 작업이 포함됩니다. 각 권한이 어느 유형에 속하는지는 다음 페이지에서 확인할 수 있습니다.
새로 설치한 앱이 다른 앱과 동일한 인증서로 서명된 경우에 권한을 부여하는 서명 권한과 같이 권한의 세부적인 사항에 대해 자세히 알고 싶다면 다음 링크를 참고하세요.
그렇다면 브로드캐스트는 어떤 방식으로 시스템 이벤트를 획득할 수 있을까요?
Broadcast Receiver 개요
안드로이드 앱은 시스템과 기타 안드로이드 앱에서 특정 이벤트가 발생하면, 해당 이벤트를 수신하여 적절한 행동을 취할 수 있습니다. Broadcast Receiver가 이벤트를 수신하는 역할을 맡으며, 이러한 형태의 브로드캐스트 수신 방식은 일반적인 게시-구독 디자인 패턴과 유사합니다. 이벤트는 시스템 부팅과 같은 시스템 이벤트와 관련 앱에 상태를 전달하기 위한 맞춤형 이벤트로 나뉩니다.
브로드캐스트는 백그라운드에서 실행되며 사용자 플로우 외부에서 메시징 시스템으로 활용할 수 있지만, 브로드캐스트에 응답하기 위해 시스템 성능이 저하될 수 있기 때문에 남용하지 않도록 주의해야 합니다. 메모리가 충분하지 않은 장치에서 많은 백그라운드 작업이 실행되면, 시스템은 메모리 확보를 위해 프로세스를 여러 차례 스왑하며(Memory Thrashing), 이 과정에서 전체적인 시스템 퍼포먼스가 악화됩니다.
대부분의 경우 전체 앱에 브로드캐스트 되는 암시적 브로드캐스트(Implicit Broadcast) 때문에 수많은 백그라운드 작업이 실행됩니다. 인터넷 연결 상태를 브로드캐스트 하는 CONNECTIVITY_CHANGE 액션을 예로 생각해보면, 수많은 앱이 인터넷 연결 상태에 따라 다른 행동을 취하기 위해 해당 액션을 수신하며, 인터넷 연결 상태는 짧은 시간에 여러 번 변경되며 액션을 송신합니다.
암시적 브로드캐스트 제한
이러한 문제 때문에 CONNECTIVITY_CHANGE는 다음과 같이 변경되었습니다. Android N 이상의 타겟에서는 Android_Manifest.xml에 등록한 static broadcast receiver에서 CONNECTIVITY_CHANGE를 사용할 수 없습니다. 대신 context에 runtime receiver를 등록하여 앱이 실행되는 동안에만 네트워크 상태를 받아오는 방식은 가능합니다.
시스템 브로드캐스트 방식은 안드로이드 플랫폼 버전에 따라 작동 방식이 변경됩니다. 변경 기점은 Android 7.0 (API 24)로 해당 버전 이상의 앱을 타겟으로 삼고 있다면 다음 사항을 확인하여야 합니다.
Android 7.0
Android 7.0(API 레벨 24) 이상에서는 다음과 같은 시스템 브로드캐스트를 전송하지 않습니다.
또한 Android 7.0 이상을 타겟팅하는 앱은 registerReceiver(BroadcastReceiver, IntentFilter)를 사용하여 CONNECTIVITY_ACTION 브로드캐스트를 등록해야 합니다. manifest에 수신자를 선언해도 작동하지 않습니다.
Android 8.0
Android 8.0(API 레벨 26)부터 시스템은 manifest에 선언된 수신자에 추가 제한을 부과합니다.
앱이 Android 8.0 이상을 타겟팅하면 manifest를 사용하여 대다수 암시적 브로드캐스트(앱을 구체적으로 타겟팅하지 않는 브로드캐스트)의 수신자를 선언할 수 없습니다. 대부분의 경우 scheduled jobs를 대신 사용하여 같은 효과를 낼 수 있으며, 사용자가 앱을 직접 사용할 때에는 컨텍스트에 등록된 수신자를 이용하여 해당 액션을 계속 사용할 수 있습니다.
BOOT_COMPLETED가 여기에 영향을 받지 않을까요? 한 번 생각해보시기 바랍니다.
Android 9
Android 9(API 28)부터 NETWORK_STATE_CHANGED_ACTION 브로드캐스트는 사용자의 위치 또는 개인 식별 데이터에 관한 정보를 수신하지 않습니다.
또한 앱이 Android 9 이상을 실행하는 기기에 설치되면 Wi-Fi의 시스템 브로드캐스트에는 SSID, BSSID, 연결 정보 또는 검색 결과가 포함되지 않습니다. 이 정보를 얻으려면 대신 getConnectionInfo()를 호출해야 합니다.
암시적 브로드캐스트 예외
앞서 설명한 것과 같이, 앱이 Android 8.0 (API 26) 이상을 타겟팅하면 manifest를 사용하여 대다수 암시적 브로드캐스트(앱을 구체적으로 타겟팅하지 않는 브로드캐스트)의 수신자를 선언할 수 없습니다. 물론 여기에도 예외는 있습니다만, 꼭 필요한 경우에만 사용하여야 합니다. 예외로 취급되는 암시적 브로드캐스트 목록과 예외 처리된 이유는 다음에서 확인할 수 있습니다.
우리가 사용한 ACTION_BOOT_COMPLETED는 예외로 처리된 첫 번째 항목입니다. 이 브로드캐스트는 부팅이 시작된 뒤 딱 한 번만 전달되며, 많은 어플리케이션이 해당 브로드캐스트를 이용하여 스케쥴링, 알람 등을 등록하기 때문에 예외 처리 되었습니다. 예외 목록을 살펴보면 대부분의 예외 처리 사유가 "수신 빈도가 적음", "더이상 일반 앱이 수신할 수 없음", "대안 없음" 등으로 나타나는 걸 볼 수 있습니다.
브로드캐스트 수신
브로드캐스트는 두 가지 방식으로 수신할 수 있습니다. manifest-declared 방식과 context-registered 방식입니다.
Manifest-declared receivers
manifest 파일에 브로드캐스트 리시버를 선언하면, 브로드캐스트가 발생했을 때 시스템이 앱을 실행합니다. 다음과 같은 과정을 거쳐 브로드캐스트를 수신할 수 있습니다.
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)를 구현합니다.
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 (Context, Intent)가 실행되는 동안에만 유효합니다. 이 메서드에서 코드가 반환되면 시스템은 구성 요소가 더 이상 활성 상태가 아닌 것으로 간주합니다.
Context-registered receivers
context에 리시버를 등록하기 위해 다음 과정을 따릅니다
1. BroadcastReceiver를 정의하고 인스턴스를 생성합니다.
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()
}
}
}
}
val br = MyBroadcastReceiver()
2. 인텐트 필터를 생성하고 리시버를 등록합니다.
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
}
registerReceiver(br, filter)
로컬 브로드캐스트에 리시버를 등록하려면 LocalBroadcastManager.registerReceiver(BroadcastReceiver, IntentFilter)를 사용합니다. context-registered 방식은 리시버가 등록된 context가 유효한 한 브로드캐스트를 수신합니다.
3. 더이상 브로드캐스트를 수신할 필요가 없거나, context가 더이상 유효하지 않을 때 브로드캐스트 수신을 중지하려면 unregisterReceiver(BroadcastReceiver)를 호출합니다. 리시버가 중복 등록되는 것을 막기 위해 등록 시점을 고려해야 합니다. onCreate(Bundle)에서 등록한 경우 onDestroy()에서, onResume()에서 등록한 경우 onPause()에서 등록 해제하여 메모리 누수와 불필요한 시스템 오버헤드를 막을 수 있습니다. onSaveInstanceState(Bundle)는 사용자가 뒤로가기를 눌렀을 때에는 호출되지 않기 때문에 해당 영역에서 등록을 해제하려고 하면 문제가 생길 수 있습니다.
<receiver> Properties
<receivier>는 다음과 같은 속성을 가질 수 있으며, 각각의 속성의 의미는 다음과 같습니다.
<receiver android:directBootAware=["true" | "false"]
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . .
</receiver>
android:directBootAware : true 시 direct-boot 상태를 획득하여 사용자가 디바이스를 잠금 해제하기 전에 실행될 수 있습니다. 이렇게 실행되었을 때 어플리케이션은 디바이스의 protected storage에 저장된 데이터에만 액세스 가능합니다.
default = false
android:enabled : true 시 브로드캐스트 리시버가 시스템에 의해 실행될 수 있습니다. <application> 태그의 enabled 속성과 <receiver>의 속성이 모두 true여야 정상적으로 실행됩니다.
default = true
android:exported : true 시 브로드캐스트 리시버가 외부 어플리케이션으로부터 메시지를 수신할 수 있습니다. 리시버가 하나 이상의 인텐트 필터를 가지고 있다면 디폴트 값은 true입니다. 해당 속성만이 브로드캐스트 리시버 외부 노출을 막을 수 있는 것은 아니며, 권한 설정을 통해서도 같은 효과를 낼 수 있습니다.
android:icon : 브로드캐스트 리시버를 대표하는 아이콘으로, drawable 리소스를 참조하도록 설정되어야 합니다. 디폴트 값은 <application>의 icon 속성과 같습니다. <receiver>의 icon 속성은 다시 <intent-filter>의 icon 속성으로 전달됩니다.
android:label : 사용자가 읽을 수 있는 라벨입니다. 디폴트 값은 <application>의 label 속성과 같습니다.<receiver>의 label 속성은 다시 <intent-filter>의 label 속성으로 전달됩니다. 라벨 속성 값이 문자열 리소스를 참조하여 언어에 따라 현지화 가능하도록 만들어야 합니다.
android:name : 브로드캐스트 리시버를 구현한 클래스의 이름. BroadcastReceiver를 상속해야 합니다. 이 이름은 정규화된 클래스 이름(예: 'com.example.project.ReportReceiver')이어야 하지만 이름의 첫 번째 문자가 마침표인 경우(예 '. ReportReceiver') <manifest> 요소에 지정된 패키지 이름에 추가됩니다. 애플리케이션을 게시한 후에는 android:exported="false"를 설정하지 않은 경우 이 이름을 변경하면 안 됩니다. 기본값은 없으며, 이름을 지정해야합니다.
android:permission : broadcaster가 broadcast receiver에 메시지를 보내는 데 필요한 권한 이름입니다. 이 속성이 설정되어 있지 않으면 <application> 요소의 permission 속성에서 설정한 권한이 broadcast receiver에 적용됩니다. 아무 속성도 설정되어 있지 않으면 receiver가 권한으로 보호되지 않습니다.
android:process : broadcast receiver가 실행되어야 하는 프로세스의 이름입니다. 일반적으로 애플리케이션의 모든 구성요소는 애플리케이션용으로 생성된 기본 프로세스에서 실행됩니다. 애플리케이션 패키지와 이름이 동일합니다. <application> 요소의 process 속성은 모든 구성 요소에 다른 기본값을 설정할 수 있습니다. 하지만 각 구성요소는 애플리케이션을 여러 프로세스에 분산할 수 있도록 자체 process 속성으로 기본값을 재정의할 수 있습니다.
이 속성에 지정된 이름이 콜론(':')으로 시작되면 필요에 따라 애플리케이션에만 공개되는 새 프로세스가 생성되고, 생성된 프로세스에서 broadcast receiver가 실행됩니다. 프로세스 이름이 소문자로 시작되는 경우 receiver는 그 이름의 전역 프로세스에서 실행됩니다(실행 권한이 있는 경우). 이를 통해 다른 애플리케이션의 구성요소가 프로세스를 공유하여 리소스 사용을 줄일 수 있습니다.
'Android' 카테고리의 다른 글
안드로이드 RoomDatabase in Java (0) | 2021.02.25 |
---|---|
#Broadcast Receiver 비동기 백그라운드 작업 #doAsync #JobScheduler (0) | 2021.02.18 |
Android Context란 무엇일까? (0) | 2021.02.08 |
View Binding = findViewById 대체하는 방법, View Binding과 Data Binding 차이 (1) | 2021.02.08 |
#Volley 로 안드로이드 앱에 #API #JSON 연결하기 (0) | 2021.02.02 |