안드로이드 폰에서 SMS 메세지를 수신했을때 어플에서 수신된 SMS메세지를 처리 하고 싶다면
BroadcastReceiver를 사용하면 된다.
우선 안드로이드 프로젝트를 하나 생성하자.
그리고 SMS관련 receiver를 사용하기 위해 AndroidManifest.xml 파일에 권한과 receiver 정보를 추가 한다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ahope.com.receiver"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<!-- SMS receive permission -->
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".TestSMSReceiver"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- SMS receiver -->
<receiver android:name="SmsReceiver">
<intent-filter>
<actionandroid:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
|
다음은 직접 SMS 메세지를 수신받아 처리할 SMSReceiver 클래스다.
public class SMSReceiver extends BroadcastReceiver {
static final String logTag = "SmsReceiver";
static final String ACTION = "android.provider.Telephony.SMS_RECEIVED";
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION)) {
//Bundel 널 체크
Bundle bundle = intent.getExtras();
if (bundle == null) {
return;
}
//pdu 객체 널 체크
Object[] pdusObj = (Object[]) bundle.get("pdus");
if (pdusObj == null) {
return;
}
//message 처리
SmsMessage[] smsMessages = new SmsMessage[pdusObj.length];
for (int i = 0; i < pdusObj.length; i++) {
smsMessages[i] = SmsMessage.createFromPdu((byte[]) pdusObj[i]);
Log.e(logTag, "NEW SMS " + i + "th");
Log.e(logTag, "DisplayOriginatingAddress : "
+ smsMessages[i].getDisplayOriginatingAddress());
Log.e(logTag, "DisplayMessageBody : "
+ smsMessages[i].getDisplayMessageBody());
Log.e(logTag, "EmailBody : "
+ smsMessages[i].getEmailBody());
Log.e(logTag, "EmailFrom : "
+ smsMessages[i].getEmailFrom());
Log.e(logTag, "OriginatingAddress : "
+ smsMessages[i].getOriginatingAddress());
Log.e(logTag, "MessageBody : "
+ smsMessages[i].getMessageBody());
Log.e(logTag, "ServiceCenterAddress : "
+ smsMessages[i].getServiceCenterAddress());
Log.e(logTag, "TimestampMillis : "
+ smsMessages[i].getTimestampMillis());
Toast.makeText(context, smsMessages[i].getMessageBody(), 0).show();
}
}
}
}
|
기기로 SMS가 도착하면 그것을 처리할 receiver 클래스이다.
클래스는 BroadcastReceiver 상속 받아 구현하고, SMS수신 시 하고자 하는 처리 내용을 onReceive 메소드에서 처리한다.
예제에서는 SMS에 관련된 내용을 Log로 출력하고, 메시지 내용을 알림창으로 출력하도록 하였다.
이제 실제적으로 SMS를 잘 수신하여 로그기록을 남기는지 살펴보도록 하자.
실제 안드로이드 폰이 있어서 직접 문사수신을 하여 테스트 할 수 도 있지만,
만약 에뮬레이터로 테스트를 진행하고자 한다면 telnet으로 에뮬레이터에 접속하여 전화 및 문자를 송신 할 수 있다.
에뮬레이터는 기본적으로 5554포트를 할당하며, telnet을 사용하여 접속 가능하다.
윈도우에서 콘솔창을 실행 후에 다음과 같은 명령어를 실행하여 에뮬레이터에 telnet으로 접속한다.
$> telnet localhost 5554
|
telnet으로 접속하였다면 이제 다음의 명령어로 문자를 보내보자.
$> sms send <phone number> <text message>
|
에뮬레이터에서 SMS 메세지를 수신하여 출력한 화면
==============================================================================================================
이제 SMS메세지를 수신받아 처리 할수 있다는 것을 알았다. 좀더 응용을 해보자.
가령 예를 들어 어딘가의 회원가입을 할때에 SMS으로 인증 번호가 날라올 경우
SMS메세지의 인증번호를 바로 번호창에 넣어준다면 사용자의 입장에서는 좀더 편하지 않을까?
이제 SMS를 수신받아 화면에 떠있는 액티비티에 값을 뿌려보자.
receiver에서 그냥 startActivity로 호출해 버려서는 현재 동작하고 있는 화면창이 유지가 안되고 새로운 액티비티로 호출된다.
그래서 화면의 값을 유지하면서 리시버에서 받은 값을 액티비티에 넣어주고자 다음과 같은 작업의 흐름도를 그렸다.
1) 문자가 수신되면 SMS Receiver에서 받아 데이터를 처리한다.
2) 액티비티가 아닌 곳에서 액티비티를 호출하기 위해서는 인텐트에 FLAG_ACTIVITY_NEW_TASK를 사용해야 하기 때문에,
인텐트를 전달해 주는 목적만을 가지는 액티비티를 따로 두어 SMS 관련 데이터를 전달한다.
3) startActivity를 통한 액티비티 호출 시, 새로운 액티비티를 호출하지 않고 기존에 호출 하여 사용하고 있는 액티비티를 재사용 하기 위해서
인텐트에 FLAG_ACTIVITY_SINGLE_TOP 와 FLAG_ACTIVITY_CLEAR_TOP 을 설정한다.
테스트를 위한 프로젝트의 Manifest 파일의 설정 및 구조는 1번의 예제를 재사용 및 추가하도록 하겠다.
main.layout 파일을 다음과 같이 꾸며보자
<?xml version="1.0" encoding="utf-8"?>
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:id="@+id/test"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<EditText android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
|
테스트를 위해서 액티비티가 유지되면서 SMS의 데이터를 전달해 주는지 확인여부를 위해,
EditText에 임의의 문자열을 입력한 상태에서 값을 유지하면서 textView에 값이 제대로 출력하는지 테스트 하겠다.
SMS Receiver 클래스
public class SmsReceiver extends BroadcastReceiver {
static final String logTag = "SmsReceiver";
static final String ACTION = "android.provider.Telephony.SMS_RECEIVED";
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION)) {
//Bundel null check
Bundle bundle = intent.getExtras();
if (bundle == null) {
return;
}
//pdu object null check
Object[] pdusObj = (Object[]) bundle.get("pdus");
if (pdusObj == null) {
return;
}
String str = ""; // 인텐트에 넣기 위한 임의 String 변수 선언
//message
SmsMessage[] smsMessages = new SmsMessage[pdusObj.length];
for (int i = 0; i < pdusObj.length; i++) {
smsMessages[i] = SmsMessage.createFromPdu((byte[]) pdusObj[i]);
str = smsMessages[i].getMessageBody(); // 임의의 String 변수에값 넣음
}
intent.putExtra("test2", str);
intent.setClass(context, RedirectActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
}
|
리시버에서 바로 해당 액티비티를 호출하지 않는 이유는
액티비티가 아닌곳에서 startActivity를 사용하면 다음과 같은 에러메시지가 출력되면서 프로세스가 멈추게 되기 때문이다.
ERROR/AndroidRuntime(): Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
|
에러메세지의 마지막엔 진짜로 그렇게 하기를 원하는 거냐고 물어봐 주시기까지 한다.
액티비티의 재사용을 위해서 FLAG_ACTIVITY_SINGLE_TOP와 FLAG_ACTIVITY_CLEAR_TOP의 flag를 사용해야 하는데
리시버에서 새로운 액티비티를 호출하기 위해서는 FLAG_ACTIVITY_NEW_TASK를 사용해서 액티비티를 호출해야 한다.
따라서 리시버에서 해당 엑티비티로 값을 전달만 해주는 임의의 액티비티를 하나 생성하도록 하겠다.
public class RedirectActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
intent.setClass(RedirectActivity.this, TestSMSReceiver.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP |Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
}
}
|
Data를 전달해주는 기능만을 가진 액티비티다.
리시버에서 액티비티 호출 시 재사용을 위한 FLAG를 사용하지 못하였기 때문에 여기에서 FLAG_ACTIVITY_SINGLE_TOP와FLAG_ACTIVITY_CLEAR_TOP을 사용한다.
FLAG_ACTIVITY_SINGLE_TOP은 액티비티가 스택의 맨 위에 존재하는 경우에 기존 액티비티를 재활용한다.
FLAG_ACTIVITY_CLEAR_TOP은 스택에 기존에 사용하던 액티비티가 있다면, 그 위의 스택을 전부 제거해 주고 호출한다.
이제 화면의 값을 뿌려줄 액티비티를 꾸며보도록 하자.
public class TestSMSReceiver extends Activity {
private String test2 = "";
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
TextView textView2 = (TextView)findViewById(R.id.test);
test2 = intent.getStringExtra("test2");
textView2.setText(test2);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
|
특정 액티비티가 새로운 Intent를 전달 받을 경우 내부적으로 onNewIntent API 가 호출된다.
액티비티가 새로 만들어지는 것이 아니라 다시 재사용 되는 것이기 때문에
receiver에서 받은 정보를 출력하는 과정은 onCreate가 아닌 onNewIntent에서 처리하도록 한다.
EditeText창에 임의의 값을 넣어놓은 상태에서 값을 유지하면서 수신된 문자 메세지의 내용 '123'을 위에 표시한 화면이다.
무엇인가 찾아보면 더 좋은 방법이 있을 것이라 생각하지만,
우선 이러한 방법도 있다는 것(이라기 보다는 어떻게 해보려고 꼼수를 부려본 듯한 느낌)을 남겨 놓는다.
댓글 없음:
댓글 쓰기