본문 바로가기
Android/Concepts

Broadcast Receiver를 이용한 SMS 수신

by JuHy_ 2020. 4. 4.

Broadcast Receiver(방송수신자)란?

안드로이드 시스템에서는 전화가 오거나 문자메시지가 오는 등 특정 상황에서 방송을 내보낸다.

이런 시스템에서 방송을 해주는 이벤트들을 Global Event라고 하며,

이를 앱에서 받아 처리하려면 Broadcast Receiver에 등록해주어야 한다.

 

SMS 수신 과정

Broadcast Receiver에 등록된 종류의 방송이 오면 이를 수신해 intent를 통해 전달받게 된다.

 

또한 시스템의 방송을 수신하는 것 뿐만 아니라 앱에서 방송을 보낼 수도 있다.

 

기본적인 사용법

생성은 마찬가지로 Project Explorer에서 app 우클릭 후 New - Other - Broadcast Receiver를 클릭하면 된다.

 

클래스 이름을 설정해주고 Finish를 눌러주면 자동으로 클래스 파일이 생성되고 Manifest에 등록된다.

 

package com.juhy.myapplication;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class SMSReceiver extends BroadcastReceiver {

    private static final String TAG = "SMSReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive() called");
    }

}

만들어준 클래스에서는 일단 log만 찍도록 구현했다.

그리고 그동안 activity나 service는 MainActivity에서 intent를 통해 실행시켜 주었지만,

Broadcast Receiver는 manifest에 등록해주기만 하면 자동으로 수신할 수 있다.

 

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

    <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/AppTheme">

        <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>

그 다음으로는 Manifest에서 여러 가지 설정을 해주어야 한다.

먼저 생성된 receiver 태그 안에 intent-filter 태그를 추가하여 우리가 받고싶은 방송의 종류를 지정해야 한다.

시스템에서 방송하는 내용이 많은데 이를 모두 수신할 필요는 없기 때문이다.

이번에는 문자가 왔을 때를 보기 위해 android.provider.Telephony.SMS_RECEIVED를 추가해주었다.

그리고 앱에서 문자를 수신하기 위해서는 권한이 필요하므로 application 태그 위에 권한 요청도 추가해주었다.

 

※ Marshmallow 이후 버전부터는 SMS 수신이 위험 권한으로 설정되어 사용자에게 권한 요청을 한 후 동의를 얻어야 해당 기능을 사용할 수 있다. 따라서 이 코드는 Target SDK가 Marshmallow 이하인 경우에만 작동한다. 이후 버전에서 작동하게 하려면 권한 요청 코드를 추가해주어야 한다.

 

private void requirePerms(){
    String[] permissions = {Manifest.permission.RECEIVE_SMS};
    int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS);
    if(permissionCheck == PackageManager.PERMISSION_DENIED){
        ActivityCompat.requestPermissions(this, permissions, 1);
    }
}

일단은 간단하게 권한이 없을 경우 요청하도록 하는 함수를 MainActivity에 추가해준다.

 

그리고 에뮬레이터(가상머신)에서 SMS 수신을 테스트하고 싶다면 에뮬레이터 우측 메뉴에서 ··· 클릭 후 Phone 탭에서 전송이 가능하다.

 

메세지 전송을 누르면 에뮬레이터에 정상적으로 수신된 것을 볼 수 있고

 

로그에서도 onReceive() 메소드가 정상적으로 호출된 것을 볼 수 있다.

(뒤로가기 버튼을 눌러 앱을 종료해도 Broadcast Receiver는 작동한다)

 

SMS 내용 분석하기

package com.juhy.myapplication;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;

import java.util.Date;

public class SMSReceiver extends BroadcastReceiver {

    private static final String TAG = "SMSReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive() called");

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

        if(messages.length > 0){
            String sender = messages[0].getOriginatingAddress();
            String content = messages[0].getMessageBody().toString();
            Date date = new Date(messages[0].getTimestampMillis());

            Log.d(TAG, "sender: " + sender);
            Log.d(TAG, "content: " + content);
            Log.d(TAG, "date: " + date);
        }
    }

    private SmsMessage[] parseSmsMessage(Bundle bundle){
        // PDU: Protocol Data Units
        Object[] objs = (Object[]) bundle.get("pdus");
        SmsMessage[] messages = new SmsMessage[objs.length];

        for(int i=0; i<objs.length; i++){
            messages[i] = SmsMessage.createFromPdu((byte[])objs[i]);
        }

        return messages;
    }

}

기본적으로 방송은 intent를 통해 전달되는데 이 때 extra 데이터에 bundle의 형태로 방송의 정보가 들어오게 된다.

 

먼저 이 bundle을 SmsMessage 배열로 변환하는 함수를 작성해보자.

bundle 안에는 "pdus"라는 key 값으로 SMS 정보가 들어가 있다.

안드로이드에서는 SMS가 들어오면 이를 PDU(Protocol Data Units) 형태로 전달해주기 때문이다.

bundle로부터 메시지들을 받아왔다면 SmsMessage 배열을 그 크기만큼 만들어 준 뒤,

반복문을 통해 하나씩 PDU를 SmsMessage 객체로 변환해주어야 하는데 이는 createFromPdu() 함수를 사용하면 된다.

 

SmsMessage로 변환이 완료되었다면

발신자는 getOriginatingAddress(),

메시지 내용은 getMessageBody(),

시간은 getTimestampMillis() 함수를 통해 받아올 수 있다.

 

실행 후 문자를 보내보면 정상적으로 SMS 정보들이 수신된 것을 볼 수 있다.

 

이제는 Broadcast Receiver에서 수신한 정보를 activity로 보내 띄워보자.

 

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

private void sendToActivity(Context context, String sender, String content, Date date){
    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("content", content);
    intent.putExtra("date", format.format(date));
    context.startActivity(intent);
}

date를 String으로 변환하기 위한 format 정보를 먼저 만들어주고,

intent에 새로운 task 생성을 위한 flag를 설정해주고,

extra에 수신한 데이터를 넣어준 뒤 context 객체의 startActivity() 함수를 통해 intent를 보내보자.

(이 때 context 객체는 onReceive() 메소드의 파라미터에 있으므로 이를 다시 파라미터로 전달해주자)

 

package com.juhy.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;

public class SmsActivity extends AppCompatActivity {

    TextView tv_sender;
    TextView tv_date;
    TextView tv_content;

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

        tv_sender = findViewById(R.id.textView_sender);
        tv_date = findViewById(R.id.textView_date);
        tv_content = findViewById(R.id.textView_content);

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

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        processCommand(intent);
    }

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

            tv_sender.setText(sender);
            tv_date.setText(date);
            tv_content.setText(content);
        }
    }
}

액티비티에서는 intent가 들어오면 extra 정보를 추출하여 TextView에 설정해주도록 했다.

 

실행해보면 문자가 수신되면 정상적으로 액티비티가 뜨고 정보가 표시되는 것을 볼 수 있다.

 

 

Reference

[부스트코스]안드로이드 프로그래밍

https://www.edwith.org/boostcourse-android

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

Fragment 사용법  (0) 2020.04.09
Permission 획득하기  (0) 2020.04.04
Service의 기본적인 사용법  (0) 2020.04.03
Activity Lifecycle(수명주기)란?  (0) 2020.04.03
Intent를 이용한 Activity 간 데이터 전달  (0) 2020.04.03