https://developer.android.com/guide/components/broadcasts?hl=ko
안드로이드에서 브로드캐스팅이란 메세지를 여러 객체에 전달하는 것을 의미한다
여러 앱 구성 요소에 메세지를 전달할 때 브로드캐스팅을 사용한다
문자를 받았을 때 이 문자를 SMS 수신 앱에 알려줘야 한다면 브로드캐스팅으로 전달하면 된다
이런 메세지 전달 방식은 단말 전체에 적용될 수 잇다
이런 메세지 전달 방식을 글로벌 이벤트라고 부른다
'전화가 왔습니다.', '문자 메세지가 도착했습니다.'와 같은 사용자 알림이 글로벌 이벤트이다
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.a38_receiver">
<!-- RECEIVE_SMS 권한 추가(위험 권한) -->
<uses-permission android:name="android.permission.RECEIVE_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/Theme.A38_Receiver">
<!-- <receiver> 태그 추가하고 name 속성에 수신자 이름 지정 -->
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<!-- 액션 정보 -->
<intent-filter>
<!-- 단말에서 sms를 수신했을 때 이 액션 정보가 들어간 인텐트를 전달 -->
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
build.gradle(Module)에 implementation 'com.yanzhenjie:permission:2.0.3'
권한에 관련된 api
dependencies {
...
implementation 'com.yanzhenjie:permission:2.0.3'
}
MyReceiver.java
package com.example.a38_receiver;
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 java.util.Date;
import java.util.Objects;
public class MyReceiver extends BroadcastReceiver {
public static final String TAG = "MyReceiver";
// sms를 받으면 onReceive 메소드 호출
// 파라미터로 전달되는 Intent 객체 안에 sms 데이터가 있음
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive() 메소드 호출됨");
// 인텐트에서 번들 객체 가져오기
Bundle bundle = intent.getExtras();
SmsMessage[] messages = parseSmsMessages(bundle);
if (messages != null && messages.length > 0) {
String sender = messages[0].getOriginatingAddress();
Log.i(TAG, "SMS sender : " + sender);
String contents = messages[0].getMessageBody();
Log.i(TAG, "SMS contents : " + contents);
Date receivedDate = new Date(messages[0].getTimestampMillis());
Log.i(TAG, "SMS received Date : " + receivedDate.toString());
}
}
// SmsMessage 자료형으로 된 배열 겍체를 리턴
// SmsMessage 객체에는 sms 데이터를 확인할 수 있는 메소드들이 있음
private SmsMessage[] parseSmsMessages(Bundle bundle) {
// Bundle 객체에 들어가 있는 부가 데이터 중에서 pdus 가져오기
Object[] objs = (Object[]) bundle.get("pdus");
SmsMessage[] messages = new SmsMessage[objs.length];
int smsCount = objs.length;
// 단말 OS 버전에 따라 다른 방식으로 메소드 호출하기
for (int i = 0; i < smsCount; i++) {
// 안드로이드 os가 계속 업데이트되므로, 단말의 os 버전에 따라 코드가 약간씩 달라져야할 때가 있다
// Build.VERSION_CODES에는 안드로이드 os 버전별로 상수가 정의되어 있다
// 'os가 마시멜로(Build.VERSION_CODES.M) 버전과 같거나 그 이후 버전일때'라는 의미이다
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String format = bundle.getString("format");
// 인텐트 객체 안에 부가 데이터로 들어있는 sms 데이터를 확인하려면
// SmsMessage 클래스의 createFromPdu 메소드를 사용하여 SmsMessage 객체로 변환하면
// sms 뎅티러를 확인할 수 있다
messages[i] = SmsMessage.createFromPdu((byte[]) objs[i], format);
} else {
messages[i] = SmsMessage.createFromPdu((byte[]) objs[i]);
}
}
return messages;
}
}
MainActivity
package com.example.a38_receiver;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;
// 샘플 참고
// https://github.com/yanzhenjie/AndPermission/blob/master/sample/src/main/java/com/yanzhenjie/permission/sample/app/MainActivity.java
import com.yanzhenjie.permission.Action;
import com.yanzhenjie.permission.AndPermission;
import com.yanzhenjie.permission.runtime.Permission;
import java.util.List;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 모든 위험 권한을 자동 보여하는 메소드
AndPermission.with(this).runtime().permission(Permission.RECEIVE_SMS).onGranted(new Action<List<String>>() {
@Override
public void onAction(List<String> permissions) {
showToast("허용된 권한 개수 : " + permissions.size());
}
}).onDenied(new Action<List<String>>() {
@Override
public void onAction(List<String> permissions) {
showToast("거부된 권한 개수 : " + permissions.size());
}
}).start();
}
public void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}
에뮬레이터 실행 후 [...] 클릭 - Phone 탭에서 가상으로 문자를 보낼 수 있다
발신자와 sms 내용, 수신 시각 등을 로그 창이 아니라 사용자가 보는 화면에 나타내려면, 브로드캐스트 수신자는 화면이 없으므로 보여주려는 화면은 액티비티로 만든 후 그 화면을 띄워야한다
브로드캐스트 수신자에서 인텐트 객체를 만들고 startActivity 메소드를 사용해 액티비티로 인텐트 객체를 전달해야 한다
프로젝트 메뉴의 app을 우클릭하여 empty activity 만들기로 SmsActivity를 만든다
package com.example.a38_receiver;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class SmsActivity extends AppCompatActivity {
EditText editText1;
EditText editText2;
EditText editText3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sms);
editText1 = findViewById(R.id.editText1);
editText2 = findViewById(R.id.editText2);
editText3 = findViewById(R.id.editText3);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
// 브로드캐스트 수신자로부터 인텐트를 전달받을 것이므로 onCreate 메소드 안에서 getIntent 메소드 호출
Intent passedIntent = getIntent();
processIntent(passedIntent);
}
// 이 액티비티가 이미 만들어져있는 상태에서 전달받은 인텐트 처리하기 위해 onNewIntent 메소드 재정의
@Override
protected void onNewIntent(Intent intent) {
processIntent(intent);
super.onNewIntent(intent);
}
private void processIntent(Intent intent) {
if (intent != null) {
String sender = intent.getStringExtra("sender");
String contents = intent.getStringExtra("contents");
String receivedDate = intent.getStringExtra("receivedDate");
editText1.setText(sender);
editText2.setText(contents);
editText3.setText(receivedDate);
}
}
}
activity_sms.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".SmsActivity">
<EditText
android:id="@+id/editText1"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:ems="10"
android:gravity="center"
android:hint="발신번호"
android:inputType="textPersonName" />
<EditText
android:id="@+id/editText2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_weight="4"
android:ems="10"
android:gravity="start"
android:hint="내용"
android:inputType="textPersonName" />
<EditText
android:id="@+id/editText3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:ems="10"
android:gravity="center"
android:hint="수신 시각"
android:inputType="textPersonName" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:text="확인" />
</LinearLayout>
MyReceiver.java에 코드 추가
package com.example.a38_receiver;
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 java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
public class MyReceiver extends BroadcastReceiver {
public static final String TAG = "MyReceiver";
public SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// sms를 받으면 onReceive 메소드 호출
// 파라미터로 전달되는 Intent 객체 안에 sms 데이터가 있음
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("asd : " + context);
Log.i(TAG, "onReceive() 메소드 호출됨");
// 인텐트에서 번들 객체 가져오기
Bundle bundle = intent.getExtras();
SmsMessage[] messages = parseSmsMessages(bundle);
if (messages != null && messages.length > 0) {
String sender = messages[0].getOriginatingAddress();
Log.i(TAG, "SMS sender : " + sender);
String contents = messages[0].getMessageBody();
Log.i(TAG, "SMS contents : " + contents);
Date receivedDate = new Date(messages[0].getTimestampMillis());
Log.i(TAG, "SMS received Date : " + receivedDate.toString());
sendToActivity(context, sender, contents, receivedDate);
}
}
// SmsMessage 자료형으로 된 배열 겍체를 리턴
// SmsMessage 객체에는 sms 데이터를 확인할 수 있는 메소드들이 있음
private SmsMessage[] parseSmsMessages(Bundle bundle) {
// Bundle 객체에 들어가 있는 부가 데이터 중에서 pdus 가져오기
Object[] objs = (Object[]) bundle.get("pdus");
SmsMessage[] messages = new SmsMessage[objs.length];
int smsCount = objs.length;
// 단말 OS 버전에 따라 다른 방식으로 메소드 호출하기
for (int i = 0; i < smsCount; i++) {
// 안드로이드 os가 계속 업데이트되므로, 단말의 os 버전에 따라 코드가 약간씩 달라져야할 때가 있다
// Build.VERSION_CODES에는 안드로이드 os 버전별로 상수가 정의되어 있다
// 'os가 마시멜로(Build.VERSION_CODES.M) 버전과 같거나 그 이후 버전일때'라는 의미이다
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String format = bundle.getString("format");
// 인텐트 객체 안에 부가 데이터로 들어있는 sms 데이터를 확인하려면
// SmsMessage 클래스의 createFromPdu 메소드를 사용하여 SmsMessage 객체로 변환하면
// sms 뎅티러를 확인할 수 있다
messages[i] = SmsMessage.createFromPdu((byte[]) objs[i], format);
} else {
messages[i] = SmsMessage.createFromPdu((byte[]) objs[i]);
}
}
return messages;
}
// SmsActivity로 인텐트 보내는 메소드
private void sendToActivity(Context context, String sender, String contents, Date receivedDate) {
Intent myIntent = new Intent(context, SmsActivity.class);
// 브로드캐스트 수신자는 화면이 없으므로 인텐트의 플래그로 FLAG_ACTIVITY_TASK가 있어야한다
// 이미 메모리에 만든 SmsActivity가 있을 때 액티비티를 중복 생성하지 않도록 FLAG_ACTIVITY_SINGLE_TOP이 있어야한다
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
myIntent.putExtra("sender", sender);
myIntent.putExtra("contents", contents);
myIntent.putExtra("receivedDate", format.format(receivedDate));
context.startActivity(myIntent);
}
}
매니페스트 파일 안에 <receiver> 태그로 추가되어있지만, 매니페스트에 등록하지 않고 소스 파일에서 registerReceiver 메소드를 사용하면, 화면이 사용자에게 보일 때만 브로드캐스트 수신자에서 메세지를 받도록 할 수 있다
다른 앱에 메세지를 보내려면 sendBroadcast 메소드를 이용하며 된다
물론 다른 앱에 브로드캐스트 수신자를 만들어야한다
sms 수신 시 문자는 단말의 기본 메세지 앱이 먼저 받아 처리한 후 다른 앱으로 넘겨주게 된다
직접 만든 앱을 사용자가 단말의 기본 메시지 앱으로 지정하면 단말의 기본 sms 앱으로 직접 만든 sms 수신 앱을 띄울 수 있다
브로드캐스트 수신자를 포함하고 있는 앱의 메인 액티비타 적어도 한 번은 실행되어야 브로드캐스트 수신자가 메세지를 받을 수 있다
'안드로이드' 카테고리의 다른 글
외부 라이브러리를 이용한 위험 권한 자동 부여 (0) | 2021.10.26 |
---|---|
위험 권한 부여 (0) | 2021.10.22 |
서비스 (0) | 2021.10.22 |
바로가기 메뉴, Navigation Drawer (0) | 2021.10.21 |
ViewPager2로 여러 프래그먼트 간 슬라이드 (0) | 2021.10.21 |