【发布时间】:2013-12-06 16:17:35
【问题描述】:
我正在尝试通过让设备向自身发送 SMS 并自动检查是否已收到 SMS 来验证 Android 设备的电话号码。我该怎么做?
【问题讨论】:
我正在尝试通过让设备向自身发送 SMS 并自动检查是否已收到 SMS 来验证 Android 设备的电话号码。我该怎么做?
【问题讨论】:
首先,这需要两个权限;一个用于发送 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.SmsManager 和android.telephony.SmsMessage。请确保您为两者都导入了正确的类。
要发送外发短信,您将使用SmsManager 的sendTextMessage() 方法,该方法具有以下签名:
sendTextMessage(String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent)
这个方法调用只需要两个参数——destinationAddress和text;第一个是电话号码,第二个是消息内容。 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" 操作注册BroadcastReceiver 和IntentFilter。此接收器可以在清单中静态注册,也可以在运行时在Context 上动态注册。
在清单中静态注册 Receiver 类将允许您的应用接收传入消息,即使您的应用碰巧在接收之前被终止。但是,可能需要一些额外的工作才能在您想要的位置获得结果。 <application> 标签之间:
<receiver
android:name=".SmsReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
PackageManager#setComponentEnabledSetting() 方法可用于根据需要启用和禁用此<receiver>。
在Context 上动态注册 Receiver 实例在代码方面更易于管理,因为 Receiver 类可以成为注册它的任何组件的内部类,因此可以直接访问该组件的成员.但是,这种方法可能不如静态注册可靠,因为一些不同的事情可能会阻止接收器获取广播;例如,您的应用程序的进程被杀死,用户导航离开注册Activity,等等。
SmsReceiver receiver = new SmsReceiver();
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
registerReceiver(receiver, filter);
记得在适当的时候取消注册接收器。
在 Receiver 的 onReceive() 方法中,实际消息以附加到 Intent 的 byte 数组的形式出现。解码细节因 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" 属性被添加到开始的<intent-filter> 标记中。动态地,IntentFilter#setPriority() 方法也可以做到这一点。
这根本不可靠,因为另一个应用程序总是有可能比你的应用程序具有更高的优先级。
在这些示例中,我省略了在获得广播公司许可的情况下保护 Receiver,部分原因是为了简单明了,部分原因是事物的性质不会真正让您接受任何可能的欺骗伤害。但是,如果您想包含它,那么您只需将android:permission="android.permission.BROADCAST_SMS" 属性添加到静态选项的开头<receiver> 标记。对于动态,使用 registerReceiver() 方法的四参数重载,将权限 String 作为第三个参数传递,null 作为第四个参数传递。
【讨论】:
android:enabled="false"是真的而不是假的吗?
PackageManager#setComponentEnabledSetting() 方法可用于根据需要启用和禁用 <receiver>。”如果您需要始终启用它,您可以删除该属性设置。