Android 4대 컴포넌트 (BroadcastReceiver)

SSY·2025년 1월 25일
0

AndroidFramework

목록 보기
1/3
post-thumbnail

시작하며

안드로이드 앱은 Android System, System App, Thrid Party App으로부터 이벤트를 받을 수 있다.

우선 Android System이란 핸드폰에 기본으로 설치된 앱이 아닌, 시스템 자체에서 보내는 브로드캐스트를 의미한다. 대표적으로 비행기 모드 상태, 네트워크 연결 상태, wifi 연결 여부, 셀룰러 데이터 연결 여부 등이 있다.

System App이란 우리가 핸드폰을 살 때, 기본적으로 설치된 앱을 의미한다. 이 앱에서 또한 우리가 만든 앱에 브로드캐스트를 전송할 수 있다. 대표적으로 문자메시지를 수신받았을 때, 전화가 왔을 때 등이 있다.

마지막으로 개발자가 직접 만든 앱인 Third Party App에선 개발자가 원하는 이벤트를 전송할 수 있다. 가령, A라는 회사에서 사내 시스템 앱으로 캘린더 앱과 업무용 앱을 만들었다 치자. 이때, 캘린더 앱 일정이 등록되었을 때, 업무용 앱이 브로드캐스트를 받는 경우가 있다.

브로드캐스트 리시버의 구현 및 흐름

1. BroadcastReceiver 구현하기

브로드캐스트 구현을 위해 우선 해야할 일은 BroadcastReceiver의 구현 클래스를 만드는 것이다. 이 클래스를 오버라이딩 했을 시, 내부적으로 onReceive(context Context, intent Intent) {...}콜백 메서드를 작성할 수 있다. 이 곳이야말로 내 앱이 브로드캐스트를 받았을 때 호출되는 곳이다.

onReceive()메서드의 실행 방식의 이해에 따른 주의점이 있다. 안드로이드 시스템은 메모리 최적화를 위해 onReceive() 메서드 실행이 끝까지 진행되었을 때 해당 객체를 메모리에서 삭제하려 한다. 따라서 onReceive()메서드에서 장기적인 백그라운드 작업을 진행시켰을 때, ANR에러 및 앱의 비정상종료가 발생한다. 물론, onReceive()는 메인 스레드에서 동작한다. 이런 이유로 안드로이드 공식 홈페이지에 의거, 아래 방식을 통해 비동기 작업을 진행해야한다.

[onReceive에서 비동기작업 권장사항]
1. goAsync()메서드 호출 및 비동기 작업 실행함을 알림.
2. 무거운 작업 실행 시, Service/WorkManager/JobScheduler/Coroutine의 사용 고려.
3. 작업 완료 시, goAsync()의 반환타입인 PendingResult타입의 .finish()호출로 비동기 작업이 끝났음을 알림

아래는 안드로이드 공식 홈페이지에 있는 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"
            // Do something with the data, for example send it to a data repository:
            dataRepository.updateData(data)
        }
    }
}

2. 리시버 등록 방식 결정하기

앱을 처음 설치했을 때, 브로드캐스트 리시버를 등록할 것인가? 아니면 안드로이드 컴포넌트 수명주기에 맞춰 리시버를 등록할 것인가? 를 경정해야 한다.

[리시버 등록 법]

  • Manifest.xml에 등록
  • Android Component에 등록

'전자'의 경우, Manifest.xml을 통해 등록할 수 있다. 이는 Manifest.xml에 리시버가 등록된 앱은 해당 앱 설치 시, 안드로이드 시스템이 브로드캐스트를 등록시킨다. 그럼으로써 Android System/Thrid Party App/System App에서 발생한 브로드캐스트를 해당 앱에도 전송시켜주게 된다. 하지만 이 방식은 문제가 존재한다. 만약 A라는 사람의 핸드폰에 10개의 앱이 있고, 이들은 동일한 시스템 브로드캐스트를 Manifest.xml을 통해 등록되었다고 가정해보자. 이럴 경우, A라는 사람의 핸드폰에서 브로드캐스트가 발생할 때마다 10개의 앱에 브로드캐스트가 전송하게 되고 해당 앱들이 실행되므로 배터리를 많이 사용하게 된다. 따라서 안드로이드 공식 홈페이지는 이보단 '후자'의 방법을 권장한다.

'후자'의 경우, Activity/Service에서 등록할 수 있다. 이 컴포넌트들은 생명주기가 존재하므로, 개발자가 원하는 타이밍에 리시버를 등록하고 해제하는것이 가능하다. 다만, 이 방식은 메모리 누수가 발생할 수 있는데, 이를 방지하기 위해 개발자는 unregisterReceiver만 신경써서 잘 호출해주면 된다. 아래는 예시 코드이다.

// Compose의 생명주기에 따라 리시버 등록/해제하기
@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() {
    // Implement your screen
}
// Activity의 생명주기에 따라 리시버 등록/해제하기
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()
        // When you forget to unregister your receiver here, you're causing a leak!
        this.unregisterReceiver(myBroadcastReceiver)
    }
}

3. 보안 범위 설정하기

내가 등록한 리시버를 어느 범위까지 공개할지 설정할 수 있다. 2가지 방식이 있는데, 한 가지는 exported설정이고, 또 한가지는 permission설정이다.

[보안 범위 설정 법]

  • exported
  • permission

Android System/System App/Third Party App이 브로드캐스트를 보낸다 한들, 위 설정값에 따라 브로드캐스트 전송을 막을 수 있다. 이 방식을 고려해야하는 이유는 악의적인 해커가 적절치 않은 브로드캐스트를 전송했을 경우, 비용이 발생하기 때문이다. 대표적으로 브로드캐스트 수신 시, API호출을 한다고 가정해보자. 이때 해커가 브로드캐스트의 악의적 전송을로 서버의 무한정 API호출 및 공격을 시도할 수 도 있다.

따라서 위 방식들로 이를 방어할 필요가 있다. 우선 exported속성이란, System App/Third Party App이 전송하는 브로드캐스트를 받지 않겠다는 선언이다. 즉, 어떠한 App에게도 내가 가진 리시버를 노출하지 않겠다는 선언이다.

그 다음 permission이란, 특정 브로드캐스트 실행을 위해, 송신자와 수신자 모두 권한을 허용해야함을 의미한다. 예를 들어, 문자 메시지 브로드캐스트가 있다 가정해보자. 그럼 우선, 송/수신자는 해당 권한을 Manifest.xml에 명시하고 사용자로부터 허용받아야만 한다.

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

송신자 앱도 권한을 받았으니 수신자 앱도 권한을 받아야한다. 그러기 위해 해당 앱의 리시버 선언 시, 아래와 같은 코드 작성이 이뤄져야 한다.

[Manifest에서 리시버 등록]

<receiver
    android:name=".util.MySMSReceiver"
    android:enabled="true"
    android:exported="true"
    android:permission="android.permission.BROADCAST_SMS">
    <intent-filter>
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
</receiver>

[Component에서 리시버 등록]

val smsReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent?.action == "android.provider.Telephony.SMS_RECEIVED") {
            println("SMS received!")
        }
    }
}

fun registerSmsReceiver(context: Context) {
    val intentFilter = IntentFilter("android.provider.Telephony.SMS_RECEIVED")
    context.registerReceiver(smsReceiver, intentFilter, "android.permission.BROADCAST_SMS", null)
}

fun unregisterSmsReceiver(context: Context) {
    // 등록한 리시버를 해제
    context.unregisterReceiver(smsReceiver)
}

4. 송신자의 전송 방식

브로드캐스트 송신자 즉, System App/Thrid Party App 입장에선 전송할 브로드캐스트 종류와 전송할 앱을 결정한다.

[송신자의 전송 방식]

  • 전송할 브로드캐스트 종류
  • 전송할 앱

우선 System App의 경우를 보자. 이 앱들은 안드로이드 기기에 설치되는 모든 앱에게 브로드 캐스트를 전송해야한다. 따라서 setPackage와 같은 옵션은 따로 설정하지 않는것으로 보인다. 하지만 Thrid Party App의 경우, 하나의 회사에서 만들어진 업무 앱들끼리만 브로드캐스트를 전송하기 위해 setPackage를 설정할 수 있다.

즉, 전송한 브로드캐스트의 종류는 System App이든, Thrid Party App이든간에 무조건적으로 설정한다. 예를 들어, System App에 해당하는 문자메시지 앱의 경우, android.provider.Telephony.SMS_RECEIVED를 전송함으로써 수신받은 문자메시지를 모든 앱들에게 전송한다. (setPackage없이) 반면, Thrid Party App의 경우, kr.co.helloApp.CUSTOM_BROADCAST와 같은 이벤트를 전송할 수 있다.

profile
불가능보다 가능함에 몰입할 수 있는 개발자가 되기 위해 노력합니다.

0개의 댓글