SmsManager(短信管理器)
使用 SmsManager (短信管理器)
SmsManager
是 Android 提供的一个系统服务类,用于发送短信(SMS)和管理短信相关操作。它允许开发者发送文本短信、数据短信或多部分短信,适合需要程序化发送消息的场景(如验证码、通知)。结合 Android 动画合集的背景,SmsManager
可用于在动画触发时发送短信(如用户操作后发送确认消息),并搭配动画效果(如发送按钮的旋转动画)。本文将详细介绍 SmsManager
的功能、使用方法、权限要求及注意事项,重点展示发送短信的实现。
SmsManager 的作用与原理
- 作用:
SmsManager
提供发送短信的接口,支持文本短信(Text SMS)和数据短信(Data SMS)。它还可以将长短信拆分为多部分发送,并支持发送状态监听。 - 原理:
SmsManager
通过 Android 的电话模块与底层的短信服务通信,直接调用设备 SIM 卡的短信功能。它不直接处理接收短信(需通过BroadcastReceiver
实现),但支持发送结果的回调。 - 应用场景:
- 发送验证码或通知短信(如注册确认)。
- 触发动画后的消息确认(如按钮点击后发送短信并显示动画)。
- 自动化消息发送(如提醒、警报)。
- 与电话状态(
TelephonyManager
)结合,检查网络后再发送。
权限要求
发送短信需要以下权限,在 AndroidManifest.xml
中声明:
<uses-permission android:name="android.permission.SEND_SMS" />
<!-- 可选:监听发送状态 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
- 动态权限(API 23+):
Manifest.permission.SEND_SMS
:发送短信。Manifest.permission.READ_PHONE_STATE
(可选):获取设备状态以确认 SIM 卡可用。- 使用
ActivityCompat.requestPermissions
请求权限。 - 注意:API 29+(Android 10)对后台发送短信有限制,建议在用户交互时发送。
获取 SmsManager
通过 Context
获取 SmsManager
实例:
import android.telephony.SmsManager;
SmsManager smsManager = SmsManager.getDefault();
常用功能与方法
以下是 SmsManager
的核心方法:
方法/功能 | 描述 | 用法示例 |
---|---|---|
sendTextMessage(String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) | 发送文本短信。 | smsManager.sendTextMessage("1234567890", null, "Hello!", sentIntent, deliveryIntent); |
sendMultipartTextMessage(String destinationAddress, String scAddress, ArrayList parts, ArrayList sentIntents, ArrayList deliveryIntents) | 发送长短信(自动拆分)。 | smsManager.sendMultipartTextMessage("1234567890", null, parts, sentIntents, deliveryIntents); |
divideMessage(String text) | 将长文本拆分为多部分(每部分 160 字符,Unicode 为 70 字符)。 | ArrayList<String> parts = smsManager.divideMessage(longText); |
getDefault() | 获取默认 SmsManager 实例。 | SmsManager smsManager = SmsManager.getDefault(); |
- 参数说明:
destinationAddress
:目标手机号(如 “+1234567890″)。scAddress
:短信中心号码(通常为null
,由 SIM 卡自动设置)。text
:短信内容。sentIntent
:发送状态回调(如成功、失败)。deliveryIntent
:送达状态回调(需运营商支持)。
完整示例(发送短信 + 动画)
以下是一个使用 SmsManager
发送短信的示例,结合属性动画在发送时显示按钮旋转效果,并监听发送状态。
import android.Manifest;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import android.animation.ObjectAnimator;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class SmsAnimationActivity extends AppCompatActivity {
private static final int REQUEST_SMS_PERMISSION = 100;
private static final String SENT_ACTION = "SMS_SENT";
private EditText phoneInput, messageInput;
private Button sendButton;
private ObjectAnimator rotationAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sms_animation);
phoneInput = findViewById(R.id.phone_input);
messageInput = findViewById(R.id.message_input);
sendButton = findViewById(R.id.send_button);
// 初始化动画
rotationAnimator = ObjectAnimator.ofFloat(sendButton, "rotation", 0f, 360f);
rotationAnimator.setDuration(1000);
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
// 注册发送状态广播
registerReceiver(sentReceiver, new IntentFilter(SENT_ACTION));
// 发送按钮点击
sendButton.setOnClickListener(v -> {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SEND_SMS}, REQUEST_SMS_PERMISSION);
} else {
sendSms();
}
});
}
private void sendSms() {
String phoneNumber = phoneInput.getText().toString().trim();
String message = messageInput.getText().toString().trim();
if (phoneNumber.isEmpty() || message.isEmpty()) {
Toast.makeText(this, "Please enter phone number and message", Toast.LENGTH_SHORT).show();
return;
}
try {
SmsManager smsManager = SmsManager.getDefault();
// 创建 PendingIntent 用于发送状态
PendingIntent sentIntent = PendingIntent.getBroadcast(this, 0, new Intent(SENT_ACTION), PendingIntent.FLAG_IMMUTABLE);
// 发送短信
smsManager.sendTextMessage(phoneNumber, null, message, sentIntent, null);
rotationAnimator.start(); // 启动动画
Toast.makeText(this, "Sending SMS...", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Failed to send SMS", Toast.LENGTH_SHORT).show();
rotationAnimator.cancel();
}
}
private final BroadcastReceiver sentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
rotationAnimator.cancel(); // 停止动画
switch (getResultCode()) {
case RESULT_OK:
Toast.makeText(context, "SMS sent successfully", Toast.LENGTH_SHORT).show();
break;
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
Toast.makeText(context, "Failed: Generic error", Toast.LENGTH_SHORT).show();
break;
case SmsManager.RESULT_ERROR_NO_SERVICE:
Toast.makeText(context, "Failed: No service", Toast.LENGTH_SHORT).show();
break;
case SmsManager.RESULT_ERROR_NULL_PDU:
Toast.makeText(context, "Failed: Null PDU", Toast.LENGTH_SHORT).show();
break;
case SmsManager.RESULT_ERROR_RADIO_OFF:
Toast.makeText(context, "Failed: Radio off", Toast.LENGTH_SHORT).show();
break;
}
}
};
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_SMS_PERMISSION && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
sendSms();
} else {
Toast.makeText(this, "SMS permission denied", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(sentReceiver);
rotationAnimator.cancel();
}
}
- 布局 XML(res/layout/activity_sms_animation.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center">
<EditText
android:id="@+id/phone_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter phone number"
android:inputType="phone" />
<EditText
android:id="@+id/message_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter message"
android:inputType="text" />
<Button
android:id="@+id/send_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send SMS" />
</LinearLayout>
- AndroidManifest.xml(添加权限):
<uses-permission android:name="android.permission.SEND_SMS" />
- 说明:
- 权限:动态请求
SEND_SMS
权限。 - 发送短信:通过
sendTextMessage
发送短文本,PendingIntent
监听发送状态。 - 动画:发送时按钮旋转动画(
rotation
),发送完成或失败时停止。 - 状态监听:使用
BroadcastReceiver
监听短信发送结果(成功或失败)。 - 生命周期:在
onDestroy
取消动画和注销广播接收器。
发送长短信
对于超过 160 字符(或 Unicode 70 字符)的消息,使用 divideMessage
和 sendMultipartTextMessage
:
String longMessage = "This is a very long message that exceeds the SMS length limit...";
ArrayList<String> parts = smsManager.divideMessage(longMessage);
ArrayList<PendingIntent> sentIntents = new ArrayList<>();
for (int i = 0; i < parts.size(); i++) {
sentIntents.add(PendingIntent.getBroadcast(this, i, new Intent(SENT_ACTION), PendingIntent.FLAG_IMMUTABLE));
}
smsManager.sendMultipartTextMessage(phoneNumber, null, parts, sentIntents, null);
监听发送状态
通过 PendingIntent
和 BroadcastReceiver
监听发送结果:
- 常见结果代码:
RESULT_OK
:发送成功。SmsManager.RESULT_ERROR_GENERIC_FAILURE
:通用错误(如 SIM 卡不可用)。SmsManager.RESULT_ERROR_NO_SERVICE
:无网络服务。SmsManager.RESULT_ERROR_NULL_PDU
:协议数据单元错误。SmsManager.RESULT_ERROR_RADIO_OFF
:无线电关闭(如飞行模式)。- 送达状态(需运营商支持):
PendingIntent deliveryIntent = PendingIntent.getBroadcast(this, 0, new Intent("SMS_DELIVERED"), PendingIntent.FLAG_IMMUTABLE);
smsManager.sendTextMessage(phoneNumber, null, message, sentIntent, deliveryIntent);
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (getResultCode() == RESULT_OK) {
Toast.makeText(context, "SMS delivered", Toast.LENGTH_SHORT).show();
}
}
}, new IntentFilter("SMS_DELIVERED"));
优缺点
- 优点:
- 简单易用,直接发送短信。
- 支持长短信自动拆分。
- 提供发送和送达状态回调。
- 缺点:
- 需要用户授权(
SEND_SMS
权限)。 - API 29+ 对后台发送限制严格。
- 不支持接收短信(需
BroadcastReceiver
)。 - 依赖 SIM 卡和网络,设备差异可能影响效果。
- 替代方案:
- SMS Retriever API:接收验证码短信(Google Play Services)。
- Third-party APIs:如 Twilio、Nexmo(云短信服务)。
- Jetpack Compose State:结合现代 UI 管理。
注意事项
- 权限:API 23+ 需动态请求
SEND_SMS
,API 29+ 限制后台发送。 - 设备兼容性:检查设备是否支持短信(
getSystemService(Context.TELEPHONY_SERVICE) != null
)。 - 隐私:避免发送敏感信息,遵守 Google Play 短信政策。
- 生命周期:及时注销
BroadcastReceiver
和取消动画。 - 调试:通过 Log 检查发送结果,验证手机号格式和网络状态。
- 费用:发送短信可能产生费用(取决于运营商)。
扩展:Jetpack Compose 中的实现
在 Jetpack Compose 中,SmsManager
可结合 Composable 实现短信发送和动画:
import android.Manifest
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.telephony.SmsManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
@Composable
fun SmsScreen() {
val context = LocalContext.current
var phoneNumber by remember { mutableStateOf("") }
var message by remember { mutableStateOf("") }
var isSending by remember { mutableStateOf(false) }
val rotation by animateFloatAsState(
targetValue = if (isSending) 360f else 0f,
animationSpec = infiniteRepeatable(tween(1000))
)
val smsManager = remember { SmsManager.getDefault() }
val sentReceiver = remember {
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
isSending = false
when (resultCode) {
android.app.Activity.RESULT_OK -> {
// SMS sent successfully
}
SmsManager.RESULT_ERROR_GENERIC_FAILURE -> {
// Handle generic failure
}
SmsManager.RESULT_ERROR_NO_SERVICE -> {
// Handle no service
}
SmsManager.RESULT_ERROR_NULL_PDU -> {
// Handle null PDU
}
SmsManager.RESULT_ERROR_RADIO_OFF -> {
// Handle radio off
}
}
}
}
}
val permissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) {
sendSms(context, smsManager, phoneNumber, message, { isSending = it })
}
}
DisposableEffect(Unit) {
context.registerReceiver(sentReceiver, IntentFilter("SMS_SENT"))
onDispose {
context.unregisterReceiver(sentReceiver)
}
}
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
value = phoneNumber,
onValueChange = { phoneNumber = it },
label = { Text("Phone Number") },
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(8.dp))
TextField(
value = message,
onValueChange = { message = it },
label = { Text("Message") },
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.height(16.dp))
Button(
onClick = {
if (phoneNumber.isEmpty() || message.isEmpty()) {
// Handle empty input
return@Button
}
if (ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) == android.content.pm.PackageManager.PERMISSION_GRANTED) {
sendSms(context, smsManager, phoneNumber, message, { isSending = it })
} else {
permissionLauncher.launch(Manifest.permission.SEND_SMS)
}
},
modifier = Modifier.graphicsLayer(rotationZ = rotation)
) {
Text("Send SMS")
}
}
}
private fun sendSms(context: Context, smsManager: SmsManager, phoneNumber: String, message: String, setSending: (Boolean) -> Unit) {
try {
val sentIntent = PendingIntent.getBroadcast(context, 0, Intent("SMS_SENT"), PendingIntent.FLAG_IMMUTABLE)
smsManager.sendTextMessage(phoneNumber, null, message, sentIntent, null)
setSending(true)
} catch (e: Exception) {
e.printStackTrace()
setSending(false)
}
}
- 说明:
- 使用
remember
缓存SmsManager
和BroadcastReceiver
。 animateFloatAsState
实现按钮旋转动画。rememberLauncherForActivityResult
处理权限请求。DisposableEffect
确保注销广播接收器。- 发送状态通过
BroadcastReceiver
更新 UI。
示例:结合电话状态
结合 TelephonyManager
(前文介绍)检查 SIM 卡状态后再发送短信:
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (telephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY) {
smsManager.sendTextMessage(phoneNumber, null, message, sentIntent, null);
rotationAnimator.start();
} else {
Toast.makeText(this, "No SIM card available", Toast.LENGTH_SHORT).show();
}
如果需要更复杂功能(如多部分短信、送达报告、与 Canvas 绘制短信状态动画)、或继续探讨 Android 动画合集(如过渡动画、Lottie),请告诉我!