브로드캐스팅(Broadcasting) : 메시지를 여러 객체에 전달
글로벌 이벤트(Global Event) : 메시지를 브로드캐스팅으로 전달하는 방식 (ex. 전화, 문자에 대한 사용자 알림 메시지)
브로드캐스팅 메시지를 받고 싶으면 브로드캐스트 수신자를 만들어 앱에 등록한다. (registerReceiver(), onReceive())
다른 앱의 메시지를 전달받을 수 있음.
이 중 원하는 메시지만 받으려면 인텐트 필터를 사용하여 시스템에 등록하면 된다.
브로드캐스트 수신자의 특징 : 앱을 실행하지 않은 상태에서도 인텐트 안에 들어있는 메시지를 받아볼 수 있다.
브로드캐스트 수신자를 포함하고 있는 메인 액티비티가 적어도 한번 실행되어야 브로드캐스트 수신자가 메시지를 받을 수 있다.
브로드캐스트를 등록하는 방법은 2가지가 있다.
- 매니패스트 파일에 등록 : 언제든 브로드캐스트 수신자에서 메시지를 받음.
- 소스 파일에서 등록 : 화면이 사용자에게 보일 때만 브로드캐스트 수신자에서 메시지를 받도록 만들 수 있음.
sendBroadcast() : 다른 앱에 메시지를 보내고 싶을 경우 이 메서드를 사용한다. (이번 예제에서는 안씀)
<SMS 문자 받은 뒤 발신번호, 문자내용, 받은시각 액티비티에 띄우기>
1. 새 프로젝트를 만들고 브로드캐스트 수신자를 생성한다.
브로드캐스트 수신자 이름은 SMSReceiver로 설정함.
2. 매니패스트 파일을 수정한다.
AndroidManifest.xml에 들어간다.
앱에서 SMS를 수신하기 위해 권한을 추가해야한다. 아래 코드를 입력한다.
<uses-permission android:name="android.permission.RECEIVE_SMS" />
SMS 메시지가 들어간 인텐트를 구분하기 위한 액션 정보를 추가한다. android.provider.Telephony.SMS_RECEIVED
<reciever> 태그 안에 아래 코드를 입력한다.
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
3. SMSReceiver.java 작성 (브로드캐스트 수신자)
package com.example.chaoter6_2;
public class SMSReceiver extends BroadcastReceiver {
public static final String TAG = "SMSReciever";
public SimpleDateFormat format = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
// receivedDate 양식
@Override
public void onReceive(Context context, Intent intent) {
// 파라미터로 들어오는 인텐트 객체 안에 SMS 데이터가 있음.
// TODO: This method is called when the BroadcastReceiver is receiving
Log.d(TAG,"OnReceive() 메서드 호출됨.");
Bundle bundle = intent.getExtras(); // 번들 객체 안에 부가 데이터가 들어감
SmsMessage[] messages = parseSmsMessage(bundle);
// sms 메시지를 뽑아내기 위해 만든 메서드 parsSmsMessage
// 입력이 들어올 경우
if(messages!=null && messages.length>0){
// 발신자 번호 가져오기
String sender = messages[0].getOriginatingAddress();
Log.i(TAG, "SMS sender : " +sender.toString());
// 문자 내용 가져오기
String contents = messages[0].getMessageBody();
Log.i(TAG,"SMS contents : "+contents.toString());
// SMS 받은 시각 가져오기
Date receivedDate = new Date(messages[0].getTimestampMillis());
Log.i(TAG,"SMS received date : "+receivedDate.toString());
sendToActivity(context, sender, contents, receivedDate);
}
//throw new UnsupportedOperationException("Not yet implemented");
}
private SmsMessage[] parseSmsMessage(Bundle bundle){
Object[] objs = (Object[]) bundle.get("pdus"); // 부가 데이터 중 pdus 가져오기
SmsMessage[] messages = new SmsMessage[objs.length];
int smsCount = objs.length;
for(int i=0;i<smsCount;i++){
// 단말 OS 버전에 따라 다른 방식으로 메서드 호출.
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
// OS가 마시멜로(M) 버전과 같거나 이후 버전일 경우
String format = bundle.getString("format");
messages[i]=SmsMessage.createFromPdu((byte[]) objs[i], format);
}
else
messages[i]=SmsMessage.createFromPdu((byte[]) objs[i]);
}
return messages;
}
private void sendToActivity(Context context, String sender, String contents, Date receivedDate){
Intent myIntent = new Intent(context, SMSActicity.class); //SMSActivity로 인텐트 보내기
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK| // 브캐 수신자는 화면이 없으므로 화면을 생성하기 위해 추가
Intent.FLAG_ACTIVITY_SINGLE_TOP| // 이미 메모리에 만든 SMSActivity가 있을 때 액티비티 중복생성하지 않기
Intent.FLAG_ACTIVITY_CLEAR_TOP);
myIntent.putExtra("sender",sender);
myIntent.putExtra("contents",contents);
myIntent.putExtra("receivedDate",format.format(receivedDate));
context.startActivity(myIntent);
}
}
- SimpleDateFormat
public class SMSReceiver extends BroadcastReceiver {
public static final String TAG = "SMSReciever";
public SimpleDateFormat format = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
// receivedDate 양식
...
SimpleDateFormat은 날짜와 시간을 원하는 형태의 문자열로 만들 때 사용한다.
- onRecieve()
@Override
public void onReceive(Context context, Intent intent) {
// 파라미터로 들어오는 인텐트 객체 안에 SMS 데이터가 있음.
// TODO: This method is called when the BroadcastReceiver is receiving
Log.d(TAG,"OnReceive() 메서드 호출됨.");
Bundle bundle = intent.getExtras(); // 번들 객체 안에 부가 데이터가 들어감
SmsMessage[] messages = parseSmsMessage(bundle); // sms 메시지를 뽑아내기 위해 만든 메서드
// 입력이 들어올 경우
if(messages!=null && messages.length>0){
String sender = messages[0].getOriginatingAddress(); // 발신자 번호 가져오기
Log.i(TAG, "SMS sender : " +sender.toString());
String contents = messages[0].getMessageBody(); // 문자 내용 가져오기
Log.i(TAG,"SMS contents : "+contents.toString());
Date receivedDate = new Date(messages[0].getTimestampMillis()); // SMS 받은 시각 가져오기
Log.i(TAG,"SMS received date : "+receivedDate.toString());
sendToActivity(context, sender, contents, receivedDate);
}
//throw new UnsupportedOperationException("Not yet implemented");
}
SMS를 뽑아내기 위한 메서드 parseSmsMessage가 초기에 실행되어 messages에 문자정보가 들어간다.
문자 정보는 messages[0]에서 접근가능한가봄.
sender(수신번호) ← getOriginatingAddress()
contents(문자내용) ← getMessageBody()
receivedDate(받은시각) ← getTimestampMillis()
이 중에서 receivedDate만 String 형식이 아닌 Date 형식인 것에 주의. (Date는 자동으로 import 되지 않아서 빨간색으로 표시된다. Alt+Enter를 누르고 java.util 패키지 안에 있는 것을 선택하자)
메서드 sendToActivity가 실행되어 위의 문자 정보가 파라미터로 전달되어, 액티비티로 전달됨.
마지막 //주석 부분은 자동으로 생성된 코드인데 에러가 발생해서 지움. (메시지 알림이 뜨면 앱이 강제종료됨)
https://stackoverflow.com/questions/14493907/android-unable-to-start-receiver-not-yet-implemented
- parseSmsMessage() : SMS를 뽑아내기 위한 메서드
private SmsMessage[] parseSmsMessage(Bundle bundle){
Object[] objs = (Object[]) bundle.get("pdus"); // 부가 데이터 중 pdus 가져오기
SmsMessage[] messages = new SmsMessage[objs.length];
int smsCount = objs.length;
for(int i=0;i<smsCount;i++){
// 단말 OS 버전에 따라 다른 방식으로 메서드 호출.
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
// OS가 마시멜로(M) 버전과 같거나 이후 버전일 경우
String format = bundle.getString("format");
messages[i]=SmsMessage.createFromPdu((byte[]) objs[i], format);
}
else
messages[i]=SmsMessage.createFromPdu((byte[]) objs[i]);
}
return messages;
}
- createFromPdu()
인텐트 객체 안에 부가 데이터로 들어있는 SMS 데이터를 확인하려면 SmsMessage 클래스의 createFromPdu
메서드를 사용하여 SmsMessage 객체로 변환하면 된다.
- Build.VERSION.SDK_INT
단말의 OS 버전을 확인할 때 사용함
- Build.VERSION.CODE
안드로이드 OS 버전별로 상수가 정의되어 있다.
- sendToActivity() : 인텐트를 사용하여 액티비티로 문자 정보를 보내는 메서드
private void sendToActivity(Context context, String sender, String contents, Date receivedDate){
Intent myIntent = new Intent(context, SMSActicity.class); //SMSActivity로 인텐트 보내기
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK| // 브캐 수신자는 화면이 없으므로 화면을 생성하기 위해 추가
Intent.FLAG_ACTIVITY_SINGLE_TOP| // 이미 메모리에 만든 SMSActivity가 있을 때 액티비티 중복생성하지 않기
Intent.FLAG_ACTIVITY_CLEAR_TOP);
// Task 내에 해당 속성이 적용된 activity부터 top activity까지 모두 제거한뒤
// 해당 activity를 활성화 하여 top이 되도록 한다.
myIntent.putExtra("sender",sender);
myIntent.putExtra("contents",contents);
myIntent.putExtra("receivedDate",format.format(receivedDate));
context.startActivity(myIntent);
}
아직 context의 개념을 잘 모르겠다.
4. build.gradle
변경된 부분을 따라서 작성한다.
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
dependencies {
constraints {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") {
because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib")
}
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") {
because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib")
}
}
implementation ("com.github.pedroSG94:AutoPermissions:1.0.3")
implementation(libs.activity)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
implementation("androidx.core:core:1.13.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.9.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
}
이 부분은 특히 아주 그지 같았던 부분이다. 설명은 아래 글을 보자
2025.01.05 - [TIL/안드로이드 스튜디오] - duplicate class 에러 (JDK7, JDK8 충돌)
5. gradle.properties
android.enableJetifier=true
6. settings.gradle
url을 추가.
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {url = uri ("https://www.jitpack.io")}
}
}
7. MainActivity 작성
어.. 지금 다시 보니까 내가 책이랑 다르게 작성을 했다. 근데 책에도 다음 목차에서 다루는 내용이라서 자세한 설명은 패스했다. 일단 이 부분은 그냥 대충 훑어보고 넘어가자.
package com.example.chaoter6_2;
public class MainActivity extends AppCompatActivity implements AutoPermissionsListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AutoPermissions.Companion.loadAllPermissions(this, 101);
}
@Override
public void onGranted(int requestCode, @NonNull String[] permissions) {
Toast.makeText(this, "permissions granted : " + permissions.length, Toast.LENGTH_LONG).show();
}
@Override
public void onDenied(int requestCode, @NonNull String[] permissions) {
Toast.makeText(this, "permissions denied : " + permissions.length, Toast.LENGTH_LONG).show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
AutoPermissions.Companion.parsePermissions(this, requestCode, permissions, this);
Toast.makeText(this,"requestCode : "+requestCode+", permissions : "+permissions+", grantResults : "+grantResults, Toast.LENGTH_LONG).show();
}
}
8. 새로운 액티비티를 생성한다. (SMSActivity)
브로드캐스트 수신자에서 인텐트 객체를 만들고 startActivity 메서드를 사용해 액티비티 쪽으로 인텐트 객체를 전달한다. 여기서 전달받을 액티비티를 만든다.
- activity_sms.xml
여기서 editTextText2는 입력이 길어질 경우 자동으로 줄바꿈이 될 수 있도록 아래 코드를 추가한다.
android:inputType="textMultiLine"
android:scrollHorizontally="false"
- SMSActivity.java
package com.example.chaoter6_2;
public class SMSActivity extends AppCompatActivity {
EditText editText_num, editText_content, editText_time;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sms);
editText_num = findViewById(R.id.editTextText);
editText_content = findViewById(R.id.editTextText2);
editText_time = findViewById(R.id.editTextText3);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
// 전달받은 인텐트를 처리하도록 processIntent 메서드 호출하기
Intent passedIntent = getIntent();
processIntent(passedIntent);
}
@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");
editText_num.setText(sender);
editText_content.setText(contents);
editText_time.setText(receivedDate);
}
}
}
- onClick() : 앱 종료
- onNewIntent() : 재정의하여 이 액티비티가 이미 만들어져 있는 상태에서 전달받은 인텐트도 처리함
- processIntent() : 인텐트 객체 안에 들어있는 부가 데이터를 꺼내서 입력상자에 설정
9. 가상 SMS 전송
AVD 화면에서 상단탭 중 가장 오른쪽에 위치한 점 3개를 누른다.
10. 실행결과
입력을 많이해도 한글 최대 67자까지 액티비티에 전달되었다.
왜 그런가 알아보니 다음과 같았다.
11. 브로드캐스트 수신자 동작 방식 정리
1. SMS 문자를 받았을 때, 텔레포니(Telephony) 모듈이 처리하도록 한다.
2. 인텐트를 받았을 때 onReceive 메서드가 자동 호출
3. Receiver 객체에서 데이터 확인 후 Activity로 인텐트 전달
브로드캐스트 사용 시 주의할 점 : 앱 A가 실행되어 있지 않아도 앱 A가 원하는 브로드캐스트 메시지가 도착하면
다른 앱 B를 실행하고 있는 도중에도 앱 A가 실행될 수 있음.
즉 동일한 SMS 수신 앱을 여러 개 수정하여 만들어 설치하면 오류가 발생했을 때
어디서 발생했는지 찾기가 힘들다.
여러 버전의 앱이 깔려있을 때, 구 개발 버전의 앱은 삭제하는 것이 좋다.
'TIL > 안드로이드 스튜디오' 카테고리의 다른 글
duplicate class 에러 (JDK7, JDK8 충돌) (0) | 2025.01.05 |
---|---|
6장 - 서비스 (0) | 2025.01.02 |
도전!10 - 기본 앱 화면 구성 (0) | 2025.01.01 |
도전!9 - 고객 정보 입력 화면의 구성 (0) | 2024.12.24 |
5장 바로가기 메뉴(NavigationDrawer) 만들기 (0) | 2024.12.23 |