دریافت پیامک (SMS) در برنامه اندرویدی + سورس پروژه

دریافت پیامک (SMS) در برنامه نویسی اندروید
کاربردهای متعددی را برای قابلیت دریافت پیامک (SMS) در برنامه نویسی اندروید می‌توان برشمرد. با گسترش کاربرد اپلیکیشن‌های موبایلی و بویژه اندرویدی، حجم زیادی از خدمات بخش خصوصی و سازمانی در این محیط به مردم ارائه می‌گردد.
برای مثال احراز هویت کاربر/مشتری یکی از آیتم‌های ضروری در ارائه این خدمات به شمار می‌رود که ارسال پیامک کد تایید به شماره همراه شخص یکی از رایج‌ترین روش‌هاست. اما برای بهبود UX (تجربه کاربری) می‌توان این بخش را به صورت خودکار انجام داد تا کاربر ملزم به وارد کردن دستی کد دریافتی نبوده و کد موجود در پیامک به طور خوکار وارد برنامه شود.
البته کاربردهای دیگری هم برای دسترسی به پیامک‌های دریافتی وجود دارد و محدود به احراز هویت نیست. در این جلسه نحوه‌ی دریافت پیامک (SMS) در برنامه نویسی اندروید را به دو روش متفاوت بررسی می‌کنیم.

نحوه‌ دریافت پیامک در اپلیکیشن اندرویدی

به نام خدا. به طور کل با استفاده از دو کلاس SmsMessage و Cursor می‌توانیم به پیامک‌های موجود در دستگاه اندرویدی دسترسی داشته باشیم. البته روش متداول، استفاده از SmsMessage است. در ادامه به بررسی هر دو روش پرداخته و سپس به صورت عملی پیاده سازی می‌کنیم.
SmsMessage: کلاسی با نام SmsMessage در API 4 به سیستم عامل اندروید اضافه شده که وظیفه دسترسی به پیامک یا همان SMS را در دستگاه‌های اندرویدی بر عهده دارد.
البته از API 1 هم کلاسی با این نام وجود داشت (android.telephony.gsm.SmsMessage) که تنها از پروتکل GSM پشتیبانی می‌کرد. اما در API 4 این کلاس با android.telephony.SmsMessage جایگزین شد که از هر دو پروتکل GSM و CDMA پشتیبانی می‌کند.
همچنین برای شنود رویداد مربوط به دریافت پیامک لازم است از برودکست رسیور استفاده کنیم که قبلا در آموزش BroadcastReceiver در اندروید با این مبحث آشنا شدیم.
Cursor: علاوه بر کلاس SmsMessage با استفاده از کلاس Cursor هم می‌توان به آرشیو پیامک‌ها دسترسی داشت که با وجود کلاس SmsMessage استفاده از آن جایگاهی نخواهد داشت با این حال در انتهای آموزش به این مورد نیز اشاره مختصری خواهیم کرد.

ساخت پروژه دریافت SMS در اندروید استودیو

برای بررسی نحوه دریافت پیامک (SMS) در برنامه نویسی اندروید مطابق مبحث آموزش ساخت پروژه در اندروید استودیو یک پروژه اندرویدی با نام ReceiveSMS می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.

دریافت پیامک (SMS) توسط SmsMessage

اگر بخاطر داشته باشید در جلسه نحوه ارسال پیامک (SMS) در برنامه نویسی اندروید در مانیفست پروژه مجوز ارسال پیامک را تعریف کردیم. در این پروژه لازم است عکس آن یعنی مجوز دریافت SMS را تعریف کنیم:

<uses-permission android:name="android.permission.RECEIVE_SMS" />

AndroidManifest.xml

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

    <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.ReceiveSMS">
        <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>

در قدم بعد لازم است یک BroadcastReceiver ایجاد کنم. بر خلاف پروژه ارسال SMS که برودکست درون اکتیویتی و به صورت داینامیک رجیستر شد، در این جلسه برودکست را در کلاس مجزا تعریف کرده و همچنین رجیستر یا ثبت آن را به صورت استاتیک و درون Manifest انجام می‌دهم.
یک کلاس با نام SMSBroadcastReceiver به پروژه اضافه کرده و آنرا از BroadcastReceiver مشتق می‌کنم. سپس متد onReceive را اضافه می‌کنم:

SMSBroadcastReceiver.java

package ir.android_studio.receivesms;

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

public class SMSBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        
    }
}

سپس برودکست رسیور را در مانیفست پروژه ثبت می‌کنم:

<receiver android:name=".SMSBroadcastReceiver">
    <intent-filter>
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
</receiver>

کد کامل مانیفست:
AndroidManifest.xml

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

    <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.ReceiveSMS">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name=".SMSBroadcastReceiver">
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

یک اکشن با نام android.provider.Telephony.SMS_RECEIVED در مانیفست تعریف کردیم. این اکشن وظیفه بررسی رویدادهای مربوط به دریافت پیامک را در سیستم عامل بر عهده دارد.
قبل از پیاده سازی کلاس SmsMessage قصد دارم صرفا عملکرد برودکست را بررسی کنم و مطمئن شوم action ای که در مانیفست تعریف کردم می‌تواند به دریافت پیامک واکنش نشان دهد. بنابراین در متد onReceive برودکست رسیور صرفا یک پیغام Toast تعریف می‌کنم:

public void onReceive(Context context, Intent intent) {

    Toast.makeText(context, "یک پیامک دریافت شد", Toast.LENGTH_SHORT).show();

}

با توجه به اینکه قصد دارم پروژه را روی یک دیوایس بالاتر از اندروید ۶ اجرا کنم، لازم است کدهای مربوط به Runtime Permission را مطابق آموزش Runtime Permission در اندروید به اکتیویتی اضافه کنم:

MainActivity.java

package ir.android_studio.receivesms;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private final int SMS_REQUEST_CODE = 100;

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

        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECEIVE_SMS) != PackageManager.PERMISSION_GRANTED) {

            requestReceiveSMSpermission();

        } else {

            Toast.makeText(this, "مجوز قبلا دریافت شده", Toast.LENGTH_SHORT).show();

        }

    }

    private void requestReceiveSMSpermission() {

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.RECEIVE_SMS)) {

            new AlertDialog.Builder(this)
                    .setTitle("درخواست مجوز")
                    .setMessage("برای عملکرد صحیح برنامه باید دسترسی به دریافت پیامکتایید شود")
                    .setPositiveButton("موافقم", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            reqPermission();

                        }
                    })
                    .setNegativeButton("لغو", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            dialogInterface.dismiss();

                        }
                    })
                    .create()
                    .show();

        } else {

            reqPermission();

        }

    }

    private void reqPermission() {

        ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.RECEIVE_SMS}, SMS_REQUEST_CODE);

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == SMS_REQUEST_CODE) {

            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                Toast.makeText(this, "مجوز تایید شد", Toast.LENGTH_SHORT).show();

            } else {

                Toast.makeText(this, "مجوز رد شد", Toast.LENGTH_SHORT).show();

            }

        }

    }

}

یکی از دیوایس‌های امولاتور اندرویدی Genymotion را اجرا می‌کنم. یکی از امکانات کاربردی شبیه سازهای اندرویدی از جمله Genymotion و همچنین AVD (شبیه ساز داخلی اندروید استودیو) قابلیت ارسال پیامک به دستگاه و همچنین برقراری تماس است. بدون آنکه نیاز به اجرا و تست برنامه روی یک دستگاه واقعی داشته باشیم.
پروژه را روی شبیه ساز اجرا کرده و مجوز دسترسی به پیامک‌ها را برای برنامه تایید می‌کنم. سپس روی آیکون Phone منوی سمت راست دیوایس کلیک می‌کنم:

تایید مجوز دسترسی به پیامک‌ها در اندروید
تایید مجوز دسترسی به پیامک‌ها در اندروید
قابلیت ارسال SMS در امولاتور Genymotion
قابلیت ارسال SMS در امولاتور Genymotion
ارسال پیامک آزمایشی در شبیه ساز اندروید
ارسال پیامک آزمایشی در شبیه ساز اندروید

در کادر Phone یک شماره فرستنده و متن دلخواه وارد کرده و پیام را ارسال می‌کنم. بلافاصله پیغام Toast ای که در متد onReceive تعریف کرده بودم اجرا شد:

دریافت رویداد پیامک توسط BroadcastReceiver
دریافت رویداد پیامک توسط BroadcastReceiver

تا اینجای کار مطمئن شدیم برنامه ما می‌تواند به درستی رویدادهای مربوط به دریافت پیامک را شنود کند. در قدم بعد می‌خواهیم محتوای SMS دریافتی را به دست بیاوریم که شامل شماره ارسال کننده پیام و همچنین متن پیامک می‌باشد.
پیغام Toast داخل متد onReceive برودکست رسیور را حذف کرده و کد زیر را جایگزین آن می‌کنم:

if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {

    Bundle mBundle = intent.getExtras();
    SmsMessage[] msg;
    String smsFrom;

    if (mBundle != null) {
        try {
            Object[] mPdus = (Object[]) mBundle.get("pdus");
            msg = new SmsMessage[mPdus.length];

            for (int i = 0; i < mPdus.length; i++) {
                msg[i] = SmsMessage.createFromPdu((byte[]) mPdus[i]);
                smsFrom = msg[i].getOriginatingAddress();
                String smsBody = msg[i].getMessageBody();

                Toast.makeText(context, "شماره: " + smsFrom + " / پیام: " + smsBody, Toast.LENGTH_SHORT).show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

در ابتدا یک if تعریف شده که بررسی می‌کند کدها تنها در صورتی اجرا شوند که اینتنت اکشن دریافتی از نوع android.provider.Telephony.SMS_RECEIVED باشد. یعنی دقیقا همان اکشنی که قبلا در AndroidManifest.xml و در تگ receiver ثبت کردیم.
سپس یک نمونه از Bundle و SmsMessage و یک String ساخته شده. در ادامه ابتدا بررسی می‌شود آیا مقدار ذخیره شده در mBundle نال است یا نه؛ یعنی داده‌ای که مربوط به محتوای یک پیامک باشد در باندل ذخیره شده یا خیر.
یک نمونه از Object[] با نام دلخواه mPdus ایجاد شده که داده‌ها با فرمت PDU (مخفف Protocol Data Unit) در آن ذخیره می‌شود. با استفاده از PDU به ساختار محتوای پیام دریافتی دسترسی داریم.
سپس در حلقه for با استفاده از SmsMessage.createFromPdu() محتوای پیام دریافتی تفکیک می‌شود. همانطور که ملاحظه می‌کنید در دو خط بعد توسط getOriginatingAddress و getMessageBody به ترتیب به شماره ارسال کننده پیام و متن پیام دسترسی خواهیم داشت. کاربرد متدها از نحوه نامگذاری آنها هم مشخص است. عبارت Originating Address به معنی آدرس مبدا و Message Body به معنی بدنه یا متن پیام است.
در انتها، این دو آیتم در یک پیغام Toast قرار داده شده است.
کد کامل کلاس برودکست رسیور:

SMSBroadcastReceiver.java

package ir.android_studio.receivesms;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.widget.Toast;

public class SMSBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        // Toast.makeText(context, "یک پیامک دریافت شد", Toast.LENGTH_SHORT).show();

        if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {

            Bundle mBundle = intent.getExtras();
            SmsMessage[] msg;
            String smsFrom;

            if (mBundle != null) {
                try {
                    Object[] mPdus = (Object[]) mBundle.get("pdus");
                    msg = new SmsMessage[mPdus.length];

                    for (int i = 0; i < mPdus.length; i++) {
                        msg[i] = SmsMessage.createFromPdu((byte[]) mPdus[i]);
                        smsFrom = msg[i].getOriginatingAddress();
                        String smsBody = msg[i].getMessageBody();

                        Toast.makeText(context, "شماره: " + smsFrom + " / پیام: " + smsBody, Toast.LENGTH_SHORT).show();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }

    }
}
نکته: در کد فوق، متد createFromPdu دو پارامتر ورودی به صورت

createFromPdu(byte[] pdu, String format)

می‌گیرد که تا قبل از API 23 دارای یک پارامتر ورودی و به صورت

createFromPdu(byte[] pdu)

بوده است. اما نیازی به تعریف هردو متد بر اساس نسخه اندروید کاربر نیست. من پروژه را بر روی اندروید ۴ به بالا تست کردم و اختلالی در دریافت SMS در نسخه‌های قدیمی وجود نداشت.

پروژه را دوباره اجرا کرده و با استفاده از گزینه Phone یک پیام جدید ارسال می‌کنم. حالا شماره ارسال کننده و متن SMS هم در Toast نمایش داده می‌شود:

دریافت شماره مبدا و متن پیامک توسط SmsMessage
دریافت شماره مبدا و متن پیامک توسط SmsMessage

دریافت پیامک (SMS) توسط Cursor

همانطور که در ابتدای جلسه اشاره شد، با وجود کلاس SmsMessage استفاده از Cursor برای دریافت پیامک توجیهی نخواهد داشت اما لازم دانستم حداقل برای تمرین به این مورد هم اشاره کنم.
یک پروژه جدید بسازید و TextView پیش فرض اکتیویتی را به Button تبدیل کنید. سپس کد زیر را در رویداد onClickListener دکمه قرار دهید (از تعریف Runtime Permission فراموش نکنید).

Cursor cursor = getContentResolver().query(Uri.parse("content://sms"), null, null, null, null);

cursor.moveToFirst();

Toast.makeText(this, cursor.getString(12), Toast.LENGTH_SHORT).show();

بعد از اجرای پروژه و تایید مجوز دریافت SMS، مانند قبل یک پیامک ارسال کرده و سپس روی Button کلیک کنید. متن پیامک در قالب Toast نمایش داده خواهد شد. البته در این روش هم می‌توان شماره ارسال کننده پیامک را دریافت کرد که به آن نمی‌پردازم.

توجه : سورس پروژه درون پوشه Exercises قرار دارد

دانلود نسخه PDF این آموزش به همراه سورس پروژه
تعداد صفحات : ۱۳
حجم : ۲ مگابایت
قیمت : رایگان
دانلود رایگان با حجم ۲ مگابایت لینک کمکی
این مطلب چقدر برایتان مفید بود؟ لطفا امتیاز دهید
دوره آموزش برنامه نویسی اندروید
دوره آموزش برنامه نویسی اندروید

آموزش‌ها در دو دسته «پایه» و «تکمیلی» منتشر می‌شوند.
آموزش‌های پایه شامل مباحث اصلی و آموزش‌های تکمیلی مطالبی است که می‌بایست در کنار مطالب اصلی از آنها استفاده کنیم.
شما با دریافت این دوره به تمامی آموزش‌های غیر رایگان و رایگان موجود در وب سایت دسترسی دارید که تخفیفی برای آموزش‌های غیر رایگان نیز درنظر گرفته شده. این پکیج به دو صورت دانلودی و ارسال پستی ارائه می‌گردد.
با خرید این دوره، به تمامی آموزش‌های غیر رایگانی که در آینده منتشر می‌شود نیز به صورت رایگان دسترسی خواهید داشت!

یک دیدگاه بنویسید

پرسش‌های زیر تایید و پاسخ داده نـــخواهند شد:
۱: جزء موارد مطرح شده در صفحات مشکلات و پرسش‌های رایج و بروزرسانی‌های محتوای آموزشی باشد
۲: سوال قبلا توسط کاربران در دیدگاه‌ها مطرح و پاسخ داده شده باشد
۳: پرسش خارج از مبحث آموزشی موجود در این صفحه باشد