在Android(4.4版本之前)中发送和接收短信和彩信

134

我已经学会了如何发送和接收短信。发送短信需要调用SmsManager类的sendTextMessage()sendMultipartTextMessage()方法。接收短信需要在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>

然而,我在想你是否可以以类似的方式发送和接收MMS消息。经过一些研究,博客上提供的许多示例仅传递一个Intent到本机Messaging应用程序。我正在尝试在不离开我的应用程序的情况下发送MMS。似乎没有标准的发送和接收MMS的方法。有人成功实现了吗?
此外,我知道SMS/MMS ContentProvider不是官方Android SDK的一部分,但我认为可能有人能够实现这个。非常感谢任何帮助。
更新
我已经将一个BroadcastReceiver添加到AndroidManifest.xml文件中来接收MMS消息。
<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...
    }
}

根据 android.provider.Telephony 的文档

Broadcast Action: A new text based SMS message has been received by the device. The intent will have the following extra values:

pdus - An Object[] of byte[]s containing the PDUs that make up the message.

The extra values can be extracted using getMessagesFromIntent(android.content.Intent) If a BroadcastReceiver encounters an error while processing this intent it should set the result code appropriately.

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

Broadcast Action: A new data based SMS message has been received by the device. The intent will have the following extra values:

pdus - An Object[] of byte[]s containing the PDUs that make up the message.

The extra values can be extracted using getMessagesFromIntent(android.content.Intent). If a BroadcastReceiver encounters an error while processing this intent it should set the result code appropriately.

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

Broadcast Action: A new WAP PUSH message has been received by the device. The intent will have the following extra values:

transactionId (Integer) - The WAP transaction ID

pduType (Integer) - The WAP PDU type`

header (byte[]) - The header of the message

data (byte[]) - The data payload of the message

contentTypeParameters (HashMap<String,String>) - Any parameters associated with the content type (decoded from the WSP Content-Type header)

If a BroadcastReceiver encounters an error while processing this intent it should set the result code appropriately. The contentTypeParameters extra value is map of content parameters keyed by their names. If any unassigned well-known parameters are encountered, the key of the map will be 'unassigned/0x...', where '...' is the hex value of the unassigned parameter. If a parameter has No-Value the value in the map will be null.

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

更新 #2

我已经找到如何通过PendingIntent传递额外参数给BroadcastReceiver并接收:

安卓PendingIntent的额外参数无法被BroadcastReceiver接收

然而,额外参数被传递给了SendBroadcastReceiver而不是SMSReceiver。我该如何将额外参数传递给SMSReceiver

更新 #3

接收MMS

经过更多的研究,我看到一些建议注册一个ContentObserver来检测content://mms-sms/conversations内容提供程序中的任何更改,从而可以检测到传入的MMS。这是我找到的最接近能够起作用的例子:接收MMS

然而,有一个类型为ServiceController的变量mainActivity。 ServiceController类在哪里实现?是否有其他已注册的ContentObserver的实现?
发送MMS
至于发送MMS,我遇到了这个例子:发送MMS 问题是,我尝试在我的Nexus 4上运行此代码,该设备运行Android v4.2.2,并且我收到以下错误:
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);

显然,在Android 4.2中无法读取APNs

所有使用移动数据执行操作(如发送MMS)且不知道设备上存在的默认APN设置的应用程序的替代方案是什么?

更新#4

发送MMS

我尝试了以下示例:发送MMS

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

您必须将jsoup添加到构建路径中,将jar文件添加到构建路径并导入com.droidprism。要在Android中执行此操作,请首先将jars添加到libs目录中,然后配置项目构建路径以使用已经在libs目录中的jars,然后在构建路径配置上单击“顺序和导出”并选中框中的jars,并将jsoup和droidprism jar移到构建顺序的顶部。

现在我不再遇到SecurityException错误。我正在Nexus 5上测试Android KitKat。运行示例代码后,调用结束后返回200响应代码。

MMResponse mmResponse = sender.send(out, isProxySet, MMSProxy, MMSPort);

然而,我和我尝试发送MMS的人进行了核实。他们表示从未收到过MMS。


你之前看过这个教程吗?http://maximbogatov.wordpress.com/2011/08/13/mms-in-android/ - HaemEternal
3
是的,我尝试拼凑Maxim的答案,但是无法使其生效。这里有很多类都导入了android.provider.telephony,这个库似乎已经被弃用了。 - Etienne Lawlor
而且,阅读了@Sahil的答案后,您也尝试过这个:https://dev59.com/KnA85IYBdhLWcg3wCe9Z#2973016 - HaemEternal
我已经尝试了ContentObserver方法和BroadcastReceiver方法来接收MMS消息,但无论哪种方法,问题都是我的应用程序在内置文本应用程序显示消息之前就会接收到MMS接收通知。因此,我的应用程序会发出警报,然后用户去查看他们的短信应用程序,新消息的图像还没有显示出来。大约3分钟后,短信应用程序将显示图像。如何防止我的应用程序过早地发出警报? - Someone Somewhere
看起来我们可能会面临一些变化:http://android-developers.blogspot.com/2013/10/getting-your-sms-apps-ready-for-kitkat.html - Etienne Lawlor
显示剩余4条评论
6个回答

15

我曾经遇到了你所描述的完全相同的问题(在美国t-mobile上使用Galaxy Nexus),这是因为移动数据关闭了。

在Jelly Bean中,操作如下: 设置 > 数据使用情况 > 移动数据

请注意,在发送或接收MMS之前,我必须先开启移动数据。如果我在移动数据关闭的情况下收到一条MMS,我会收到新消息通知,并且会出现下载按钮,但是如果事先没有开启移动数据,则无法接收到即使在消息接收后再开启移动数据也不行。

由于某种原因,当您的手机运营商为您启用发送和接收MMS的功能时,必须启用移动数据,即使您正在使用Wifi,如果移动数据已启用,您仍将能够接收和发送MMS,即使设备上显示的是Wifi作为您的互联网。

这真的很麻烦,因为如果您没有开启移动数据,消息可能会挂起很久,即使打开移动数据,也可能需要重新启动设备。


同时,您必须知道在后台发送短信和彩信是两个完全不同的事情。 彩信更多地是一种基于互联网的网络服务,因为它需要发送附加项(媒体)与文本。 给定的代码在我测试过的几个设备上运行良好。PS:您可以忽略诺基亚部分。 - Manan Sharma
当我运行这个例子时,在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 is not TYPE_MOBILE_MMS, bail它还说:java.lang.SecurityException: No permission to write APN settings: Neither user 10099 nor current process has android.permission.WRITE_APN_SETTINGS.看起来它无法执行此查询: final Cursor apnCursor = this.context.getContentResolver().query(Uri.withAppendedPath(Carriers.CONTENT_URI, "current"), null, null, null, null);我正在Nexus 4上进行测试。 - Etienne Lawlor
这也是@Sahil提供的相同示例。 - Etienne Lawlor

7

没有官方的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>

这只是启动本机的消息应用程序吗? - Etienne Lawlor
1
对不起,我刚意识到你已经知道如何做了。不过我确实添加了如何接收彩信的内容。 - user1959417
谢谢,最近我一直在实现MMS的BroadcastReceiver的一部分,并且使用了你发布的Intent Filter。我会尽快更新这个问题。 - Etienne Lawlor

4

如果您想在Android 4.0 API 14或更高版本中发送MMS而又没有写入APN设置的权限,您可以使用这个库:从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 你也遇到这个问题了吗? - Etienne Lawlor
你需要将jsoup添加到构建路径中,将jar包添加到构建路径中,并导入com.droidprism.*; 我会编辑答案。在Android中,首先将jar包添加到libs目录中,然后配置项目构建路径以使用已经在libs目录中的jar包,然后在构建路径配置中点击“顺序和导出”,勾选jar包的框并将jsoup和droidprism jar移动到构建顺序的顶部。 - Sam Adamsh
添加Jsoup .jar文件解决了NoClassDefFoundError问题。现在我可以获取APN设置。下一步是弄清楚如何发送MMS。 - Etienne Lawlor

3

我查看了androidbridge.blogspot.com中Nokia实现的评论,似乎许多人在设备上无法正常使用。 - Etienne Lawlor
@toobsco42,所以可能目前还没有对其提供支持。 - Sahil Mahajan Mj

-2

我不理解这些挫折。为什么不只是创建一个过滤此意图的广播接收器呢:

android.provider.Telephony.MMS_RECEIVED

我再仔细检查了一下,你可能需要系统级别的访问权限才能获取此信息(需要越狱手机)。

3
嘿@j2emanue,问题是在接收到此意图后,您如何实际获取MMS的内容?如果MMS包含图像和文本,您如何提取这些组件? - Etienne Lawlor
但是我注意到如果按照我提到的方式进行操作,你可以获得一个字节数组。byte[] data = intent.getByteArrayExtra("data"); 不过很抱歉我不确定如何解析它。 - j2emanue
我能够解析它。但是我只能获取主题、MMS来源和内容位置,其中MMS的内容存储在该位置。然而,此URL不可访问。 - Etienne Lawlor

-3
短信监听器类
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();

    }
}

活动 (Huódòng)
@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();
    }
}

Manifest

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接