
本文讲解如何通过状态标记机制防止 android 应用中 sms 被重复发送,重点解决因 handler 消息循环或 ui 逻辑缺陷导致的「短信持续发送」问题,并提供可直接复用的线程安全解决方案。
本文讲解如何通过状态标记机制防止 android 应用中 sms 被重复发送,重点解决因 handler 消息循环或 ui 逻辑缺陷导致的「短信持续发送」问题,并提供可直接复用的线程安全解决方案。
在 Android 开发中,SmsManager.sendTextMessage() 本身是无状态、无去重能力的纯发送接口。若调用逻辑嵌套在周期性回调(如 Handler 的 handleMessage)、定时器(Timer)、传感器监听或 Dialog 关闭事件中,极易因条件判断缺失或状态未持久化,造成短信被反复触发——正如问题中描述的“必须强制关闭 App 才能停止发送”。
根本原因在于:当前代码中 sendSMS() 被置于 handler 的 else 分支内,而该分支可能在 timer.cancel() 前被多次执行(例如 msg.arg1
✅ 正确解法:引入原子性发送标志位(flag),配合恰当的作用域与重置时机。
✅ 推荐实现方案(线程安全 & 易维护)
// 在 Activity / Fragment / Service 类成员变量区声明
private boolean hasSentSMS = false;
private final Object smsLock = new Object(); // 可选:提升多线程场景下的安全性
// 修改 sendSMS 方法(增强健壮性)
private void sendSMS(String address, String time) {
// 防御性检查
if (address == null || time == null ||
sharedPreferences == null ||
context == null) {
Toast.makeText(context, "Missing required data", Toast.LENGTH_SHORT).show();
return;
}
String name = sharedPreferences.getString("pre_key_name", "");
String phoneNum = sharedPreferences.getString("pre_key_phone", "");
// 空号/无效号码保护
if (phoneNum == null || phoneNum.trim().isEmpty()) {
Toast.makeText(context, "Phone number not configured", Toast.LENGTH_SHORT).show();
return;
}
String smsContent = String.format("%s %s in %s falls!", time, name, address);
SmsManager smsManager = SmsManager.getDefault();
try {
smsManager.sendTextMessage(phoneNum, null, smsContent, null, null);
Toast.makeText(context, "Message sent successfully", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(context, "SMS failed: " + e.getMessage(), Toast.LENGTH_LONG).show();
Log.e("SMS", "Send failed", e);
}
}
// 在 Handler 中安全调用
public Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.arg1 > 0) {
countingView.setText(" " + msg.arg1 + context.getString(R.string.fall_contacts));
} else {
if (dialog != null) {
dialog.dismiss();
if (isVibrate) stopVibrate();
stopAlarm();
// ? 关键:加锁 + 状态检查,确保仅执行一次
synchronized (smsLock) {
if (!hasSentSMS) {
sendSMS(locationAddress, locationTime);
hasSentSMS = true;
}
}
return;
}
timer.cancel();
}
}
};⚠️ 注意事项与最佳实践
- 作用域一致性:hasSentSMS 必须声明为类成员变量(而非局部变量),否则每次进入 handleMessage 都会重置为 false;
- 重置时机明确:若业务需要支持「多次跌倒报警」,应在合适时机(如用户手动重启监测、Activity 重建后、或新事件触发前)重置 hasSentSMS = false;
- 权限与兼容性:Android 6.0+ 需动态申请 SEND_SMS 权限;Android 12(API 31)起,非默认短信应用需使用 RoleManager 申请短信角色,否则 sendTextMessage 将静默失败;
- 替代方案建议:对关键告警类短信,推荐改用 PendingIntent + BroadcastReceiver 的异步结果回调(sentIntent/deliveryIntent),可精确感知发送成败,避免“假成功”;
- 测试验证:可在 sendSMS() 开头添加 Log.d("SMS", "Sending... [" + System.currentTimeMillis() + "]");,结合 Logcat 观察是否真只打印一次。
✅ 总结
防止重复发送 SMS 的核心不是“阻止方法调用”,而是控制业务逻辑的执行频次。一个轻量、可靠、易理解的布尔标志位(配合必要同步)即可彻底解决该类问题。比起复杂的状态机或 RxJava 流控,此方案更符合中小型告警类 App 的工程实际,兼顾可读性与稳定性。










