본문 바로가기

Android/Basic

[Android] Broadcast receiver 예제

! 참고로 해당 포스트에서는 브로드캐스트를 발신하는 예제는 없습니다.


1. 브로드캐스트 수신자

 브로드캐스팅이란, 메시지를 여러 대상에게 전달하는 것을 말한다. 예를 들어, 채팅 앱에서 이대일 채팅을 할 때 메시지는 두 사람끼리 주고받지만 여러 사람에게 한꺼번에 전달하고 싶을때는 그룹 채팅방을 만들어 한번 쓴 글을 여러 사람에게 전달하는 것을 상상하면 쉽다.

 안드로이드는 여러 애플리케이션 구성 요소에게 메시지를 전달하고 싶을 때, 브로드캐스팅을 사용한다. 가장 전형적인 예로 SMS가 있다. 이 때, 인텐트를 사용한다.

 앱에서 스마트폰 전역에 발생되는 글로벌 이벤트(global event)를 받아 처리하려면 브로드캐스트 수신자를 등록해야한다. 글로벌 이벤트란 "전화가 왔습니다.", "문자 메시지가 도착했습니다."와 같이 안드로이드 시스템 전체에 보내지는 이벤트를 말한다.

 브로드캐스트는 화면(UI)이 없고, 시스템이 관리한다.

 인텐트를 이용해서 액티비티를 실행하면, foreground로 실행되어 사용자에게 보여지지만, 브로드캐스트를 이용해서 처리하면 background로 동작하므로 사용자에게 보여지는 것이 없다. (그래서 앱이 실행중이 아니더라도 이벤트를 받을 수 있다) 인텐트를 받게되면 onReceive() 메소드가 자동으로 호출되는데, 이 메소드에서 브로드캐스트 메시지를 수신했을 때 어떤 기능을 처리할지 구현하면 된다.


 브로드캐스트를 이용해서 인텐트 객체를 직접 보낼 수 있고 받을 수도 있다.


 중요한 점은, SMS 브로드캐스팅 메시지를 받을 때 필요한 권한을 추가해야한다. 하지만 마쉬멜로우버전 이후부터는 특정 권한이 위험권한이 되었기때문에, 매니페스트에 추가한다고 해서 해결할 수 없고 사용자에게 다시 한 번 요청해야한다. 


2-1. 브로드캐스트 리시버 기본 코드


// 화면이 필요없기 때문에 메인액티비티 코드 생략.

 위와 같이 브로드캐스트 수신자를 만들면, 자동으로 매니페스트에 추가된다.

브로드캐스트 수신자는 인텐트필터를 포함하며, 매니페스트에 등록함으로써 인텐트를 받을 준비를 한다. (코드에서 RegisterReceiver로 동적으로 추가할 수 있다.)


// Manifest.xml

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

 인텐트 필터가 없으면, 모든 브로드캐스팅 메시지에 응답한다는 의미가 되기 때문에 받고자하는것이 무엇인지 등록해야한다. (인텐트 필터는 거름종이 처럼 특정 인텐트를 걸러내는 역할)


public class SmsReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Log.i("SmsReceiver","onReceive 호출됨.");
}
}

화면이 없는 서비스이기 때문에 앱을 구동하지 않아도 된다.



스샷이 잘 안보이지만 에뮬레이터를 구동한 후 설정화면에서 찾아보면 된다.

에뮬레이터에서 가상으로 메시지를 보내면 로그창에 찍히는 것을 확인해볼 수 있다. 



2-2. 브로드캐스트 리시버로 받은 데이터 출력해보기

수신한 메시지의 데이터를 보고싶다면? 브로드캐스트 리시버 클래스를 다시 보자.

@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG,"onReceive 호출됨.");

Bundle bundle = intent.getExtras();
SmsMessage[] messages = parseSmsMessage(bundle);

if (messages.length >0){
String sender = messages[0].getOriginatingAddress(); // 발신번호
Log.d(TAG, "sender : "+sender);

String contents = messages[0].getMessageBody().toString();
Log.d(TAG, "contents : "+contents);

Date receivedDate = new Date(messages[0].getTimestampMillis()); // 발신 시각
Log.d(TAG, "received date : "+receivedDate);
}
}

private SmsMessage[] parseSmsMessage(Bundle bundle) {
Object[] objs = (Object[]) bundle.get("pdus"); // pdus : SMS 국제 표준 프로토콜 SMPP 안에 데이터가 들어가있는 이름. 안드로이드 내부 모듈이 처리 후 데이터를 던져줌.
SmsMessage[] messages = new SmsMessage[objs.length];

for (int i=0; i<objs.length; i++) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String format = bundle.getString("format"); // 인텐트에 저장된 정보 format
messages[i] = SmsMessage.createFromPdu((byte[])objs[i], format);
} else {
messages[i] = SmsMessage.createFromPdu((byte[]) objs[i]); // 마쉬멜로우 이전버전
}
}


// 번들객체 안의 데이터를 이용해 smsMessages라는 객체로 변환 후 리턴.
// 이 안에는 데이터가 있음.
return messages;
}



 메시지를 보내보면, 다음과 같이 브로드캐스트 메시지에서 받은 인텐트의 번들객체로부터 메시지 내용에 관한 정보를 가져올 수 있다.


이러한 문자가 왔습니다! 라는 화면을 띄워주고 싶을 때, 화면(액티비티)을 만들어서 그 내용을 띄워주는게 가능하다.

브로드캐스트 리시버도 애플리케이션의 구성요소 이므로, 인텐트 객체를 만들어 startActivity를 호출해 다른 액티비티에게 뿌려줄 수 있다.



2-3. 브로드캐스트 리시버로 받은 데이터를 화면에 출력해보기

위에서는 로그캣 창에서 볼 수 있었는데, 이번엔 별도의 액티비티를 만들어서 그 뷰에 출력해보는 예제이다.


먼저, SmsActivity를 생성하고 xml에 데이터를 출력할 뷰를 선언한다.

<EditText
android:id="@+id/sendNum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:ems="10"
android:hint="발신번호" />

<EditText
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="351dp"
android:layout_alignParentStart="true"
android:layout_below="@+id/sendNum"
android:ems="10"
android:hint="내용"/>

<EditText
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/btnOk"
android:layout_alignParentStart="true"
android:ems="10"
android:hint="수신시각" />

<Button
android:id="@+id/btnOk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="17dp"
android:text="확인" />


findByViewId로 뷰 등록하는 과정은 생략.


곧바로, 브로드캐스트 리시버 클래스의 onReceive() 메소드를 재정의한다. 


@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive 호출됨.");

Bundle bundle = intent.getExtras();
SmsMessage[] messages = parseSmsMessage(bundle);

if (messages.length > 0) {
String sender = messages[0].getOriginatingAddress(); // 발신번호
Log.d(TAG, "sender : " + sender);

String contents = messages[0].getMessageBody().toString();
Log.d(TAG, "contents : " + contents);

Date receivedDate = new Date(messages[0].getTimestampMillis()); // 발신 시각
Log.d(TAG, "received date : " + receivedDate);

sendToActivity(context, sender, contents, receivedDate);
}
}



private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");

Date 객체는 다음과 같이 String으로 변환할 수 있다. (맨 위에 class 변수로 선언하면 된다)


그리고 브로드캐스트 리시버는 화면이 없어 Task에 등록될 수 없기 때문에, 다음과 같은 3개의 플래그를 설정해주어야 한다.

private void sendToActivity(Context context, String sender, String contents, Date receivedDate) {
Intent intent = new Intent(context, SmsActivity.class);

//화면이 없는곳에서 화면을 띄울 때 사용되는 플래그
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP
);
intent.putExtra("sender", sender);
intent.putExtra("contents", contents);
intent.putExtra("receivedDate", format.format(receivedDate)); // Date 클래스를 위에서 정의한 문자열로 파싱

context.startActivity(intent);
}


브로드캐스트 리시버는 getApplicationContext() 메소드를 호출할 수 없어서 onReceive() 메소드가 호출될 때 넘겨주는 context 객체를 이용해 intent를 제어할 수 있다.

전달해주고싶은 값을 intent의 번들에 put 한 후 startActivity()를 호출하면 원하는 액티비티에 전달된다.


다음으로 브로드캐스트 리시버가 받은 데이터들을 출력할 액티비티 클래스.

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sms);

editText = findViewById(R.id.sendNum);
editText2 = findViewById(R.id.content);
editText3 = findViewById(R.id.time);

findViewById(R.id.btnOk).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});

Intent passedIntent = getIntent();
processCommand(passedIntent);
}


onCreate() 메소드가 실행되면(처음 화면이 만들어지는 경우) getIntent() 메소드를 통해 리시버로부터 전달받은 인텐트를 참조할 수 있지만, 이미 화면이 만들어져 있는 경우는 onCreate() 콜백이 발생하지 않으므로, onNewIntent() 메소드를 재정의해주어야한다.


// single_top 이미 화면이 만들어져 있는 경우, override를 통해 onNewIntent를 재정의.
@Override
protected void onNewIntent(Intent intent) {
processCommand(intent);

super.onNewIntent(intent);
}

private void processCommand(Intent intent){
if (intent != null){
String sender = intent.getStringExtra("sender");
String contents = intent.getStringExtra("contents");
String receivedDate = intent.getStringExtra("receivedDate");

editText.setText(sender);
editText2.setText(contents);
editText3.setText(receivedDate);
}
}



문자 메시지를 보내면 뜨는 창. 어플리케이션이 실행되어있지 않아도, 문자가 오는 순간 방금 구현한 액티비티가 뜬다.



 이 방법으로 문자메시지 수신창을 내 맘대로 만들 수 있다고 한다. 토*, 카카** 와 같은 경우도 브로드캐스트 리시버를 아주 많이 사용하는것 같다. 토*는 계좌만 복사해도 송금하기 창이 뜨고, 카카**는 어느 순간 내 문자들을 모아주기 시작했다. 


 공부를 하면서 생각해본 것은, 서비스와 마찬가지로 브로드캐스트 리시버도 항상 백그라운드에서 돌아가고있어서 원하는 브로드캐스트를 받을 수 있는 것이 아닐까?(계속 귀를 열어두고 있는데 힘이 들지 않나..)

 아니면 그냥 '나 이거 보고싶어요' 라고 명찰을 달아놓을 뿐 어플이 실행중인 것은 아닌가?

 좀 더 자세히 보아야 할 부분인 것 같다.



출처 : 부스트코스 Android developer 강좌

'Android > Basic' 카테고리의 다른 글

[Android] Time to String 및 시간순으로 정렬하기  (0) 2018.06.22
[Android] Action Bar  (0) 2018.05.27
[Android] startActivityForResult() 활용 예제  (0) 2018.05.18
[Android] Permission  (0) 2018.05.17
[Android] Activity 생명주기  (0) 2018.05.15