【问题标题】:Using SMS to verify a device's phone number使用 SMS 验证设备的电话号码
【发布时间】:2013-12-06 16:17:35
【问题描述】:

我正在尝试通过让设备向自身发送 SMS 并自动检查是否已收到 SMS 来验证 Android 设备的电话号码。我该怎么做?

【问题讨论】:

    标签: android sms


    【解决方案1】:

    首先,这需要两个权限;一个用于发送 SMS 消息,一个用于接收它们。以下内容需要在您的 AndroidManifest.xml 中,在 <manifest> 标签之间,但在 <application> 标签之外。

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

    这些都是危险权限,因此如果您的应用要在 Marshmallow(API 级别 23)或更高版本上运行,并且 targetSdkVersion 为 23+,则需要相应地处理它们。有关如何在运行时请求这些权限的信息,请访问 this developer page


    您需要的 Java 类位于 android.telephony 包中;特别是android.telephony.SmsManagerandroid.telephony.SmsMessage。请确保您为两者都导入了正确的类。

    要发送外发短信,您将使用SmsManagersendTextMessage() 方法,该方法具有以下签名:

    sendTextMessage(String destinationAddress, String scAddress, String text,
                    PendingIntent sentIntent, PendingIntent deliveryIntent)
    

    这个方法调用只需要两个参数——destinationAddresstext;第一个是电话号码,第二个是消息内容。 null 可以通过其余的。例如:

    String number = "1234567890";
    String message = "Verification message.";
    SmsManager sm = SmsManager.getDefault();
    sm.sendTextMessage(number, null, message, null, null);
    

    保持消息文本相对较短很重要,因为如果文本长度超过单个消息的字符限制,sendTextMessage() 通常会静默失败。


    要接收和阅读传入的消息,您需要为"android.provider.Telephony.SMS_RECEIVED" 操作注册BroadcastReceiverIntentFilter。此接收器可以在清单中静态注册,也可以在运行时在Context 上动态注册。

    • 在清单中静态注册 Receiver 类将允许您的应用接收传入消息,即使您的应用碰巧在接收之前被终止。但是,可能需要一些额外的工作才能在您想要的位置获得结果。 &lt;application&gt; 标签之间:

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

      PackageManager#setComponentEnabledSetting() 方法可用于根据需要启用和禁用此&lt;receiver&gt;

    • Context 上动态注册 Receiver 实例在代码方面更易于管理,因为 Receiver 类可以成为注册它的任何组件的内部类,因此可以直接访问该组件的成员.但是,这种方法可能不如静态注册可靠,因为一些不同的事情可能会阻止接收器获取广播;例如,您的应用程序的进程被杀死,用户导航离开注册Activity,等等。

      SmsReceiver receiver = new SmsReceiver();
      IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
      registerReceiver(receiver, filter);
      

      记得在适当的时候取消注册接收器。


    在 Receiver 的 onReceive() 方法中,实际消息以附加到 Intentbyte 数组的形式出现。解码细节因 Android 版本而异,但此处的结果是一个 SmsMessage 对象,其中包含您要查找的电话号码和消息。

    class SmsReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            SmsMessage msg;
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                SmsMessage[] msgs = Telephony.Sms.Intents.getMessagesFromIntent(intent);
                msg = msgs[0];
            } else {
                Object pdus[] = (Object[]) intent.getExtras().get("pdus");
                msg = SmsMessage.createFromPdu((byte[]) pdus[0]);
            }
    
            String number = msg.getOriginatingAddress();
            String message = msg.getMessageBody();
            ...
        }
    }
    

    此时,您只需将此处的number 与传递给sendTextMessage() 调用的那个进行比较。建议为此使用PhoneNumberUtils.compare(),因为在接收器中检索到的号码可能与所寻址的格式不同。


    注意事项:

    • 此处演示的示例是使用一个单部分消息,因此应将消息文本限制为相对较短的长度。如果您确实想发送更长的消息,出于某种原因,可以使用sendMultipartTextMessage() 方法来代替。您需要先使用SmsManager#divideMessage() 拆分文本,然后将生成的ArrayList 传递给该方法,以代替消息String。要在 Receiver 中重新组装完整的消息,您必须将每个 byte[] 解码为 SmsMessage,并连接消息正文。

    • 自 KitKat(API 级别 19)以来,如果您的应用不是默认消息应用,则此处使用的消息将由系统和默认应用保存到 SMS Provider,因此任何其他应用都可以使用使用提供者。对此您无能为力,但如果您真的想避免这种情况,同样的技术可以用于数据 SMS,它不会触发默认应用,也不会保存到 Provider。

      为此,使用了sendDataMessage() 方法,它需要一个额外的short 参数作为(任意)端口号,并且消息作为byte[] 传递,而不是String。要过滤的操作是"android.intent.action.DATA_SMS_RECEIVED",过滤器需要设置数据方案和权限(主机和端口)。在清单中,它看起来像:

      <intent-filter>
          <action android:name="android.intent.action.DATA_SMS_RECEIVED" /> 
          <data
              android:scheme="sms"
              android:host="localhost"
              android:port="1234" /> 
      </intent-filter>
      

      IntentFilter 类中有相应的方法可以动态设置。

      解码SmsMessage是一样的,但是消息byte[]是用getUserData()检索的,而不是getMessageBody()

    • 在 KitKat 之前,应用程序负责编写自己的传出消息,因此如果您不想记录任何信息,您可以在这些版本上不这样做。

      传入的消息可能会被拦截,并且它们的广播在主消息应用程序接收和写入它们之前中止。为此,过滤器的优先级设置为最大值,并在接收器中调用abortBroadcast()。在静态选项中,android:priority="999" 属性被添加到开始的&lt;intent-filter&gt; 标记中。动态地,IntentFilter#setPriority() 方法也可以做到这一点。

      这根本不可靠,因为另一个应用程序总是有可能比你的应用程序具有更高的优先级。

    • 在这些示例中,我省略了在获得广播公司许可的情况下保护 Receiver,部分原因是为了简单明了,部分原因是事物的性质不会真正让您接受任何可能的欺骗伤害。但是,如果您想包含它,那么您只需将android:permission="android.permission.BROADCAST_SMS" 属性添加到静态选项的开头&lt;receiver&gt; 标记。对于动态,使用 registerReceiver() 方法的四参数重载,将权限 String 作为第三个参数传递,null 作为第四个参数传递。

    【讨论】:

    • 此答案也可用于 Stackoverflow 中关于如何避免从手机收件箱接收短信的问题
    • 不应该android:enabled="false"是真的而不是假的吗?
    • @MohsenEmami 此示例假定您只希望在进行验证时短时间启用接收器。否则,它将不必要地为收到的每条消息运行。正如我在下一句中提到的那样:“PackageManager#setComponentEnabledSetting() 方法可用于根据需要启用和禁用 &lt;receiver&gt;。”如果您需要始终启用它,您可以删除该属性设置。
    • 我更喜欢读/写详细的答案,这个答案中的细节是典型的。它包含你需要知道的一切,整个过程。谢谢你,先生。
    猜你喜欢
    • 2017-12-24
    • 2019-10-20
    • 1970-01-01
    • 2018-10-30
    • 2014-11-30
    • 2019-02-28
    • 2021-05-22
    • 2011-05-19
    • 2013-08-24
    相关资源
    最近更新 更多