Android API 发送短信技术文档
概述
本文档介绍如何使用 Android 系统 API 发送短信。提供了 SmsUtils 工具类和 SmsUtilsExample 示例类,简化短信发送功能的实现。
功能特性
- 简单短信发送
- 带回调结果的短信发送
- 长短信自动分段发送(超过 160 字符)
- 发送状态和送达状态监听
- 运行时权限处理
- 设备支持检测
权限要求
AndroidManifest.xml 配置
xml
<uses-permission android:name="android.permission.SEND_SMS" />
Android 6.0+ 运行时权限
需要在代码中动态申请 SEND_SMS 权限。
SmsUtils 工具类
SmsUtils 是一个 Kotlin object 单例,封装了所有短信发送相关的方法。
方法 1:简单发送短信
功能:最简单的短信发送方法,不关心发送结果。
源码:
kotlin
@Deprecated("建议使用 sendSmsWithCallback 以获取发送结果")
fun sendSms(context: Context, phoneNumber: String, message: String): Boolean {
return try {
val smsManager = SmsManager.getDefault()
smsManager.sendTextMessage(phoneNumber, null, message, null, null)
Log.d(TAG, "短信发送请求已提交: $phoneNumber")
true
} catch (e: Exception) {
Log.e(TAG, "短信发送失败", e)
false
}
}
参数说明:
context: 应用上下文phoneNumber: 目标手机号message: 短信内容
返回值:
true: 发送请求已成功提交false: 发送失败
注意 :此方法已废弃,推荐使用 sendSmsWithCallback。
原理说明:
- 调用
SmsManager.getDefault()获取短信管理器 - 调用
sendTextMessage()方法发送短信 - 最后两个
null参数分别表示不监听发送状态和送达状态
方法 2:带回调的短信发送
功能:发送短信并通过回调函数通知发送结果。
源码:
kotlin
fun sendSmsWithCallback(
context: Context,
phoneNumber: String,
message: String,
onResult: ((success: Boolean, errorMessage: String?) -> Unit)? = null
) {
try {
val smsManager = SmsManager.getDefault()
// 创建发送状态 PendingIntent
val sentIntent = Intent(SMS_SENT_ACTION).let { intent ->
intent.putExtra("phone_number", phoneNumber)
PendingIntent.getBroadcast(
context,
System.currentTimeMillis().toInt(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
// 创建送达状态 PendingIntent
val deliveredIntent = Intent(SMS_DELIVERED_ACTION).let { intent ->
intent.putExtra("phone_number", phoneNumber)
PendingIntent.getBroadcast(
context,
(System.currentTimeMillis() + 1).toInt(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
// 注册广播接收器(建议在 Activity/Fragment 中注册,这里为简化逻辑)
// 实际使用时,应该在发送前注册广播接收器来监听结果
smsManager.sendTextMessage(phoneNumber, null, message, sentIntent, deliveredIntent)
Log.d(TAG, "短信发送请求已提交: $phoneNumber")
onResult?.invoke(true, null)
} catch (e: Exception) {
Log.e(TAG, "短信发送失败: ${e.message}", e)
onResult?.invoke(false, e.message)
}
}
参数说明:
context: 应用上下文phoneNumber: 目标手机号message: 短信内容onResult: 发送结果回调函数
原理说明:
- 创建两个
PendingIntent:一个用于监听发送状态,一个用于监听送达状态 - 使用
SMS_SENT_ACTION和SMS_DELIVERED_ACTION作为广播 action - 通过
putExtra()将手机号存入 Intent,方便后续识别 - 使用
FLAG_UPDATE_CURRENT更新已存在的 PendingIntent - 使用
FLAG_IMMUTABLE(Android 12+ 要求) 标记 PendingIntent 为不可变
方法 3:发送长短信(自动分段)
功能:处理超过 160 字符的长短信,自动分段发送。
源码:
kotlin
fun sendLongSms(
context: Context,
phoneNumber: String,
message: String,
onProgress: ((current: Int, total: Int) -> Unit)? = null,
onResult: ((success: Boolean, errorMessage: String?) -> Unit)? = null
) {
try {
val smsManager = SmsManager.getDefault()
// 分割短信
val parts = smsManager.divideMessage(message)
if (parts.isEmpty()) {
onResult?.invoke(false, "短信内容为空")
return
}
// 如果只有一段,直接发送
if (parts.size == 1) {
sendSmsWithCallback(context, phoneNumber, parts[0], onResult)
return
}
// 多段短信发送
val sentIntents = ArrayList<PendingIntent>()
val deliveredIntents = ArrayList<PendingIntent>()
for (i in parts.indices) {
val sentIntent = Intent(SMS_SENT_ACTION).let { intent ->
intent.putExtra("phone_number", phoneNumber)
intent.putExtra("part_index", i)
intent.putExtra("total_parts", parts.size)
PendingIntent.getBroadcast(
context,
(System.currentTimeMillis() + i).toInt(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
sentIntents.add(sentIntent)
val deliveredIntent = Intent(SMS_DELIVERED_ACTION).let { intent ->
intent.putExtra("phone_number", phoneNumber)
intent.putExtra("part_index", i)
intent.putExtra("total_parts", parts.size)
PendingIntent.getBroadcast(
context,
(System.currentTimeMillis() + i + 100).toInt(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
deliveredIntents.add(deliveredIntent)
onProgress?.invoke(i + 1, parts.size)
}
// 发送多段短信
smsManager.sendMultipartTextMessage(
phoneNumber,
null,
parts,
sentIntents,
deliveredIntents
)
Log.d(TAG, "长短信发送请求已提交: $phoneNumber, 共 ${parts.size} 段")
onResult?.invoke(true, null)
} catch (e: Exception) {
Log.e(TAG, "长短信发送失败: ${e.message}", e)
onResult?.invoke(false, e.message)
}
}
参数说明:
context: 应用上下文phoneNumber: 目标手机号message: 短信内容(可长可短)onProgress: 发送进度回调(当前段/总段数)onResult: 发送结果回调
原理说明:
- 调用
smsManager.divideMessage(message)自动分割短信 - 如果只有一段,调用
sendSmsWithCallback单独处理 - 如果多段,为每段创建独立的
PendingIntent - 使用
part_index和total_parts标记当前段的序号和总段数 - 调用
sendMultipartTextMessage()批量发送多段短信 - 使用不同的 requestCode (
System.currentTimeMillis() + i) 确保 PendingIntent 不冲突
方法 4:设备支持检测
功能:检查设备是否支持发送短信功能。
源码:
kotlin
fun isSmsSupported(context: Context): Boolean {
return try {
SmsManager.getDefault() != null
} catch (e: Exception) {
Log.e(TAG, "设备不支持发送短信", e)
false
}
}
参数说明:
context: 应用上下文
返回值:
true: 设备支持发送短信false: 设备不支持
原理说明:
- 尝试获取
SmsManager实例 - 如果获取失败或抛出异常,说明设备不支持(如平板设备或不支持移动网络的设备)
方法 5:获取短信最大长度
功能:返回单条短信最大字符数。
源码:
kotlin
fun getSmsMaxLength(): Int {
return try {
SmsManager.getDefault() // GSM: 160, CDMA: 70
160 // 默认返回 GSM 标准长度
} catch (e: Exception) {
Log.e(TAG, "获取短信长度限制失败", e)
160
}
}
返回值:
- 通常返回 160 字符(GSM 标准)
- CDMA 网络可能是 70 字符
原理说明:
- GSM 网络标准:单条短信最多 160 字符
- CDMA 网络标准:单条短信最多 70 字符
- 该方法默认返回 160,实际使用时可根据网络类型调整
方法 6:计算短信分段数
功能:根据消息长度计算短信会被分成多少段。
源码:
kotlin
fun calculateSmsParts(message: String): Int {
val maxLength = getSmsMaxLength()
return if (message.length <= maxLength) 1 else (message.length + maxLength - 1) / maxLength
}
参数说明:
message: 短信内容
返回值:
- 段数
原理说明:
- 如果消息长度 ≤ 160,返回 1
- 否则使用公式
(长度 + 159) / 160向上取整 - 例如:161 字符 → (161+159)/160 = 320/160 = 2 段
方法 7:获取意图过滤器
功能:返回用于注册广播接收器的 IntentFilter。
源码:
kotlin
fun getSentIntentFilter(): IntentFilter {
return IntentFilter(SMS_SENT_ACTION)
}
fun getDeliveredIntentFilter(): IntentFilter {
return IntentFilter(SMS_DELIVERED_ACTION)
}
原理说明:
- 用于在 Activity/Fragment 中注册广播接收器
SMS_SENT_ACTION监听发送状态SMS_DELIVERED_ACTION监听送达状态
方法 8:短信发送结果广播接收器
功能:抽象类,用于监听短信发送结果。
源码:
kotlin
abstract class SmsSentReceiver : android.content.BroadcastReceiver() {
abstract fun onResult(
phoneNumber: String,
success: Boolean,
errorCode: Int,
errorMessage: String?
)
override fun onReceive(context: Context?, intent: Intent?) {
val phoneNumber = intent?.getStringExtra("phone_number") ?: ""
val partIndex = intent?.getIntExtra("part_index", 0) ?: 0
val totalParts = intent?.getIntExtra("total_parts", 1) ?: 1
val success = resultCode == android.app.Activity.RESULT_OK
val errorCode = if (!success) resultCode else 0
val errorMessage = getErrorMessage(errorCode)
onResult(phoneNumber, success, errorCode, errorMessage)
}
private fun getErrorMessage(errorCode: Int): String? {
return when (errorCode) {
SmsManager.RESULT_ERROR_GENERIC_FAILURE -> "通用错误"
SmsManager.RESULT_ERROR_RADIO_OFF -> "无线电关闭"
SmsManager.RESULT_ERROR_NULL_PDU -> "PDU 为空"
SmsManager.RESULT_ERROR_NO_SERVICE -> "无服务"
SmsManager.RESULT_ERROR_LIMIT_EXCEEDED -> "超出限制"
else -> null
}
}
}
使用方式:
继承此抽象类并实现 onResult() 方法:
kotlin
val receiver = object : SmsUtils.SmsSentReceiver() {
override fun onResult(
phoneNumber: String,
success: Boolean,
errorCode: Int,
errorMessage: String?
) {
if (success) {
Log.d("SMS", "发送成功: $phoneNumber")
} else {
Log.e("SMS", "发送失败: $errorMessage")
}
}
}
context.registerReceiver(receiver, SmsUtils.getSentIntentFilter())
错误码说明:
RESULT_ERROR_GENERIC_FAILURE: 通用错误RESULT_ERROR_RADIO_OFF: 无线电关闭(飞行模式)RESULT_ERROR_NULL_PDU: PDU 为空RESULT_ERROR_NO_SERVICE: 无服务RESULT_ERROR_LIMIT_EXCEEDED: 超出限制
原理说明:
- 从 Intent 中提取之前存储的
phone_number、part_index、total_parts - 检查
resultCode判断是否成功 resultCode == Activity.RESULT_OK表示成功- 其他值表示失败,通过错误码匹配错误信息
SmsUtilsExample 示例类
示例 1:简单发送短信
功能:最基础的使用示例,不关心发送结果。
源码:
kotlin
fun example1(context: Context) {
val phoneNumber = "183XXXXXXXX"
val message = "这是一条测试短信"
val success = SmsUtils.sendSms(context, phoneNumber, message)
if (success) {
Toast.makeText(context, "短信发送请求已提交", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "短信发送失败", Toast.LENGTH_SHORT).show()
}
}
使用场景:
- 测试环境
- 不关心发送结果的场景
- 快速验证功能
示例 2:发送短信并获取结果
功能:使用回调函数获取发送结果。
源码:
kotlin
fun example2(context: Context) {
val phoneNumber = "183XXXXXXXX"
val message = "这是一条测试短信2"
SmsUtils.sendSmsWithCallback(
context = context,
phoneNumber = phoneNumber,
message = message
) { success, errorMessage ->
if (success) {
Log.d(TAG, "短信发送请求成功")
} else {
Log.e(TAG, "短信发送失败: $errorMessage")
}
}
}
使用场景:
- 需要知道是否提交成功
- 记录日志
- 显示发送状态
示例 3:发送长短信
功能:发送超过 160 字符的长短信,并监听发送进度。
源码:
kotlin
fun example3(context: Context) {
val phoneNumber = "183XXXXXXXX"
val message = """
这是一条很长的短信内容,用来测试分段发送功能。
当短信内容超过160个字符时,SmsUtils会自动将其分割成多条发送。
这样可以确保长内容也能完整送达。
继续添加内容以达到测试目的...
""".trimIndent()
// 计算会被分成多少段
val parts = SmsUtils.calculateSmsParts(message)
Log.d(TAG, "短信将被分成 $parts 段发送")
SmsUtils.sendLongSms(
context = context,
phoneNumber = phoneNumber,
message = message,
onProgress = { current, total ->
Log.d(TAG, "发送进度: $current / $total")
},
onResult = { success, errorMessage ->
if (success) {
Log.d(TAG, "长短信发送请求成功")
} else {
Log.e(TAG, "长短信发送失败: $errorMessage")
}
}
)
}
使用场景:
- 发送验证码(可能较长)
- 发送通知消息
- 需要知道分段进度的场景
示例 4:在 Activity 中完整使用(包括权限申请和结果监听)
功能:完整的 Activity 集成示例,包括运行时权限申请、广播接收器注册和注销。
权限申请和发送源码:
kotlin
fun requestAndSendSms(
activity: Activity,
phoneNumber: String,
message: String,
onResult: ((success: Boolean, errorMessage: String?) -> Unit)? = null
) {
// 检查权限
if (ContextCompat.checkSelfPermission(
activity,
Manifest.permission.SEND_SMS
) == PackageManager.PERMISSION_GRANTED
) {
// 已有权限,直接发送
SmsUtils.sendSmsWithCallback(
activity,
phoneNumber,
message,
onResult
)
} else {
// 申请权限
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.SEND_SMS),
REQUEST_SEND_SMS_PERMISSION
)
// 保存发送参数,在权限结果回调中使用
savePendingSms(activity, phoneNumber, message, onResult)
}
}
处理权限结果源码:
kotlin
fun handlePermissionResult(
requestCode: Int,
grantResults: IntArray,
onPermissionResult: ((granted: Boolean) -> Unit)? = null
) {
if (requestCode == REQUEST_SEND_SMS_PERMISSION) {
val granted = grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED
onPermissionResult?.invoke(granted)
}
}
注册广播接收器源码:
kotlin
fun registerSmsReceiver(
context: Context,
onResult: (phoneNumber: String, success: Boolean, errorCode: Int, errorMessage: String?) -> Unit
): BroadcastReceiver {
val receiver = object : SmsUtils.SmsSentReceiver() {
override fun onResult(
phoneNumber: String,
success: Boolean,
errorCode: Int,
errorMessage: String?
) {
onResult(phoneNumber, success, errorCode, errorMessage)
}
}
context.registerReceiver(receiver, SmsUtils.getSentIntentFilter())
return receiver
}
注销广播接收器源码:
kotlin
fun unregisterSmsReceiver(context: Context, receiver: BroadcastReceiver) {
try {
context.unregisterReceiver(receiver)
} catch (e: IllegalArgumentException) {
Log.w(TAG, "广播接收器未注册")
}
}
保存待发送短信源码(权限申请期间使用):
kotlin
private fun savePendingSms(
context: Context,
phoneNumber: String,
message: String,
onResult: ((success: Boolean, errorMessage: String?) -> Unit)?
) {
// 使用 SharedPreferences 保存
val prefs = context.getSharedPreferences("sms_pending", Context.MODE_PRIVATE)
prefs.edit()
.putString("phone_number", phoneNumber)
.putString("message", message)
.putBoolean("has_callback", onResult != null)
.apply()
Log.d(TAG, "已保存待发送短信,等待权限结果")
}
获取并清除待发送短信源码:
kotlin
fun getPendingSms(context: Context): Triple<String, String, Boolean>? {
val prefs = context.getSharedPreferences("sms_pending", Context.MODE_PRIVATE)
val phone = prefs.getString("phone_number", null)
val msg = prefs.getString("message", null)
val hasCallback = prefs.getBoolean("has_callback", false)
return if (phone != null && msg != null) {
// 清除保存的数据
prefs.edit().clear().apply()
Triple(phone, msg, hasCallback)
} else {
null
}
}
完整的 Activity 使用示例:
kotlin
class MainActivity : AppCompatActivity() {
private var smsReceiver: BroadcastReceiver? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 检查设备支持
checkDeviceSupport()
// 注册短信结果监听
smsReceiver = SmsUtilsExample.registerSmsReceiver(this) { phone, success, code, error ->
runOnUiThread {
if (success) {
Toast.makeText(this, "发送成功: $phone", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "发送失败: $error", Toast.LENGTH_SHORT).show()
}
}
}
// 发送短信(带权限检查)
sendSmsWithPermissionCheck(this, "183XXXXXXXX", "测试短信")
}
private fun sendSmsWithPermissionCheck(activity: Activity, phone: String, msg: String) {
SmsUtilsExample.requestAndSendSms(activity, phone, msg) { success, error ->
runOnUiThread {
Toast.makeText(this, if (success) "发送成功" else "发送失败: $error", Toast.LENGTH_SHORT).show()
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
SmsUtilsExample.handlePermissionResult(requestCode, grantResults) { granted ->
if (granted) {
// 权限已授予,发送待发送的短信
val pending = SmsUtilsExample.getPendingSms(this)
pending?.let { (phone, msg, _) ->
SmsUtils.sendSmsWithCallback(this, phone, msg) { success, error ->
Log.d("SMS", if (success) "发送成功" else "发送失败: $error")
}
}
} else {
Toast.makeText(this, "需要短信权限才能发送", Toast.LENGTH_SHORT).show()
}
}
}
private fun checkDeviceSupport() {
val supported = SmsUtils.isSmsSupported(this)
val maxLength = SmsUtils.getSmsMaxLength()
Log.d("SMS", "设备支持发送短信: $supported")
Log.d("SMS", "单条短信最大长度: $maxLength 字符")
}
override fun onDestroy() {
smsReceiver?.let { SmsUtilsExample.unregisterSmsReceiver(this, it) }
super.onDestroy()
}
}
示例 5:检查设备支持
功能:检查设备是否支持发送短信。
源码:
kotlin
fun checkDeviceSupport(context: Context) {
val supported = SmsUtils.isSmsSupported(context)
val maxLength = SmsUtils.getSmsMaxLength()
Log.d(TAG, "设备支持发送短信: $supported")
Log.d(TAG, "单条短信最大长度: $maxLength 字符")
}
使用场景:
- 应用启动时检测
- 适配不支持短信的设备(如某些平板)
核心概念解释
1. SmsManager
Android 系统提供的短信管理器,用于发送短信、彩信等。
kotlin
val smsManager = SmsManager.getDefault()
2. PendingIntent
PendingIntent 是一种特殊的 Intent,可以让其他应用(如系统)代表你的应用执行操作。
在短信发送中用于:
- 监听发送状态(
SMS_SENT_ACTION) - 监听送达状态(
SMS_DELIVERED_ACTION)
3. BroadcastReceiver
广播接收器,用于接收系统发送的广播。
短信发送成功/失败时,系统会发送广播,通过注册 BroadcastReceiver 可以接收这些广播。
4. FLAG_IMMUTABLE
Android 12+ 要求创建 PendingIntent 时必须指定可变性标志:
kotlin
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
FLAG_UPDATE_CURRENT: 如果已存在相同 requestCode 的 PendingIntent,则更新其 ExtrasFLAG_IMMUTABLE: 标记 PendingIntent 为不可变,提高安全性
注意事项
1. 权限申请
- Android 6.0(API 23)及以上版本需要在运行时申请
SEND_SMS权限 - 必须在
AndroidManifest.xml中声明权限
2. 发送结果
sendSms方法返回true仅表示发送请求已提交,不代表短信已送达- 如需监听实际发送和送达状态,需要使用
sendSmsWithCallback并注册广播接收器
3. 长短信处理
- 单条短信最大长度通常为 160 字符(GSM 标准)
- 超过限制的短信会被自动分段发送
- 长短信会消耗多条短信配额
4. 设备兼容性
- 部分平板设备或不支持移动网络的设备可能无法发送短信
- 使用
isSmsSupported()检查设备支持情况
5. 广播接收器
- 必须在不需要时注销广播接收器(通常在
onDestroy()中) - 使用
PendingIntent.FLAG_IMMUTABLE标志以适应 Android 12+ 的安全要求
6. 短信费用
- 发送短信会产生运营商费用
- 应用商店可能审核要求用户确认后才能发送短信
最佳实践
- 始终检查权限:发送前先检查权限,未授权则申请
- 使用回调监听结果:不要依赖简单的返回值,使用回调获取真实状态
- 处理长短信 :使用
sendLongSms自动处理分段发送 - 检查设备支持 :使用
isSmsSupported()避免在不支持的设备上报错 - 及时注销监听器:在 Activity/Fragment 生命周期结束时注销广播接收器
- 提供用户反馈:使用 Toast 或 UI 显示发送状态
常见问题
Q: 为什么发送请求成功但收不到短信?
A: sendSms 返回成功仅表示请求已提交到系统,实际发送可能因以下原因失败:
- 设备无信号或无服务
- 目标号码错误
- 运营商网络问题
- 短信被拦截
建议使用 sendSmsWithCallback 并注册广播接收器监听实际发送结果。
Q: 如何获取短信送达确认?
A: 使用 sendSmsWithCallback 时,第二个 PendingIntent 会接收送达确认。需要额外注册监听 SMS_DELIVERED_ACTION 的广播接收器。
Q: 长短信为什么分成多段?
A: 短信协议限制单条短信最大长度(GSM 为 160 字符)。超过此长度的内容会被分成多条发送。使用 sendLongSms 可自动处理此过程。
Q: Android 12+ 报错 FLAG_IMMUTABLE 相关错误?
A: 代码中已正确使用 PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,请确保使用最新版本的示例代码。
更新日志
v2.0 (当前版本)
- 完善文档,添加每个方法的源码解释
- 增加详细的使用示例
- 添加核心概念解释
v1.0
- 基础短信发送功能
- 长短信分段发送
- 发送结果回调
- 设备支持检测
- 运行时权限处理