코딩하기 좋은날
Android 브로드 캐스트 인덴트 / 리시버 (kotlin) 본문
오늘은 안드로이드 4대 컴포넌트 중 하나인 BroadCast Receiver에 대해서 알아 보겠습니다.
BroadCast Intent
먼저 그전에 BroadCast Intent에 대해서 얘기를 해보겠습니다. 기본적으로 Intent는 액티비이를 론칭 하는데 사용이 되는데 시스템의 다른 컴포넌트들에게 시스템 차원의 메시지를 전달하는데도 사용 할 수 있습니다.
BoradCast Intent는 Activity 클래스의 sendBroadcast() / sendOrderedBroadcast() 메서드를 호출하여 전파할 수 있습니다. 둘의 차이점은 모든 리시버에게 broadcast를 전송하느냐 / 한번헤 하나의 리시버에게 순차적으로 전송하느냐의 차이를 가집니다. sendOrderedBroadcast는 우선순위를 지정하여 전송을 받고 그에 대한 결과값을 이용하여야 하거나 할 때 사용하게 됩니다.
BroadCast Intent는 action string이라는 것을 포함해야 하는데 이 action string 값을 intent filter 에 가지고 있는 receiver에게 보내기 위해 사용 됩니다. 일반적으로는 패키지 이름 형태를 사용합니다. 또한 Intent와 동일하게 extra를 전달 할 수 있습니다. 간단하게 예를 들면 아래와 같습니다.
Intent().also { intent ->
intent.action = "com.example.mybroadcast.MY_NOTIFICATION"
intent.putExtra("data", "Notice me senpai!")
sendBroadcast(intent)
}
action값을 지정해주고 Extra를 전달하고 싶으면 값을 넣어주면 됩니다. 이렇게 하면 저러한 action이 intent-filter에 등록되어 있는 앱의 리시버의 onReceive 함수가 실행 되게 됩니다.
BroadCastReceiver
자 그렇다면 BroadCastReceiver를 어떻게 구현하는지 살펴보겠습니다.
우선은 BroadcastReceiver 클래스를 상속받고 onReceive 메서드를 오버라이딩 하여야 합니다. 이후
BroadcastReceiver를 코드 또는 메니페스트 파일에 등록하여야 하는데 이를 동적/정적 방식으로 구분합니다. 메니페스트 파일에 등록된 리시버는 삭제가 불가하기 때문에 정적이라 부르고 코드 에서 등록한 리시버는 등록과 삭제가 가능하기 때문입니다.
그런데 앱이 API 레벨 26 이상을 타겟팅 할때 암시적(implicit) 브로드캐스트의 리시버 등록은 manifest를 통할 경우 특정 예외 상황을 제외하면 불가합니다.
https://developer.android.com/guide/components/broadcast-exceptions
아래에 그 예외 상황들이 있습니다. 제가 테스트할 프로그램에서 문자메시지를 수신하는 상황은 예외 상황이므로 Manifest에 Reciver를 등록한 것을 이후 볼 수 있습니다. 시스템 성능 저하의 문제로 인해서 그런 것 같습니다. Manifest에 등록해놓으면 앱이 실행 되어 있지 않은 상태에서도 receiver가 실행 되기 때문입니다.
따라서 onStart / onDestroy에서 코드를 통해 리시버를 등록/해제 하거나 (이때는 앱이 Destroy되지 않았다면 화면에 표시되지 않아도 브로드캐스트를 받을 수 있습니다.)
onResume/ onPause 에서 등록 / 해제를 하여 사용 하면 될 것 같습니다. (이때는 앱이 현재 실행중 일때만 브로드캐스트를 받을 수 있습니다.)
자그렇다면 문자를 수신하여 그 내용을 editText에 입력하는 Receiver와 받은 Extra를 toast로 나타내는 Receiver 를 구현한 예제를 한번 보겠습니다.
예제프로그램
AndroidManifest.xml
문자를 읽기위해서 uses-permission에서 해당 권한을 얻어오고
receiver 를 등록해주면 됩니다. enabled 는 인스턴스화 가능 여부를 뜻하고, exported 는 외부에서 메시지를 받을 수 있는지 여부를 뜻합니다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mybroadcastexample">
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".SMSReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
MainActivity.kt
앱이 실행될 때만 broadcast를 받기위해 Receiver를 해당 부분에서 등록/해제를 해주고 버튼을 클릭해 broadcast를 보내는 이벤트를 등록해 줍니다. 또한 메시지를 받았을 때 editText에 적는 부분의 코드도 있습니다. 앱이 꺼져 있을 때는 onCreate 부분에서 processedIntent 메서드를 실행하고 앱이 Destroy 되지 않은 경우 메시지를 받았을 때의 처리를 위해 onNewIntent에서 또한 메서드를 실행 시키는 코드입니다.
package com.example.mybroadcastexample
import android.Manifest
import android.content.BroadcastReceiver
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
lateinit var br:BroadcastReceiver
lateinit var filter: IntentFilter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
intent = getIntent()
processedIntent(intent)
button.setOnClickListener {
Intent().also { intent ->
intent.action = "com.example.mybroadcast.MY_NOTIFICATION"
intent.putExtra("data", "Notice me senpai!")
sendBroadcast(intent)
}
}
}
override fun onResume() {
br = TestReciver()
filter = IntentFilter().apply {
addAction("com.example.mybroadcast.MY_NOTIFICATION")
}
registerReceiver(br, filter)
super.onResume()
}
override fun onPause() {
unregisterReceiver(br)
super.onPause()
}
//문자열 set
fun processedIntent(intent:Intent?) {
val str = intent?.getStringExtra("data")
editText.setText(str)
}
//앱이 종료되지 않은 상태에서 메시지를 받을경우 Receiver 에서 startActivity에 의해 onNewIntent가 실행됨.
override fun onNewIntent(intent: Intent?) {
processedIntent(intent)
super.onNewIntent(intent)
}
}
SMSReceiver.kt
SMS를 수신하는 Receiver 입니다.
package com.example.mybroadcastexample
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.telephony.SmsMessage
import android.util.Log
import androidx.core.content.ContextCompat.startActivity
class SMSReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if("android.provider.Telephony.SMS_RECEIVED".equals(intent?.action)) {
val bundle = intent?.getExtras()
val message = parseMessage(bundle)
if (message.size > 0) {
val contents = message[0]?.messageBody.toString()
sendToActivity(context, contents)
}
}
}
fun sendToActivity(context:Context?, str: String) {
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra("data", str)
context?.let {
it.startActivity(intent)
}
}
fun parseMessage(bundle: Bundle?): Array<SmsMessage?> {
val objs = bundle?.get("pdus") as Array<Any>
val messages = arrayOfNulls<SmsMessage>(objs.size)
for( i in 0 until messages.size) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val format = bundle?.getString("format")
messages[i] = SmsMessage.createFromPdu(objs[i] as ByteArray, format)
}
else {
messages[i] = SmsMessage.createFromPdu(objs[i] as ByteArray)
}
}
return messages
}
}
TestReciver.kt
package com.example.mybroadcastexample
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import android.widget.Toast
class TestReciver: BroadcastReceiver() {
//intent 에 해당 Reciver 넣어서 sendBroadCast 하면 해당 Reciver를 받을 수 있는 앱이 받고 onReceive 동작을 함
override fun onReceive(context: Context?, intent: Intent?) {
var data1 = intent?.getStringExtra("data")
Toast.makeText(context, data1, Toast.LENGTH_SHORT).show()
}
}
버튼 클릭시 TestReceiver가 동작하여 아래와 같이 토스트 메시지가 나옵니다.
문자수신시 SMSReceiver가 동작하여 문자가 출력 되는 것을 볼 수 있습니다.
BroadcastReceiver는 이외에도 배터리 부족, usb 연결 등등 많은 액션들에 반응 해야 할 때 유용하게 사용 할 수 있습니다.
'Android' 카테고리의 다른 글
Android Service ( Start / Intent / bound) 란? - 2 (0) | 2020.06.19 |
---|---|
Android Service ( Start / Intent / bound) 란? - 1 (0) | 2020.06.15 |
Android Realm 기본 사용법(Kotlin) (0) | 2020.05.24 |
Navigation 과 Fragment (Kotlin) (0) | 2020.05.23 |
Gradle 이란? (0) | 2020.05.20 |