دریافت پیامک (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 این آموزش به همراه سورس پروژه
تعداد صفحات : ۱۳
حجم : ۲ مگابایت
قیمت : رایگان
دانلود رایگان با حجم ۲ مگابایت لینک کمکی
این مطلب چقدر برایتان مفید بود؟ لطفا امتیاز دهید
4.2/5 - (16 امتیاز)
پرسش‌ها و دیدگاه‌های کاربران
دوره آموزش برنامه نویسی اندروید
دوره آموزش برنامه نویسی اندروید

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

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

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

  • علی گفت:

    سلام و خسته نباشید من از cursor در یکی از پروژه های قدیمیم استفاده کرده بودم با sdk 30 ولی الان اندروید استودیو کرسر رو روی sdk 31,32,33 نمیشناسه و با تغییر sdk خط مربوط به کرسر ارور میده و پروژه بیلد نمیشه ، اگر ممکنه راهنمایی بفرمایید
    با تشکر

  • yazdan گفت:

    سلام مثل همیشه عالی واقعا ممنون بابت آموزش های خوبی که میدین یک سوال با همین روش امکان خواندن اطلاعات پیام دهنده هایی مثل اوپراتور همراه اول یا پیام های بانکداری هست ؟ (alphanumeric )
    نزدیکترین چیزی که پیدا کردم این بود
    https://stackoverflow.com/questions/64872709/get-sms-sender-name-who-is-not-in-contact-list
    اما نمیدونم چطور با این کدها که شما زدین مچش کنم .

  • علیرضا... گفت:

    داداش لطفا بزار اگ برا بار دوم رو باتن کلیک شد کار متفاوتی انجام بده با اولین بار کلیک فرق داشته باشه

  • آرش گفت:

    سلام و درود.
    همین کار دریافت sms رو میخواستم وقتی برنامه بسته هست هم انجام بده و حتما لازم نباشه کاربر تو برنامه باشه.
    یعنی یک سرویس از برنامه در پس زمینه فعال باشه که دریافت sms رو تشخیص بده و بتونه متنش رو بخونه.
    مثل کاری که برنامه های پیام رسان مثل واتس آپ انجام میدن به این صورت که حتی وقتی بسته هستن یک سرویس در پس زمینه فعاله و اگر پیامی دریافت بشه نمایش میده.

  • حسین گفت:

    عالییییییییییییییی

  • behesht گفت:

    سلام. من میخوام کاربر شماره موبایلشو وارد کنه و بعد از بررسی درست بودن شماره موبایل یه پیام با کد تایید براش ارسال بشه که با وارد کردن اون کد به صفحه بعد بره میشه بگید چطوری؟
    ممنون

    • سیدمهدی مطهری گفت:

      نیاز هست با API ای که سرویس دهنده های پیامک ارائه میدن این کار رو پیاده کنید. مستندات سرویس دهنده پیامک مدنظرتون رو بررسی کنید ببینید چه توابعی لازمه برای اینکار

  • Amin گفت:

    سلام حاجی خسته نباشی
    چجوری میتونیم در همین اموزش متن پیام و شماره دریافت شده رو به یک فایل php در هاست ارسال کنیم؟!
    اگر امکانش هست اموزش بزارید تشکر

  • محمد صادق گفت:

    سلام.ضمن تشکر و قدردانی از زحمات شما استاد گرانقدر.
    بفرمایید اگر بخام پیامک درون برنامه ای داشته باشم طوری که دیگه پیام داخل اینباکس گوش نره . و بتونم فقط داخل برنامه خودم ازش استفاده کنم .آیا امکانش هست یا نه و اگر امکانش نباشه ایا امکانش هست به محض دریافت پیامک از شماره ای خاص بتونم اونو از داخل اینباکس گوشی حذف کنم.
    پاسخ این سوال برام بسیار حیاتی و مهمه.ممنون میشم لطف بفرمایید راهنمایی بفرمایید.

  • مبین گفت:

    سلام ببخشید یه سوال خارج از بحث داشتم
    ایا میشه با اندروید استدیو یه بازی دو بعدی که اجزا گرافیکی خیلی پیچیده ای هم نداره و همچنین مولتی پلیر هست رو بطور استاندارد و ایده ال ساخت
    با سپاس