본문 바로가기

안드로이드

브로드캐스트

반응형
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 수신 앱을 띄울 수 있다

 

브로드캐스트 수신자를 포함하고 있는 앱의 메인 액티비타 적어도 한 번은 실행되어야 브로드캐스트 수신자가 메세지를 받을 수 있다

반응형