
앱을 만들면서 4대컴포넌트인 액티비티, 서비스, 브로드 케스트 리시버, 컨텐츠 프로바이더가 있습니다. 오늘은 그중 브로드 케스트 리시버에 관하여 말해 볼까합니다.
브로드캐스트 리시버(BroadcastReceiver)를 해석하면 방송 + 수신입니다. 말그대로 브로드캐스트 리시버는 앱이나 시스템에서 발생한 특정 이벤트를 감지하고 처리하는 구성 요소입니다. 브로드캐스트 리시버를 사용하면 시스템 전반에서 발생하는 다양한 이벤트를 수신하고 이에 대한 대응을 할 수 있다고 합니다. 그렇다면 어떻게 이벤트를 수신할수 있을까요?

브로드캐스트 리시버가 이벤트를 수신하는 방법은 안드로이드 시스템에서 이벤트가 발생하면 해당 이벤트를 Broadcast Intent로 보내는 것입니다. 브로드캐스트 리시버는 이러한 Broadcast Intent를 수신하고, 등록된 필터와 일치하는 이벤트를 처리할 수 있습니다.
이러한 이벤트는 부팅 완료나 배터리 부족처럼 시스템에서 발생할 수도 있고, SMS 수신,사용자 정의 액션처럼 앱에서도 발생할 수 있습니다. 각 브로드캐스 이벤트는 또한 정적과 동적으로 나눌수 있습니다.
이 경우에는 브로드캐스트 리시버가 앱의 Manifest 파일에 등록됩니다. 앱이 설치될 때 시스템은 Manifest 파일을 검사하여 등록된 모든 브로드캐스트 리시버를 확인하고 시스템 레벨에서 관리합니다. 정적 리시버는 앱이 설치될 때부터 항상 활성화되며, 앱이 실행 중이 아니더라도 브로드캐스트 메시지를 수신할 수 있습니다.
이 경우에는 앱이 실행 중에 코드에서 브로드캐스트 리시버를 등록합니다. 앱의 컴포넌트(예: 액티비티, 서비스 등)에서 브로드캐스트 리시버를 등록하고, 필요에 따라 등록을 해제할 수 있습니다. 동적 리시버는 앱이 실행 중일 때만 활성화되며, 앱이 종료되면 자동으로 등록이 해제됩니다.
동적 브로드캐스트 리시버는 보다 유연한 제어가 가능하고, 필요한 경우에만 활성화할 수 있어 자원을 효율적으로 관리할 수 있습니다. 반면에 정적 브로드캐스트 리시버는 앱이 설치될 때부터 항상 활성화되므로 특정 이벤트를 수신하기 위해 항상 리소스를 사용하게 됩니다.
class SmsReceiver : BroadcastReceiver() {
private val TAG = "SmsReceiver"
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action.equals(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)) {
val bundle = intent?.extras
if (bundle != null) {
val pdus = bundle.get("pdus") as Array<Any>?
pdus?.let {
for (pdu in pdus) {
val smsMessage = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
SmsMessage.createFromPdu(pdu as ByteArray, bundle.getString("format"))
} else {
SmsMessage.createFromPdu(pdu as ByteArray)
}
val sender = smsMessage.originatingAddress
val messageBody = smsMessage.messageBody
Log.d(TAG, "SMS Received from: $sender, Message: $messageBody")
// 수신한 SMS에 대한 작업 수행
}
}
}
}
}
}
<receiver android:name=".SmsReceiver" android:enabled="true">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
object DownloadUtil {
private var downloadId: Long = -1L
private var isBroadCasterRegistered: Boolean = false
private lateinit var downloadManager: DownloadManager
private var onDownloadComplete = object: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
intent?.let {
val id = it.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L)
if(downloadId == id) {
val query: DownloadManager.Query = DownloadManager.Query() //다운로드 항목 조회에 필요한 정보 가져오기
query.setFilterById(id)
val cursor = downloadManager.query(query)
cursor.moveToFirst()
val columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
val columnReason = cursor.getColumnIndex(DownloadManager.COLUMN_REASON)
val columnUri = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
val columnType = cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE)
val status = cursor.getInt(columnIndex)
val reason = cursor.getInt(columnReason)
val uri = cursor.getString(columnUri)
val type = cursor.getString(columnType)
cursor.close()
when(status) {
DownloadManager.STATUS_SUCCESSFUL -> {
uri?.let {
val file = File(Uri.parse(uri).path.toString())
Log.e("####(success)", "uri:$it, file path:${file.path}, exists:${file.exists()}")
}
}
DownloadManager.STATUS_PAUSED -> {
Toast.makeText(context, "다운로드가 중단 되었습니다.", Toast.LENGTH_SHORT).show()
}
DownloadManager.STATUS_FAILED -> {
Toast.makeText(context, "다운로드가 취소 되었습니다.", Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
fun downLoadFile(context: Context, downloadUrl: String, fileName:String) {
downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val intentFilter: IntentFilter = IntentFilter().apply {
addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED)
}
context.registerReceiver(onDownloadComplete, intentFilter)
try {
val request = DownloadManager.Request(Uri.parse(downloadUrl))
.setTitle(fileName)
.setDescription("Downloading...")
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setAllowedOverMetered(true)
.setAllowedOverRoaming(true)
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
downloadId = downloadManager.enqueue(request)
isBroadCasterRegistered = true
} catch (e: Exception) {
e.printStackTrace()
}
}
fun unregisterReceive(context: Context) {
if(isBroadCasterRegistered) {
context.unregisterReceiver(onDownloadComplete)
isBroadCasterRegistered = false
}
}
}
class MainActivity : AppCompatActivity() {
private lateinit var exampleBroadcastReceiver : BroadcastReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initBroadCastReceiver()
findViewById<Button>(R.id.button).setOnClickListener {
sendCustomBroadcast()
}
}
private fun initBroadCastReceiver() {
exampleBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.e("*****", "onReceive: intent.action - ${intent?.action}", )
}
}
val filter = IntentFilter().apply {
addAction("com.example.ACTION_CUSTOM_BROADCAST")
}
registerReceiver(exampleBroadcastReceiver, filter)
}
private fun sendCustomBroadcast() {
val intent = Intent("com.example.ACTION_CUSTOM_BROADCAST")
sendBroadcast(intent)
}
override fun onDestroy() {
super.onDestroy()
// 액티비티가 종료될 때 브로드캐스트 리시버 해제
unregisterReceiver(exampleBroadcastReceiver)
}
}
reference
https://developer.android.com/develop/background-work/background-tasks/broadcasts?hl=ko
https://brunch.co.kr/@mystoryg/48
https://velog.io/@leeyjwinter/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Broadcast-Receiver