在 Android 中发送和接收 SMS 和 MMS(Kit Kat Android 4.4 之前)

Posted

技术标签:

【中文标题】在 Android 中发送和接收 SMS 和 MMS(Kit Kat Android 4.4 之前)【英文标题】:Sending and Receiving SMS and MMS in Android (pre Kit Kat Android 4.4) 【发布时间】:2013-01-05 08:15:29 【问题描述】:

我已经弄清楚如何发送和接收 SMS 消息。要发送 SMS 消息,我必须调用 SmsManager 类的 sendTextMessage()sendMultipartTextMessage() 方法。要接收 SMS 消息,我必须在 androidMainfest.xml 文件中注册一个接收器。然后我不得不重写BroadcastReceiveronReceive() 方法。我在下面提供了示例。

MainActivity.java

public class MainActivity extends Activity 
    private static String SENT = "SMS_SENT";
    private static String DELIVERED = "SMS_DELIVERED";
    private static int MAX_SMS_MESSAGE_LENGTH = 160;

    // ---sends an SMS message to another device---
    public static void sendSMS(String phoneNumber, String message) 

        PendingIntent piSent = PendingIntent.getBroadcast(mContext, 0, new Intent(SENT), 0);
        PendingIntent piDelivered = PendingIntent.getBroadcast(mContext, 0,new Intent(DELIVERED), 0);
        SmsManager smsManager = SmsManager.getDefault();

        int length = message.length();          
        if(length > MAX_SMS_MESSAGE_LENGTH) 
            ArrayList<String> messagelist = smsManager.divideMessage(message);          
            smsManager.sendMultipartTextMessage(phoneNumber, null, messagelist, null, null);
        
        else
            smsManager.sendTextMessage(phoneNumber, null, message, piSent, piDelivered);
        
    

    //More methods of MainActivity ...

SMSReceiver.java

public class SMSReceiver extends BroadcastReceiver 
    private final String DEBUG_TAG = getClass().getSimpleName().toString();
    private static final String ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
    private Context mContext;
    private Intent mIntent;

    // Retrieve SMS
    public void onReceive(Context context, Intent intent) 
        mContext = context;
        mIntent = intent;

        String action = intent.getAction();

        if(action.equals(ACTION_SMS_RECEIVED))

            String address, str = "";
            int contactId = -1;

            SmsMessage[] msgs = getMessagesFromIntent(mIntent);
            if (msgs != null) 
                for (int i = 0; i < msgs.length; i++) 
                    address = msgs[i].getOriginatingAddress();
                    contactId = ContactsUtils.getContactId(mContext, address, "address");
                    str += msgs[i].getMessageBody().toString();
                    str += "\n";
                
               

            if(contactId != -1)
                showNotification(contactId, str);
            

            // ---send a broadcast intent to update the SMS received in the
            // activity---
            Intent broadcastIntent = new Intent();
            broadcastIntent.setAction("SMS_RECEIVED_ACTION");
            broadcastIntent.putExtra("sms", str);
            context.sendBroadcast(broadcastIntent);
        

    

    public static SmsMessage[] getMessagesFromIntent(Intent intent) 
        Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
        byte[][] pduObjs = new byte[messages.length][];

        for (int i = 0; i < messages.length; i++) 
            pduObjs[i] = (byte[]) messages[i];
        
        byte[][] pdus = new byte[pduObjs.length][];
        int pduCount = pdus.length;
        SmsMessage[] msgs = new SmsMessage[pduCount];
        for (int i = 0; i < pduCount; i++) 
            pdus[i] = pduObjs[i];
            msgs[i] = SmsMessage.createFromPdu(pdus[i]);
        
        return msgs;
    

    /**
    * The notification is the icon and associated expanded entry in the status
    * bar.
    */
    protected void showNotification(int contactId, String message) 
        //Display notification...
    

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.myexample"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="16"
        android:targetSdkVersion="17" />

    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.WRITE_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_MMS" />
    <uses-permission android:name="android.permission.WRITE" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:debuggable="true"
        android:icon="@drawable/ic_launcher_icon"
        android:label="@string/app_name" >

        <activity
            //Main activity...
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            //Activity 2 ...
        </activity>
        //More acitivies ...

        // SMS Receiver
        <receiver android:name="com.myexample.receivers.SMSReceiver" >
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>

    </application>
</manifest>

但是,我想知道您是否可以以类似的方式发送和接收彩信。经过一些研究,博客上提供的许多示例只是将Intent 传递给本机消息传递应用程序。我正在尝试在不离开我的应用程序的情况下发送彩信。似乎没有发送和接收彩信的标准方式。有人让这个工作吗?

另外,我知道 SMS/MMS ContentProvider 不是官方 Android SDK 的一部分,但我想可能有人能够实现这一点。非常感谢任何帮助。

更新

我已将BroadcastReceiver 添加到AndroidManifest.xml 文件以接收彩信

<receiver android:name="com.sendit.receivers.MMSReceiver" >
    <intent-filter>
        <action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />

        <data android:mimeType="application/vnd.wap.mms-message" />
    </intent-filter>
</receiver>

在 MMSReceiver 类中,onReceive() 方法只能获取发送消息的电话号码。您如何从 MMS 中获取其他重要信息,例如媒体附件(图像/音频/视频)的文件路径或 MMS 中的文本?

MMSReceiver.java

public class MMSReceiver extends BroadcastReceiver 
    private final String DEBUG_TAG = getClass().getSimpleName().toString();
    private static final String ACTION_MMS_RECEIVED = "android.provider.Telephony.WAP_PUSH_RECEIVED";
    private static final String MMS_DATA_TYPE = "application/vnd.wap.mms-message";

     // Retrieve MMS
    public void onReceive(Context context, Intent intent) 

        String action = intent.getAction();
        String type = intent.getType();

        if(action.equals(ACTION_MMS_RECEIVED) && type.equals(MMS_DATA_TYPE))

            Bundle bundle = intent.getExtras();

            Log.d(DEBUG_TAG, "bundle " + bundle);
            SmsMessage[] msgs = null;
            String str = "";
            int contactId = -1;
            String address;

            if (bundle != null) 

                byte[] buffer = bundle.getByteArray("data");
                Log.d(DEBUG_TAG, "buffer " + buffer);
                String incomingNumber = new String(buffer);
                int indx = incomingNumber.indexOf("/TYPE");
                if(indx>0 && (indx-15)>0)
                    int newIndx = indx - 15;
                    incomingNumber = incomingNumber.substring(newIndx, indx);
                    indx = incomingNumber.indexOf("+");
                    if(indx>0)
                        incomingNumber = incomingNumber.substring(indx);
                        Log.d(DEBUG_TAG, "Mobile Number: " + incomingNumber);
                    
                

                int transactionId = bundle.getInt("transactionId");
                Log.d(DEBUG_TAG, "transactionId " + transactionId);

                int pduType = bundle.getInt("pduType");
                Log.d(DEBUG_TAG, "pduType " + pduType);

                byte[] buffer2 = bundle.getByteArray("header");      
                String header = new String(buffer2);
                Log.d(DEBUG_TAG, "header " + header);

                if(contactId != -1)
                    showNotification(contactId, str);
                

                // ---send a broadcast intent to update the MMS received in the
                // activity---
                Intent broadcastIntent = new Intent();
                broadcastIntent.setAction("MMS_RECEIVED_ACTION");
                broadcastIntent.putExtra("mms", str);
                context.sendBroadcast(broadcastIntent);

            
        

    

    /**
    * The notification is the icon and associated expanded entry in the status
    * bar.
    */
    protected void showNotification(int contactId, String message) 
        //Display notification...
    

根据Documentation of android.provider.Telephony:

广播操作:设备已收到一条新的基于文本的 SMS 消息。意图将具有以下额外值:

pdus - Object[] 中的 byte[]s 包含构成消息的 PDU。

可以使用getMessagesFromIntent(android.content.Intent) 提取额外的值 如果 BroadcastReceiver 在处理这个意图时遇到错误,它应该适当地设置结果代码。

 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
 public static final String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED";

广播操作:设备已收到新的基于数据的 SMS 消息。意图将具有以下额外值:

pdus - Object[]byte[]s 包含构成消息的 PDU。

可以使用 getMessagesFromIntent(android.content.Intent) 提取额外的值。 如果 BroadcastReceiver 在处理这个意图时遇到错误,它应该适当地设置结果代码。

@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";

广播操作:设备已收到新的 WAP PUSH 消息。意图将具有以下额外值:

transactionId (Integer) - WAP 交易 ID

pduType (Integer) - WAP PDU 类型`

header (byte[]) - 邮件标题

data (byte[]) - 消息的数据负载

contentTypeParameters (HashMap&lt;String,String&gt;) - 与内容类型相关的任何参数(从 WSP Content-Type 标头解码)

如果广播接收器在处理此意图时遇到错误,它应该适当地设置结果代码。 contentTypeParameters 额外值是由其名称键入的内容参数的映射。 如果遇到任何未分配的已知参数,则映射的键将为“未分配/0x...”,其中“...”是未分配参数的十六进制值。如果参数没有值,则映射中的值将为空。

@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String WAP_PUSH_RECEIVED_ACTION = "android.provider.Telephony.WAP_PUSH_RECEIVED";

更新 #2

我已经想出如何在PendingIntent 中传递额外内容以由BroadcastReceiver 接收: Android PendingIntent extras, not received by BroadcastReceiver

但是,额外的被传递给 SendBroadcastReceiver 而不是 SMSReceiver。我怎样才能将额外信息传递给 SMSReceiver

更新 #3

接收彩信

所以在做了更多研究之后,我看到了一些注册ContentObserver 的建议。这样您就可以检测到content://mms-sms/conversations 内容提供程序何时发生任何更改,从而使您能够检测传入的彩信。这是我发现的最接近的示例:Receiving MMS

但是,有一个mainActivity 类型为ServiceController 的变量。 ServiceController 类在哪里实现?注册的ContentObserver还有其他实现吗?

发送彩信

关于发送彩信,我遇到过这个例子:Send MMS

问题是我尝试在 Android v4.2.2 上的 Nexus 4 上运行此代码,但收到此错误:

java.lang.SecurityException: No permission to write APN settings: Neither user 10099 nor current process has android.permission.WRITE_APN_SETTINGS.

APNHelper类的getMMSApns()方法中查询Carriers ContentProvider后抛出错误。

final Cursor apnCursor = this.context.getContentResolver().query(Uri.withAppendedPath(Carriers.CONTENT_URI, "current"), null, null, null, null);

显然你不能read APNs in Android 4.2

对于所有使用移动数据执行操作(如发送彩信)但不知道设备中存在的默认 APN 设置的应用程序,有什么替代方案?

更新 #4

发送彩信

我已经尝试过以下示例:Send MMS

正如@Sam 在他的回答中建议的那样:

You have to add jsoup to the build path, the jar to the build path and import com.droidprism.*; To do that in android, add the jars to the libs directory first, then configure the project build path to use the jars already in the libs directory, then on the build path config click order and export and check the boxes of the jars and move jsoup and droidprism jar to the top of the build order.

所以现在我不再收到 SecurityException 错误。我现在正在 Android KitKat 上的 Nexus 5 上进行测试。运行示例代码后,它会在调用

后给我一个 200 响应代码
MMResponse mmResponse = sender.send(out, isProxySet, MMSProxy, MMSPort);

但是,我与尝试向其发送彩信的人进行了核实。他们说他们从未收到过彩信。

【问题讨论】:

你以前看过这个教程吗? maximbogatov.wordpress.com/2011/08/13/mms-in-android 是的,我有。我尝试将 Maxim 的答案拼凑在一起,但无法让它发挥作用。那里有很多导入 android.provider.telephony 的类,这似乎已被弃用。 大概,在阅读了@Sahil 的回答之后,你也尝试过这个:***.com/questions/2972845/… 我不确定如何将这个答案拼凑在一起,尽管它看起来与@Sahil 的答案非常相似。 嗨@toobsco42 你能找到你上面提到的所有查询的解决方案吗? 【参考方案1】:

我遇到了与您在上面描述的完全相同的问题(t-mobile USA 上的 Galaxy Nexus),这是因为移动数据已关闭。

在果冻豆中是: 设置 > 数据使用 > 移动数据

请注意,在发送彩信或接收彩信之前,我必须打开移动数据。如果我收到关闭移动数据的彩信,我将收到一条新消息的通知,我将收到带有下载按钮的消息。但是如果我之前没有打开移动数据,则不会收到传入的彩信附件。即使我收到消息后打开它。

由于某些原因,当您的电话提供商允许您发送和接收彩信时,您必须启用移动数据,即使您使用的是 Wifi,如果启用了移动数据,您将能够接收和发送彩信,即使 Wifi 在您的设备上显示为您的互联网。

这真的很痛苦,就像你没有打开它一样,即使打开移动数据,消息也会挂很多,并且可能需要重新启动设备。

【讨论】:

另外你必须知道,在后台发送短信和彩信是完全不同的两件事。彩信更像是一种基于互联网的网络服务,因为它需要发送带有文本的附加项目(媒体)。给定的代码在我测试过的一些设备上运行良好。 ps:诺基亚部分可以忽略。 当我运行这个例子时,在 LogCat 中它会打印:02-24 13:32:40.872: V/SendMMSActivity(5686): TYPE_MOBILE_MMS not connected, bail 02-24 13:32:40.882: V /SendMMSActivity(5686):类型不是 TYPE_MOBILE_MMS,保释它还说:java.lang.SecurityException:没有写入 APN 设置的权限:用户 10099 和当前进程都没有 android.permission.WRITE_APN_SETTINGS。看起来它无法执行此查询: final Cursor apnCursor = this.context.getContentResolver().query(Uri.withAppendedPath(Carriers.CONTENT_URI, "current"), null, null, null, null);我在 Nexus 4 上进行测试。 这也是@Sahil 提供的例子。【参考方案2】:

没有官方的 api 支持,这意味着它没有向公众记录,并且库可能随时更改。我知道你不想离开应用程序,但这里是你这样做的目的,目的是让其他人想知道。

public void sendData(int num)
    String fileString = "..."; //put the location of the file here
    Intent mmsIntent = new Intent(Intent.ACTION_SEND);
    mmsIntent.putExtra("sms_body", "text");
    mmsIntent.putExtra("address", num);
    mmsIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(fileString)));
    mmsIntent.setType("image/jpeg");
    startActivity(Intent.createChooser(mmsIntent, "Send"));


我还没有完全弄清楚如何做一些事情,比如跟踪消息的传递,但这应该可以发送。

您可以像收到短信一样收到收到彩信的提醒。接收器上的意图过滤器应如下所示。

<intent-filter>
    <action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />
    <data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>

【讨论】:

这不是启动原生消息应用吗? 是的,很抱歉。我刚刚意识到你已经知道该怎么做。我确实添加了如何接收彩信。 谢谢,我最近一直在实现部分彩信BroadcastReceiver,并使用了您发布的Intent Filter。我会尽快更新这个问题。【参考方案3】:

要在没有写入 apn 设置权限的情况下发送 Android 4.0 api 14 或更高版本的彩信,您可以使用this library: 从android中检索mnc和mcc代码,然后调用

Carrier c = Carrier.getCarrier(mcc, mnc);
if (c != null) 
    APN a = c.getAPN();
    if (a != null) 
        String mmsc = a.mmsc;
        String mmsproxy = a.proxy; //"" if none
        int mmsport = a.port; //0 if none
    

要使用它,将Jsoup和droid prism jar添加到构建路径,并导入com.droidprism.*;

【讨论】:

嘿@Sam,我将 .jar 文件添加到我的项目中,但在实例化 Carrier 对象的行中出现此错误:java.lang.NoClassDefFoundError: com.droidprism.Carrier 是发生在你身上吗? 没有。您必须将 jsoup 添加到构建路径,将 jar 添加到构建路径并导入 com.droidprism.*;我会编辑答案。要在 android 中做到这一点,首先将 jars 添加到 libs 目录,然后配置项目构建路径以使用 libs 目录中已有的 jars,然后在构建路径配置中单击 order 和 export 并选中 jars 的框并移动jsoup 和 droidprism jar 到构建顺序的顶部。 添加 Jsoup .jar 解决了 NoClassDefFoundError。我现在可以获取 APN 设置。下一步是弄清楚如何发送彩信。【参考方案4】:

我不认为有任何 sdk 支持在 android 中发送彩信。 Look here 至少我还没有找到。但是一个人声称拥有它。看看这篇文章。

Send MMS from My application in android

【讨论】:

我查看了诺基亚实现的 androidbridge.blogspot.com 帖子中的 cmets,看起来很多人都无法在他们的设备上使用它。 @toobsco42 所以,可能还不支持它。【参考方案5】:

我不明白这些挫败感。为什么不只制作一个过滤此意图的广播接收器:

android.provider.Telephony.MMS_RECEIVED

我进一步检查了一下,您可能需要系统级访问权限才能获得此(根电话)。

【讨论】:

嘿@j2emanue,问题是在你收到这个意图之后,你是如何真正得到彩信的内容的?如果彩信中包含图片和文字,如何提取这些组件。 但我注意到如果按照我提到的方式进行操作,您可以获得额外的字节数组.... byte[] data = intent.getByteArrayExtra("data");抱歉,我不确定如何解析它。 我能够解析它。但我能得到的只是主题、mms 来自谁以及存储 mms 内容的内容位置。但是无法访问此网址。【参考方案6】:

SmsListener类

public class SmsListener extends BroadcastReceiver 

static final String ACTION =
        "android.provider.Telephony.SMS_RECEIVED";

@Override
public void onReceive(Context context, Intent intent) 

    Log.e("RECEIVED", ":-:-" + "SMS_ARRIVED");

    // TODO Auto-generated method stub
    if (intent.getAction().equals(ACTION)) 

        Log.e("RECEIVED", ":-" + "SMS_ARRIVED");

        StringBuilder buf = new StringBuilder();
        Bundle bundle = intent.getExtras();
        if (bundle != null) 

            Object[] pdus = (Object[]) bundle.get("pdus");

            SmsMessage[] messages = new SmsMessage[pdus.length];
            SmsMessage message = null;

            for (int i = 0; i < messages.length; i++) 

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 
                    String format = bundle.getString("format");
                    messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i], format);
                 else 
                    messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
                

                message = messages[i];
                buf.append("Received SMS from  ");
                buf.append(message.getDisplayOriginatingAddress());
                buf.append(" - ");
                buf.append(message.getDisplayMessageBody());
            

            MainActivity inst = MainActivity.instance();
            inst.updateList(message.getDisplayOriginatingAddress(),message.getDisplayMessageBody());

        

        Log.e("RECEIVED:", ":" + buf.toString());

        Toast.makeText(context, "RECEIVED SMS FROM :" + buf.toString(), Toast.LENGTH_LONG).show();

    

活动

@Override
public void onStart() 
    super.onStart();
    inst = this;


public static MainActivity instance() 
    return inst;


public void updateList(final String msg_from, String msg_body) 

    tvMessage.setText(msg_from + " :- " + msg_body);

    sendSMSMessage(msg_from, msg_body);



protected void sendSMSMessage(String phoneNo, String message) 

    try 
        SmsManager smsManager = SmsManager.getDefault();
        smsManager.sendTextMessage(phoneNo, null, message, null, null);
        Toast.makeText(getApplicationContext(), "SMS sent.", Toast.LENGTH_LONG).show();
     catch (Exception e) 
        Toast.makeText(getApplicationContext(), "SMS faild, please try again.", Toast.LENGTH_LONG).show();
        e.printStackTrace();
    

清单

<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.SEND_SMS"/>

<receiver android:name=".SmsListener">
        <intent-filter>
            <action android:name="android.provider.Telephony.SMS_RECEIVED" />
        </intent-filter>
    </receiver>

【讨论】:

以上是关于在 Android 中发送和接收 SMS 和 MMS(Kit Kat Android 4.4 之前)的主要内容,如果未能解决你的问题,请参考以下文章

Android Studio:按特定编号删除发送和接收的短信

Android 上的 0 类 SMS(闪存 SMS)

特定号码的SMS消息未显示在其他Android设备上

如何在没有 SMS_READ 或 Android 中的类似权限的情况下发送短信并接收结果

Android,在服务当前不可用时使用 Telephony.SMS_RECEIVED 操作接收广播

有没有办法拦截 Android SMS 发送以转发到不同的传输?