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 字符)的消息,使用 divideMessagesendMultipartTextMessage

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);

监听发送状态

通过 PendingIntentBroadcastReceiver 监听发送结果:

  • 常见结果代码
  • 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 缓存 SmsManagerBroadcastReceiver
  • 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),请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注