我正在尝试通过让设备向自己发送短信来验证Android设备的电话号码,并自动检查是否已收到短信。我怎么能这样做?
答案 0 :(得分:64)
首先,这需要两个权限;一个发送短信,一个发送短信。以下内容需要位于您的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()
通常会无声地失败。
要接收和阅读收到的消息,您需要为BroadcastReceiver
操作注册IntentFilter
"android.provider.Telephony.SMS_RECEIVED"
。此Receiver可以在清单中静态注册,也可以在运行时在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()
方法中,实际消息是作为额外附加到byte
的{{1}}数组的数组。解码详细信息因Android版本而异,但此处的结果是单个Intent
对象,其中包含您之后的电话号码和消息。
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()
,因为在Receiver中检索到的数字格式可能与寻址的格式不同。
注意:
此处演示的示例是使用一个单部分消息,因此消息文本应限制为相对较短的长度。如果您确实想要发送更长的消息,出于某种原因,可以使用sendMultipartTextMessage()
方法。您需要先使用SmsManager#divideMessage()
拆分文本,然后将生成的ArrayList
传递给该方法,以代替邮件String
。要在Receiver中重新组合完整的邮件,您必须将每个byte[]
解码为SmsMessage
,并连接邮件正文。
自KitKat(API级别19)以来,如果您的应用程序不是默认的消息传递应用程序,则此处使用的消息将由系统和默认应用程序保存到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()
方法也可以这样做。
这一点都不可靠,因为其他应用程序的优先级总是高于你的。
在这些示例中,我忽略了获得广播机构许可的接收者,部分原因是为了简单和清晰,部分是因为这件事的性质不会真的让你开放任何可能造成伤害的欺骗行为。但是,如果您要包含此内容,则只需将android:permission="android.permission.BROADCAST_SMS"
属性添加到静态选项的开始<receiver>
标记中。对于动态,使用registerReceiver()
方法的四参数重载,将该权限String
作为第三个参数传递,将null
作为第四个参数传递。