我想通过让Android设备发送短信到自己,并自动检查是否已接收该短信来验证设备的电话号码。我应该如何做?
首先,这将需要两个权限;一个用于发送短信,另一个用于接收短信。以下内容应该放在您的AndroidManifest.xml文件中,在<manifest>
标签之间,但位于<application>
标签之外。
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
这两个权限都属于危险权限,因此如果您的应用程序在Marshmallow(API级别23)或更高版本上运行,并且具有23+的targetSdkVersion
,则需要相应地处理它们。有关如何在运行时请求这些权限的信息可以在此开发人员页面找到。
您需要的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"
操作注册一个带有IntentFilter
的BroadcastReceiver
。这个接收器可以在清单中静态注册,也可以在运行时动态注册到Context
上。
在清单中静态注册接收器类将允许你的应用程序在接收之前被杀死时接收传入的消息。然而,要将结果放在所需位置可能需要一些额外的工作。在<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
上动态注册接收器实例可能在代码方面更容易管理,因为接收器类可以被设置为注册它的任何组件的内部类,因此可以直接访问该组件的成员。但是,这种方法可能不像静态注册那样可靠,因为一些不同的事情可能会阻止接收器获取广播;例如,您的应用程序进程被杀死,用户导航到注册的Activity
之外等。
SmsReceiver receiver = new SmsReceiver();
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
registerReceiver(receiver, filter);
请务必在适当的时候取消注册接收器。
在接收器的onReceive()
方法中,实际消息作为一个byte
数组的数组附加到Intent
作为额外信息。解码细节因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
。要在接收器中重新组装完整的消息,您需要将每个byte[]
解码为SmsMessage
,然后连接消息正文。为此,需要使用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
的方式与以前相同,但是可以使用getUserData()
来获取消息的byte[]
,而不是getMessageBody()
。
在KitKat之前,应用程序需要自己编写发送的短信,因此如果您不想记录任何信息,则可以在这些版本上停止执行该操作。
可以拦截传入的消息,并在主短信应用程序接收和写入它们之前中止其广播。为此,将过滤器的优先级设置为最高,并在接收器中调用abortBroadcast()
方法。在静态选项中,将android:priority="999"
属性添加到开头的<intent-filter>
标记中。动态地,IntentFilter#setPriority()
方法也可以实现相同的效果。
这并不完全可靠,因为另一个应用程序始终可能具有比您更高的优先级。
在这些示例中,我忽略了使用广播者权限来保护接收器,部分原因是为了简单和清晰,另一部分原因是这种方式的性质不会让您暴露于任何可能造成损害的欺骗行为中。但是,如果您想包括这个功能,则只需要在静态选项的开头的<receiver>
标记中添加android:permission="android.permission.BROADCAST_SMS"
属性。对于动态选项,请使用registerReceiver()
方法的四参数重载,将此权限String
作为第三个参数传递,并将第四个参数设为null
。
android:enabled="false"
应该改为 true,而不是 false 吗? - Mohsen EmamiPackageManager#setComponentEnabledSetting()
方法可用于根据需要启用和禁用此<receiver>
。”如果您需要始终启用它,则可以删除该属性设置。 - Mike M.