之前『Android 实时监听短信』一文中给内部团队做了一个短信拦截功能,而最近团队内部又来了一个自动发送短信的需求,同样通过测试机,请求服务端,当返回有需要发送的短信时,则自动通过本机的 SIM 卡服务发送。
发送短信需要用到 SEND_SMS
权限:
<manifest ...>
<uses-permission android:name="android.permission.SEND_SMS" />
...
</manifest>
不要忘了在代码中动态申请。
发送短信的思路很简单,只需要提供收件人号码、短信内容,就可以发送。
但是有一个需要注意的点,我们平时接发短信时应该都能发现,一些很长的短信,是不能够像我们常用的社交软件(比如微信和 QQ)一样以一条消息发送的,它会被分割成多条短信进行发送,一般每单位短信最多是 140 个英文字符或者是 70 个汉字符,并且按条收费。
不过不用担心自己处理起来会复杂,Android 系统提供了 API,我们直接调用就好。
/**
* 发送短信
*
* @param tel 收件人号码
* @param text 短信内容
*/
private void sendSms(String tel, String text) {
if (TextUtils.isEmpty(tel)) {
return;
}
if (TextUtils.isEmpty(text)) {
return;
}
SmsManager manager = SmsManager.getDefault();
ArrayList<String> messages = manager.divideMessage(text);
for (String message : messages) {
manager.sendTextMessage(tel, null, message, null, null);
}
}
首先是对电话号码和短信内容进行判空,然后获取 SmsManager
实例,并且调用它的 divideMessage()
对短信进行分割,分割后会返回给我们一个存放了短信片段的 ArrayList
对象,我们遍历这个 ArrayList
,对每个短信片段都执行 sendTextMessage()
发送。
我尝试过不调用 divideMessage()
对短信进行分割,直接传入长内容到 sendTextMessage()
方法,发现系统会自动帮我们做内容切割。
sendTextMessage()
有两个重载方法,内部都是调用 sendTextMessageInternal()
去处理:
public final class SmsManager {
public void sendTextMessage(String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
sendTextMessageInternal(
destinationAddress,
scAddress,
text,
sentIntent,
deliveryIntent,
true,
getOpPackageName(),
getAttributionTag(),
0L
);
}
public void sendTextMessage(@NonNull String destinationAddress, @Nullable String scAddress, @NonNull String text, @Nullable PendingIntent sentIntent, @Nullable PendingIntent deliveryIntent, long messageId) {
sendTextMessageInternal(
destinationAddress,
scAddress,
text,
sentIntent,
deliveryIntent,
true,
getOpPackageName(),
getAttributionTag(),
messageId
);
}
}
简单说下这几个参数的作用。destinationAddress
就是收件人号码;scAddress
指短信服务中心号码,如果为 null
就会使用当前默认的短信服务中心,text
就是要发送的内容;sentIntent
是一个 PendingIntent
对象,用于监听短信发送是否成功,如果不为 null
,在短信发送成功或失败时系统会广播此 PendingIntent
;deliveryIntent
也是个 PendingIntent
对象,用于监听短信接收是否成功,如果不为 null
,在将消息传递给收件人时系统会广播此 PendingIntent
;messageId
是唯一标识请求发送的消息 ID,用于记录和诊断,默认为 0
。
上方示例代码中,我对两个 PendingIntent
的参数都传入了 null
,即不监听短信发送和接收结果,但上线过程中的确有遇到发送失败的情况,所以为了更好的排查问题,建议实际开发还是要把这两个监听加上,写个 BroadcastReceiver
判断结果处理即可,并不复杂,这里就不演示了。
短信分割的形式,实际上是 2G 时代的产物,当时大家基本上用的都是小灵通诺基亚,屏幕尺寸也就 3 英寸左右(第一代 Android 手机 HTC G1 为 3.17 英寸),显示的区域小无可厚非,但放到现在的 5G 时代来看,可以说是完全没必要的,现在市面上基本都是 5 英寸以上的大屏手机,短信分割交互非常不友好。
于是我查阅文档,发现系统在 Android 4.4(KitKat,API 19)提供了新的方法,允许我们将长信息以一条的样式发送,所以我们上面的方法可以改为:
/**
* 发送短信
*
* @param tel 收件人号码
* @param text 短信内容
*/
private void sendSms(String tel, String text) {
if (TextUtils.isEmpty(tel)) {
return;
}
if (TextUtils.isEmpty(text)) {
return;
}
SmsManager manager = SmsManager.getDefault();
ArrayList<String> messages = manager.divideMessage(text);
manager.sendMultipartTextMessage(tel, null, messages, null, null);
}
主要是改用了 sendMultipartTextMessage()
方法来发送短信,参数及逻辑与 sendTextMessage()
比较类似,同样是需要对短信进行分割,分割后得到的 ArrayList
对象不需要我们手动遍历发送,直接交给 sendMultipartTextMessage()
方法处理。
这里注意,我上面的描述是以一条的「样式」发送,实际上,运营商的计费规则没有变化,当内容过长时,它依然会对内容进行分割,按分割的条数计费。只不过在短信 App 显示时,它会以一条的样式进行展示。