安卓 - 监听短信信息的到来

178

我正在尝试创建一个应用程序,用于监视接收到的短信消息,并通过接收到的短信启动程序,同时它应该读取短信内容。

工作流程:

  • 短信发送到Android设备
  • 自执行的应用程序
  • 读取短信信息

1
我知道如何创建一个发送短信的应用程序,但是在这里我需要创建一个短信应用程序,它可以从短信中获取信息并将其保存到SQLite数据库中...... 我该如何开发这样的应用程序? - iShader
@iShader,希望你成功地创建了应用程序,我只是想知道你是如何在设备和服务器之间同步消息的。 - John x
9个回答

282
public class SmsListener extends BroadcastReceiver{

    private SharedPreferences preferences;

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO Auto-generated method stub

        if(intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")){
            Bundle bundle = intent.getExtras();           //---get the SMS message passed in---
            SmsMessage[] msgs = null;
            String msg_from;
            if (bundle != null){
                //---retrieve the SMS message received---
                try{
                    Object[] pdus = (Object[]) bundle.get("pdus");
                    msgs = new SmsMessage[pdus.length];
                    for(int i=0; i<msgs.length; i++){
                        msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]);
                        msg_from = msgs[i].getOriginatingAddress();
                        String msgBody = msgs[i].getMessageBody();
                    }
                }catch(Exception e){
//                            Log.d("Exception caught",e.getMessage());
                }
            }
        }
    }
}

注意:在你的清单文件中添加BroadcastReceiver-

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

添加此权限:

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

2
你能解释一下为什么要使用次级接收器吗? - WindRider
2
@VineetShukla 你能否解释一下什么是PDU? - TheGraduateGuy
14
使用 Intents.SMS_RECEIVED_ACTION 替代硬编码的方式。 - Ahmad Kayyali
6
以上评论是不正确的。任何应用程序仍然可以在Android 4.4及以上版本中接收 SMS_RECEIVED 广播,并且现在该广播无法被取消,比以前的版本更加确定。 - Mike M.
4
多部分短信。单个短信消息有一个字符限制(取决于使用的字符集,但常见的限制是70、140、160个字符)。如果消息超过这个限制,它可以分成多个消息部分。那个数组就是你需要连接起来得到完整消息的部分数组。你的接收器每次只会收到一条完整的消息;只不过这条消息可能由多个部分组成。 - Mike M.
显示剩余12条评论

76

请注意,在某些设备上,如果意图过滤器中没有android:priority="1000",则您的代码可能无法正常工作:

请注意,在某些设备上,如果意图过滤器中没有android:priority="1000",则您的代码可能无法正常工作:

<receiver android:name=".listener.SmsListener">
    <intent-filter android:priority="1000">
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
</receiver>

以下是一些优化措施:

public class SmsListener extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(intent.getAction())) {
            for (SmsMessage smsMessage : Telephony.Sms.Intents.getMessagesFromIntent(intent)) {
                String messageBody = smsMessage.getMessageBody();
            }
        }
    }
}

注意
值必须是整数,例如 "100"。较高的数字具有较高的优先级。默认值为 0。该值必须大于-1000并且小于1000。

这里是一个链接。


31
这个答案可能更加优美,但需要API 19及以上版本。这只是提醒其他人的信息。 - baekacaek
10
根据这篇文章android:priority的值不能高于1000(或低于-1000)。 - craned
3
它在安卓5.1的小米红米Note 3 Pro上无法工作。每个人都提供这个解决方案,但似乎对我没有用。 - Sermilion
在清单文件中,<receiver...>标记插入在哪里? - John Ward
3
你需要在手机的应用管理器中手动允许读取短信的权限。 - Sanjay Kushwah

8

@Mike M.和我发现了一个已接受答案的问题(请参见我们的评论):

基本上,如果我们没有每次连接多部分消息,则没有必要通过for循环进行操作:

for (int i = 0; i < msgs.length; i++) {
    msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]);
    msg_from = msgs[i].getOriginatingAddress();
    String msgBody = msgs[i].getMessageBody();
}

请注意,我们只是将 msgBody 设置为消息相应部分的字符串值,而不管我们现在处于哪个索引位置,这使得遍历不同部分的短信消息的整个过程变得毫无用处,因为它最终只会被设置为最后一个索引值。相反,我们应该使用 +=,或者像 Mike 指出的那样,使用 StringBuilder
总的来说,以下是我的短信接收代码的样子:
if (myBundle != null) {
    Object[] pdus = (Object[]) myBundle.get("pdus"); // pdus is key for SMS in bundle

    //Object [] pdus now contains array of bytes
    messages = new SmsMessage[pdus.length];
    for (int i = 0; i < messages.length; i++) {
         messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); //Returns one message, in array because multipart message due to sms max char
         Message += messages[i].getMessageBody(); // Using +=, because need to add multipart from before also
    }

    contactNumber = messages[0].getOriginatingAddress(); //This could also be inside the loop, but there is no need
}

如果有其他人也有同样的困惑,我会在这里提供答案。


5
接受的答案是正确的,并且适用于在应用程序安装时Android OS要求权限的旧版本Android,但在较新版本的Android上它不会立即起作用,因为较新版本的Android OS在应用程序需要该功能时在运行时请求权限。因此,为了在较新版本的Android上使用接受答案中提到的技术接收短信,程序员还必须实现代码,在运行时检查并请求用户的权限。在这种情况下,权限检查功能/代码可以在应用程序的第一个活动的onCreate()中实现。只需复制并粘贴以下两个方法到您的第一个活动中,并在onCreate()的结尾调用checkForSmsReceivePermissions()方法。
    void checkForSmsReceivePermissions(){
    // Check if App already has permissions for receiving SMS
    if(ContextCompat.checkSelfPermission(getBaseContext(), "android.permission.RECEIVE_SMS") == PackageManager.PERMISSION_GRANTED) {
        // App has permissions to listen incoming SMS messages
        Log.d("adnan", "checkForSmsReceivePermissions: Allowed");
    } else {
        // App don't have permissions to listen incoming SMS messages
        Log.d("adnan", "checkForSmsReceivePermissions: Denied");

        // Request permissions from user 
        ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.RECEIVE_SMS}, 43391);
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if(requestCode == 43391){
        if(grantResults.length>0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            Log.d("adnan", "Sms Receive Permissions granted");
        } else {
            Log.d("adnan", "Sms Receive Permissions denied");
        }
    }
}

4

如果有人像我一样参考如何在Xamarin Android上执行相同的功能(使用接收到的短信读取OTP),则可以按照以下步骤:

  1. Add this code to your AndroidManifest.xml file :

    <receiver android:name=".listener.BroadcastReveiverOTP">
    <intent-filter>
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
    </receiver>
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.BROADCAST_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
    
  2. Then create your BroadcastReveiver class in your Android Project.

    [BroadcastReceiver(Enabled = true)] [IntentFilter(new[] { "android.provider.Telephony.SMS_RECEIVED" }, Priority = (int)IntentFilterPriority.HighPriority)] 
    public class BroadcastReveiverOTP : BroadcastReceiver {
            public static readonly string INTENT_ACTION = "android.provider.Telephony.SMS_RECEIVED";
    
            protected string message, address = string.Empty;
    
            public override void OnReceive(Context context, Intent intent)
            {
                if (intent.HasExtra("pdus"))
                {
                    var smsArray = (Java.Lang.Object[])intent.Extras.Get("pdus");
                    foreach (var item in smsArray)
                    {
                        var sms = SmsMessage.CreateFromPdu((byte[])item);
                        address = sms.OriginatingAddress;
                        if (address.Equals("NotifyDEMO"))
                        {
                            message = sms.MessageBody;
                            string[] pin = message.Split(' ');
                            if (!string.IsNullOrWhiteSpace(pin[0]))
                            { 
                                    // NOTE : Here I'm passing received OTP to Portable Project using MessagingCenter. So I can display the OTP in the relevant entry field.
                                    MessagingCenter.Send<object, string>(this,MessengerKeys.OnBroadcastReceived, pin[0]);
                            }
                            }
                    }
                }
            }
    }
    
  3. Register this BroadcastReceiver class in your MainActivity class on Android Project:

    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity {
    
            // Initialize your class
            private BroadcastReveiverOTP _receiver = new BroadcastReveiverOTP ();
    
            protected override void OnCreate(Bundle bundle) { 
                    base.OnCreate(bundle);
    
                    global::Xamarin.Forms.Forms.Init(this, bundle);
                    LoadApplication(new App());
    
                    // Register your receiver :  RegisterReceiver(_receiver, new IntentFilter("android.provider.Telephony.SMS_RECEIVED"));
    
            }
    }
    

收到编译器错误,提示“android.permission.BROADCAST_SMS”仅授予系统应用程序。 - committedandroider

2

如果您想在已打开的活动中处理意图,可以使用PendintIntent(请按以下完整步骤操作):

public class SMSReciver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final Bundle bundle = intent.getExtras();
        try {
            if (bundle != null) {
                final Object[] pdusObj = (Object[]) bundle.get("pdus");
                for (int i = 0; i < pdusObj.length; i++) {
                    SmsMessage currentMessage = SmsMessage.createFromPdu((byte[]) pdusObj[i]);
                    String phoneNumber = currentMessage.getDisplayOriginatingAddress();
                    String senderNum = phoneNumber;
                    String message = currentMessage.getDisplayMessageBody();
                    try {
                        if (senderNum.contains("MOB_NUMBER")) {
                            Toast.makeText(context,"",Toast.LENGTH_SHORT).show();

                            Intent intentCall = new Intent(context, MainActivity.class);
                            intentCall.putExtra("message", currentMessage.getMessageBody());

                            PendingIntent pendingIntent= PendingIntent.getActivity(context, 0, intentCall, PendingIntent.FLAG_UPDATE_CURRENT);
                            pendingIntent.send();
                        }
                    } catch (Exception e) {
                    }
                }
            }
        } catch (Exception e) {
        }
    }
} 

清单文件:

<activity android:name=".MainActivity"
            android:launchMode="singleTask"/>
<receiver android:name=".SMSReciver">
            <intent-filter android:priority="1000">
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
            </intent-filter>
        </receiver>

onNewIntent:

 @Override
         protected void onNewIntent(Intent intent) {
                super.onNewIntent(intent);
                Toast.makeText(this, "onNewIntent", Toast.LENGTH_SHORT).show();

                onSMSReceived(intent.getStringExtra("message"));

            }

权限:

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

1
Google Play商店的管理员认为教程中提到的RECEIVE_SMS权限是危险的。因此,包含该权限的应用将被拒绝。然后开发者必须向Google Play管理员提交表格以获得批准。其他开发者已经提到这个过程很糟糕,反馈需要数周时间,并且会收到没有解释或通用反馈的明确拒绝。有什么避免的想法吗? - AJW

2
感谢 @Vineet Shukla(被采纳的答案)和 @Ruchir Baronia(在被采纳的答案中发现了问题),以下是 Kotlin 版本:
添加权限:
<uses-permission android:name="android.permission.RECEIVE_SMS" />

在AndroidManifest中注册BroadcastReceiver:
<receiver
    android:name=".receiver.SmsReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter android:priority="2332412">
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
</receiver>

添加BroadcastReceiver的实现:

class SmsReceiver : BroadcastReceiver() {
    private var mLastTimeReceived = System.currentTimeMillis()

    override fun onReceive(p0: Context?, intent: Intent?) {
        val currentTimeMillis = System.currentTimeMillis()
        if (currentTimeMillis - mLastTimeReceived > 200) {
            mLastTimeReceived = currentTimeMillis

            val pdus: Array<*>
            val msgs: Array<SmsMessage?>
            var msgFrom: String?
            var msgText: String?
            val strBuilder = StringBuilder()
            intent?.extras?.let {
                try {
                    pdus = it.get("pdus") as Array<*>
                    msgs = arrayOfNulls(pdus.size)
                    for (i in msgs.indices) {
                        msgs[i] = SmsMessage.createFromPdu(pdus[i] as ByteArray)
                        strBuilder.append(msgs[i]?.messageBody)
                    }

                    msgText = strBuilder.toString()
                    msgFrom = msgs[0]?.originatingAddress

                    if (!msgFrom.isNullOrBlank() && !msgText.isNullOrBlank()) {
                        //
                        // Do some thing here
                        //
                    }
                } catch (e: Exception) {
                }
            }
        }
    }
}

有时候事件会触发两次,因此我添加了 mLastTimeReceived = System.currentTimeMillis()

1

最近一段时间以来,如果你不是默认的短信应用程序,几乎不可能发布一个带有android.permission.RECEIVE_SMS权限的应用。Google提供了一个新的工具来捕获短信 ==> 使用SMS Retriever API进行自动短信验证


0
Kotlin中的广播实现:
 private class SmsListener : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Log.d(TAG, "SMS Received!")

        val txt = getTextFromSms(intent?.extras)
        Log.d(TAG, "message=" + txt)
    }

    private fun getTextFromSms(extras: Bundle?): String {
        val pdus = extras?.get("pdus") as Array<*>
        val format = extras.getString("format")
        var txt = ""
        for (pdu in pdus) {
            val smsmsg = getSmsMsg(pdu as ByteArray?, format)
            val submsg = smsmsg?.displayMessageBody
            submsg?.let { txt = "$txt$it" }
        }
        return txt
    }

    private fun getSmsMsg(pdu: ByteArray?, format: String?): SmsMessage? {
        return when {
            SDK_INT >= Build.VERSION_CODES.M -> SmsMessage.createFromPdu(pdu, format)
            else -> SmsMessage.createFromPdu(pdu)
        }
    }

    companion object {
        private val TAG = SmsListener::class.java.simpleName
    }
}

注意:在您的清单文件中添加BroadcastReceiver-。
<receiver android:name=".listener.SmsListener">
    <intent-filter>
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
</receiver>

请添加此权限:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接