티스토리 뷰

안드로이드에서 브로드캐스팅이란 메시지를 여러 객체에 전달하는 것을 말하며, 글로벌 이벤트라고 부른다.

예시로, '전화가 왔습니다.' 혹은 '문자 메시지가 도착했습니다.' 를 들 수 있다. 

 

우리가 제작한 어플이 다른 어플에서 온 브로드 캐스트 메시지를 받고 싶다면, 브로드캐스트 수신자를 만들어 앱에 등록해야한다. 

 

브로드캐스트 수신자 또한 서비스와 마찬가지로 앱 구성요소이기 때문에 매니페스트 파일에 등록해야 시스템이 알 수 있고 화면도 없게 된다. 그러나 브로드캐스트 수신자는 매니페스트 등록 방식이 아닌 소스 코드에서 registerReceiver() 메서드를 이용하여 등록할 수도 있다. 

 

바로 예제를 한번 구성해보자.


 

이후 AndroidManifest.xml 파일에 receiver 태그가 자동으로 생성되는데, 브로드 캐스트 수신자가 어떤 Intent 객체를 받는 지 명시해주기 위하여 아래와 같이 intent-filter를 추가한다.

 

◎AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.samplereceiver">

    <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.SampleReceiver">
        <receiver
                android:name=".SmsReceiver"
                android:enabled="true"
                android:exported="true">
            <intent-filter>
                <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>

 

<intent-filter> 에 대한 자세한 설명은 아래에서 확인 가능하다.

 - intent-filter

 - action

 - action android name constants 

 

다음으로 sms를 수신하고, 해당 내용을 화면에 뿌려줄 sms 액티비티를 아래와 같이 생성해주자. 

 

◎activity_sms.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".SmsActivity">

    <EditText
        android:id="@+id/editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:ems="10"
        android:hint="발신번호"
        android:inputType="textPersonName"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/editText2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        android:ems="10"
        android:gravity="top|left"
        android:hint="내용"
        android:inputType="textPersonName"
        app:layout_constraintBottom_toTopOf="@+id/editText3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editText" />

    <EditText
        android:id="@+id/editText3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:ems="10"
        android:hint="수신시각"
        android:inputType="textPersonName"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="40dp"
        android:text="확인"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

◎SmsActivity.java

package com.example.samplereceiver;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import androidx.appcompat.app.AppCompatActivity;

public class SmsActivity extends AppCompatActivity {
    EditText editText;
    EditText editText2;
    EditText editText3;

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

        editText = findViewById(R.id.editText);
        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 view) {
                finish();
            }
        });

        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.setText(sender);
            editText2.setText(contents);
            editText3.setText(receivedDate);
        }
    }

}

sms activity는 어려울 거 없이, 새 intent객체가 해당 액티비티에 전달됐을 때 이를 화면에 뿌려주는 역할이다.

 

이제 SmsReceiver 클래스에서 브로드캐스트 메시지를 수신했을 때 동작할 리스너를 작성해주자.

 

◎SmsReceiver.java

package com.example.samplereceiver;

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;

public class SmsReceiver extends BroadcastReceiver {
    private static final String TAG = "SmsReceiver";

    public SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

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

        Bundle bundle = intent.getExtras();
        SmsMessage[] messages = parseSmsMessage(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);
        }
    }

    private SmsMessage[] parseSmsMessage(Bundle bundle) {
        Object[] objs = (Object[]) bundle.get("pdus");

        SmsMessage[] messages = new SmsMessage[objs.length];
        int smsCount = objs.length;
        for (int i = 0; i < smsCount; i++) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.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, SmsActivity.class);
        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);
    }
}

 parseSmsMessage() 메서드를 확인해보면, 브로드캐스트로 전달된 Intent 객체에서 Bundle을 꺼내어 그 안에 있는 부가 데이터 중 "pdus" 만 따로 꺼낸다. 

 

PDU의 복수형인데, PDU는 Protocol Data Unit의 약자이며 지금은 SMS에 활용되는 데이터 그램 정도로 생각하면 될 거 같다.

 

또한

Build.VERSION.SDK_INT >= Build.VERSION_CODES.M

와 같은 구문은 안드로이드 OS버전에 따라 deprecated된 메서드가 있기 때문에 버전 정보를 위와 같이 확인하여 코드를 분기해준 것이다. 

VERSION_CODE를 확인하여면 버전 코드 확인 를 클릭하자.

 

이제, 제작한 어플이 문자 메시지를 수신하기 위해 필요한 권한을 설정해줘야 한다. 

AndroidManifest.xml에 아래와 같이 권한을 추가해준다.

 

◎AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.samplereceiver">

    <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.SampleReceiver">
        <receiver
                android:name=".SmsReceiver"
                android:enabled="true"
                android:exported="true">
            <intent-filter>
                <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>

        <activity android:name=".SmsActivity"/>
    </application>

</manifest>

해당 권한 상수에 대한 정보는 권한을 참조하자.

 

마지막으로 MainActivity가 실행되었을 때, 권한을 허용할 지 여부를 물어보기 위해 외부 라이브러리를 추가하고 아래와 같이 MainActivity를 작성해준다.

 

◎build.gradle

implementation 'com.yanzhenjie:permission:2.0.3'

해당 라이브러리에 대한 자세한 사용법은 여기에서 확인할 수 있다.

 

◎MainActivity.java

package com.example.samplereceiver;

import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
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_LONG).show();
    }

}

이제 실행해보자.

 

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

[Android] 선택 위젯 1: 새로운 뷰 객체 생성  (0) 2022.04.02
[Android] 위험 권한  (0) 2022.03.31
[Android] Service 1: Service 이해하기  (0) 2022.03.29
[Android] ViewModel  (0) 2022.03.27
[Android] Navigation Drawer  (0) 2022.03.27
Comments