引言
本文通过运行两个Android模拟器,介绍在Android中如何实现短信服务(SMS,short message service)的功能。通过这个例子,我想带给大家的是:更加熟悉之前介绍过的Android应用程序的概念及技术细节,且通过实例调度大家的兴趣。我 之所以选择SMS为例子,主要原因是SMS已经非常成熟了,从中可以发掘更多的信息和技术细节,而且我相信大部分人发短信比打电话多。
本文的主要内容如下:
- 1、温故知新
- 2、准备工作:SMS涉及的主要类SmsManager
- 3、简单的SMS发送程序
- 3.1、运行SMS程序给另一个android模拟器发短
- 4、SMS增强(一)
- 5、SMS增强(二)
- 6、SMS接收程序(下篇)
- 7、emulator工具(下篇)
- 8、…
1、温故知新
广播接收者:一个广播接收者是这样一个组件,它不做什么事,仅是接受广播公告并作出相应的反应。许多广播源自于系统代码, 例如公告时区的改变、电池电量低、已采取图片、用户改变了语言偏好。应用程序也可以发起广播,例如为了他其他程序知道某些数据已经下载到设备且他们可以使 用这些数据
BroadcastReceiver类:是接受sendBroadcast()发送的意图(intents)的基类。可以用Context.registerReceiver()动态地注册这个类的实例,或者通过AndroidManifest.xml中<receiver>标签静态发布。
广播接收者不显示一个用户界面。然而,它们启动一个活动去响应收到的信息,或者他们可能使用NotificationManager去通知用户。通知可以使用多种方式获得用户的注意——闪烁的背光、振动设备、播放声音等等。典型的是放在一个持久的图标在状态栏,用户可以打开获取信息。
2、准备工作:SMS涉及的主要类SmsManager
实现SMS主要用到SmsManager类,该类继承自java.lang.Object类,下面我们介绍一下该类的主要成员。
公有方法:
- ArrayList<String> divideMessage(String text)
当短信超过SMS消息的最大长度时,将短信分割为几块。
参数:text——初始的消息,不能为空
返回值:有序的ArrayList<String>,可以重新组合为初始的消息 - static SmsManager getDefault()
获取SmsManager的默认实例。
返回值:SmsManager的默认实例 - void SendDataMessage(String destinationAddress, String scAddress, short destinationPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent)
发送一个基于SMS的数据到指定的应用程序端口。
参数:
1)、destinationAddress——消息的目标地址
2)、scAddress——服务中心的地址or为空使用当前默认的SMSC 3)destinationPort——消息的目标端口号
4)、data——消息的主体,即消息要发送的数据
5)、sentIntent—— 如果不为空,当消息成功发送或失败这个PendingIntent就广播。结果代码是Activity.RESULT_OK表示成功,或 RESULT_ERROR_GENERIC_FAILURE、RESULT_ERROR_RADIO_OFF、 RESULT_ERROR_NULL_PDU之一表示错误。对应RESULT_ERROR_GENERIC_FAILURE,sentIntent可能包括额外的“错误代码”包含一个无线电广播技术特定的值,通常只在修复故障时有用。
每一个基于SMS的应用程序控制检测sentIntent。如果sentIntent是空,调用者将检测所有未知的应用程序,这将导致在检测的时候发送较小数量的SMS。
6)、deliveryIntent——如果不为空,当消息成功传送到接收者这个PendingIntent就广播。
异常:如果destinationAddress或data是空时,抛出IllegalArgumentException异常。 - void sendMultipartTextMessage(String destinationAddress, String scAddress, ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliverIntents)
发送一个基于SMS的多部分文本,调用者应用已经通过调用divideMessage(String text)将消息分割成正确的大小。
参数:
1)、destinationAddress——消息的目标地址
2)、scAddress——服务中心的地址or为空使用当前默认的SMSC
3)、parts——有序的ArrayList<String>,可以重新组合为初始的消息
4)、sentIntents——跟SendDataMessage方法中一样,只不过这里的是一组PendingIntent
5)、deliverIntents——跟SendDataMessage方法中一样,只不过这里的是一组PendingIntent
异常:如果destinationAddress或data是空时,抛出IllegalArgumentException异常。 - void sendTextMessage(String destinationAddress, String scAddress, String text, PendingIntent sentIntent, PendingIntent deliveryIntent)
发送一个基于SMS的文本。参数的意义和异常前面的已存在的一样,不再累述。
常量:
- public static final int RESULT_ERROR_GENERIC_FAILURE
表示普通错误,值为1(0x00000001) - public static final int RESULT_ERROR_NO_SERVICE
表示服务当前不可用,值为4 (0x00000004) - public static final int RESULT_ERROR_NULL_PDU
表示没有提供pdu,值为3 (0x00000003) - public static final int RESULT_ERROR_RADIO_OFF
表示无线广播被明确地关闭,值为2 (0x00000002) - public static final int STATUS_ON_ICC_FREE
表示自由空间,值为0 (0x00000000) - public static final int STATUS_ON_ICC_READ
表示接收且已读,值为1 (0x00000001) - public static final int STATUS_ON_ICC_SENT
表示存储且已发送,值为5 (0x00000005) - public static final int STATUS_ON_ICC_UNREAD
表示接收但未读,值为3 (0x00000003) - public static final int STATUS_ON_ICC_UNSENT
表示存储但为发送,值为7 (0x00000007)
3、简单的SMS发送程序
1)、首先,编辑布局文件res/layout/main.xml,达到我们想要的结果,界面如下:
图1、程序运行界面
对应的xml代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/txtPhoneNo"/> <!-- text's value define in res/values/strings.xml --> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/edtPhoneNo"/> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/txtContent"/> <EditText android:layout_width="fill_parent" android:layout_height="wrap_content" android:minLines="3" android:id="@+id/edtContent"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/btnText" android:id="@+id/btnSend"/> </LinearLayout>
相应的要在res/values/strings.xm中添加上面定义的视图的text的值,如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="txtPhoneNo">Please input phone NO:</string> <string name="txtContent">Please input SMS\'s content:</string> <string name="btnText">send!</string> <string name="app_name">SMS</string> </resources>
2)、做完这些准备工作之后,我么要开始编写代码实现简单的短信发送了。
通过第一步我们构建好界面之后,现在要在上面的基础上编写业务逻辑了。大致过程为:在java源文件中,获取用户在edtPhoneNo中 输入的电话号码,edtContent中输入要发送的内容;然后点击btnSend按钮发送短信,要达到这个目的我们要设置btnSend的 OnClickListener以达到当点击它触发发送短信的功能,而且要发送短信就要用到我们前面介绍的SmsManager类提供的方法接口。
设置btnSend的OnClickListener的代码如下:
btnSend.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { String phoneNo = edtPhoneNo.getText().toString(); String message = edtContent.getText().toString(); if (phoneNo.length() > 0 && message.length() > 0){ //call sendSMS to send message to phoneNo sendSMS(phoneNo, message); } else Toast.makeText(getBaseContext(), "Please enter both phone number and message.", Toast.LENGTH_SHORT).show(); } });
private void sendSMS(String phoneNumber, String message) { // ---sends an SMS message to another device--- SmsManager sms = SmsManager.getDefault(); PendingIntent pi = PendingIntent.getActivity(this, 0, new Intent(this,TextMessage.class), 0); //if message's length more than 70 , //then call divideMessage to dive message into several part //and call sendTextMessage() //else direct call sendTextMessage() if (message.length() > 70) { ArrayList<String> msgs = sms.divideMessage(message); for (String msg : msgs) { sms.sendTextMessage(phoneNumber, null, msg, pi, null); } } else { sms.sendTextMessage(phoneNumber, null, message, pi, null); } Toast.makeText(TextMessage.this, "短信发送完成", Toast.LENGTH_LONG).show(); }
如果你已经看了第2节介绍的SmsManager类的介绍,代 码应该很好理解。在这里要说明的是,sendTextMessage方法中的第4个和第5个参数PendingIntent设为null,这样的话不能根 据短信发出之后的状态做相应的事情,如短信发送失败后的提醒、接收者成功接收后的回执……完整的流程源码如下:
package skynet.com.cnblogs.www; import java.util.ArrayList; import android.app.Activity; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; import android.telephony.SmsManager; import android.view.View; import android.widget.*; public class TextMessage extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btnSend = (Button) findViewById(R.id.btnSend); edtPhoneNo = (EditText) findViewById(R.id.edtPhoneNo); edtContent = (EditText) findViewById(R.id.edtContent); btnSend.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { String phoneNo = edtPhoneNo.getText().toString(); String message = edtContent.getText().toString(); if (phoneNo.length() > 0 && message.length() > 0) { // call sendSMS to send message to phoneNo sendSMS(phoneNo, message); } else Toast.makeText(getBaseContext(), "Please enter both phone number and message.", Toast.LENGTH_SHORT).show(); } }); } private Button btnSend; private EditText edtPhoneNo; private EditText edtContent; private void sendSMS(String phoneNumber, String message) { // ---sends an SMS message to another device--- SmsManager sms = SmsManager.getDefault(); PendingIntent pi = PendingIntent.getActivity(this, 0, new Intent(this, TextMessage.class), 0); // if message's length more than 70 , // then call divideMessage to dive message into several part ,and call // sendTextMessage() // else direct call sendTextMessage() if (message.length() > 70) { ArrayList<String> msgs = sms.divideMessage(message); for (String msg : msgs) { sms.sendTextMessage(phoneNumber, null, msg, pi, null); } } else { sms.sendTextMessage(phoneNumber, null, message, pi, null); } Toast.makeText(TextMessage.this, "短信发送完成", Toast.LENGTH_LONG).show(); } }
3)运行前,还要在清单文件AndroidManifest.xml中加入允许发送短信的权限:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="skynet.com.cnblogs.www" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".TextMessage" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.SEND_SMS"/> </manifest>
3.1、运行SMS程序给另一个android模拟器发短信
运行上面我们编写的TextMessage程序,另外在Windows的命令行下切换到tools目录下,并输入emulator –data smsReceiver,我的如下:
这样就会启动一个android模拟器,如下所示:(注意它的编号:5556,就是用这个编号与它通信的)
图2、通过emulator启动一个android模拟器
通过我们TextMessage程序启动的android模拟器,编写短信:
图3、TextMessage程序个5556模拟器发短信
点击发送之后,通过命令行启动的5556号android模拟器会收到我们刚才发送的短信,如下所示:
图4、收到短信的提示
tips:
如果通过命令行的emulator启动android模拟器提示“NO DNS servers found!”,这时我们发的短信模拟器是收不到的。
- 在Windows下,如果电脑没有介入网络,即找不DNS服务器的话会出现这种情况!
-
在Mac下,如果提示这个警告的话,可以这样解决:检查你是否有 /etc/resolv.conf文件,如果没有的话,通过下面的命令行
ln -s /private/var/run/resolv.conf /etc/resolv.conf可以解决。
4、SMS增强(一)
上面我们实现了一个简单的SMS程序,下面我们要对它进行增强!你肯定已经注意到了,我们上面的SMS程序的sendTextMessage方法中的第4个和第5个参数PendingIntent设为null,即sentIntent和deliveryIntent。
第4个参数-sendIntent,当消息成功发送或发送失败都将被触发。广播接收者的结果码,Activity.RESULT_OK表示 成功,或RESULT_ERROR_GENERIC_FAILURE、RESULT_ERROR_RADIO_OFF、 RESULT_ERROR_NULL_PDU之一表示错误。对应RESULT_ERROR_GENERIC_FAILURE,sentIntent可能包括额外的“错误代码”包含一个无线电广播技术特定的值,通常只在修复故障时有用。第5个参数-deliveryIntent,仅当目标接收到你的SMS消息才触发。
为了跟踪发出的短信的状态,实现和注册Broadcast Receiver(广播接收者)监听传递给sendTextMessage方法的参数Pending Intents。下面我们就实现和注册这个广播接收者:
String SENT_SMS_ACTION="SENT_SMS_ACTION"; String DELIVERED_SMS_ACTION="DELIVERED_SMS_ACTION";//create the sentIntent parameter Intent sentIntent=new Intent(SENT_SMS_ACTION); PendingIntent sentPI=PendingIntent.getBroadcast( this, 0, sentIntent, 0);//create the deilverIntent parameter Intent deliverIntent=new Intent(DELIVERED_SMS_ACTION); PendingIntent deliverPI=PendingIntent.getBroadcast( this, 0, deliverIntent, 0);//register the Broadcast Receivers registerReceiver(new BroadcastReceiver(){ @Override public void onReceive(Context _context,Intent _intent) { switch(getResultCode()){ case Activity.RESULT_OK: Toast.makeText(getBaseContext(), "SMS sent success actions", Toast.LENGTH_SHORT).show(); break; case SmsManager.RESULT_ERROR_GENERIC_FAILURE: Toast.makeText(getBaseContext(), "SMS generic failure actions", Toast.LENGTH_SHORT).show(); break; case SmsManager.RESULT_ERROR_RADIO_OFF: Toast.makeText(getBaseContext(), "SMS radio off failure actions", Toast.LENGTH_SHORT).show(); break; case SmsManager.RESULT_ERROR_NULL_PDU: Toast.makeText(getBaseContext(), "SMS null PDU failure actions", Toast.LENGTH_SHORT).show(); break; } } },new IntentFilter(SENT_SMS_ACTION)); registerReceiver(new BroadcastReceiver(){ @Override public void onReceive(Context _context,Intent _intent) { Toast.makeText(getBaseContext(), "SMS delivered actions", Toast.LENGTH_SHORT).show(); } },new IntentFilter(DELIVERED_SMS_ACTION));
在基本完成了要做的工作,接下来要做的就是将sendTextMessage的第4个和第5个参数改为sentPI、deliverPI,这样工作基本完成,修改后的sendSMS方法如下:
private void sendSMS(String phoneNumber, String message) { // ---sends an SMS message to another device--- SmsManager sms = SmsManager.getDefault(); String SENT_SMS_ACTION = "SENT_SMS_ACTION"; String DELIVERED_SMS_ACTION = "DELIVERED_SMS_ACTION"; // create the sentIntent parameter Intent sentIntent = new Intent(SENT_SMS_ACTION); PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, sentIntent, 0); // create the deilverIntent parameter Intent deliverIntent = new Intent(DELIVERED_SMS_ACTION); PendingIntent deliverPI = PendingIntent.getBroadcast(this, 0, deliverIntent, 0); // register the Broadcast Receivers registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context _context, Intent _intent) { switch (getResultCode()) { case Activity.RESULT_OK: Toast.makeText(getBaseContext(), "SMS sent success actions", Toast.LENGTH_SHORT) .show(); break; case SmsManager.RESULT_ERROR_GENERIC_FAILURE: Toast.makeText(getBaseContext(), "SMS generic failure actions", Toast.LENGTH_SHORT) .show(); break; case SmsManager.RESULT_ERROR_RADIO_OFF: Toast .makeText(getBaseContext(), "SMS radio off failure actions", Toast.LENGTH_SHORT).show(); break; case SmsManager.RESULT_ERROR_NULL_PDU: Toast.makeText(getBaseContext(), "SMS null PDU failure actions", Toast.LENGTH_SHORT) .show(); break; } } }, new IntentFilter(SENT_SMS_ACTION)); registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context _context, Intent _intent) { Toast.makeText(getBaseContext(), "SMS delivered actions", Toast.LENGTH_SHORT).show(); } }, new IntentFilter(DELIVERED_SMS_ACTION)); // if message's length more than 70 , // then call divideMessage to dive message into several part ,and call // sendTextMessage() // else direct call sendTextMessage() if (message.length() > 70) { ArrayList<String> msgs = sms.divideMessage(message); for (String msg : msgs) { sms.sendTextMessage(phoneNumber, null, msg, sentPI, deliverPI); } } else { sms.sendTextMessage(phoneNumber, null, message, sentPI, deliverPI); } }
运行之后的,发送短信成功的话就可以看到如下界面:
图5、增强SMS(一)
6、温故知新之Intent
此系列前面简单地接受过意图(Intent),这里再次简单介绍一下,在短信接收程序和使用Intent发送SMS中我们要用到。android应 用程序的三大组件——Activities、Services、Broadcast Receiver,通过消息触发,这个消息就称作意图(Intent)。下面以Acitvity为例,介绍一下Intent。Android用 Intent这个特殊的类实现在Activity与Activity之间的切换。Intent类用于描述应用的功能。在Intent的描述结构中,有两个最重要的部分:动作和动作对应的数据。典型的动作类型有MAIN、VIEW、PICK、EDIT等,我们在短信接收程序中就用到从广播意图中提取动作类型并判断是否是"android.provider.Telephony.SMS_RECEIVED",进而作深一步的处理。而动作对应的数据则以URI的形式表示。例如,要查看一个人的联系方式,需要创建一个动作为VIEW的Intent,以及表示这个人的URI。
通过解析各种Intent,从一个屏幕导航到另一个屏幕是很简单的。当向前导航时,Activity将会调用startActivity("指定一个Intent")方法。然后,系统会在所有已安装的应用程序中定义的IntentFilter中查找,找到最匹配的Intent对应的Activity。新的Activity接收到指定的Intent的通知后,开始运行。当startActivity()方法被调用时,将触发解析指定Intent的动作,该机制提供了两个关键的好处:
- Activity能够重复利用从其他组件中以Intent形式产生的请求。
- Activity可以在任何时候被具有相同IntentFilter的新的Activity取代。
7、准备工作:SmsMessage类
顾名思义,SmsMessage类是一个表示短信的类,为了更好地了解Android的短信机制及以后更好地编写短信相关程序,这里介绍一下该类的公有方法和常量,及嵌套枚举、类成员。
公有方法:
- public static int[] calculateLength (CharSequence msgBody, boolean use7bitOnly)
参数:
msgBody-要封装的消息、use7bitOnly-如果为TRUE,不是广播特定7-比特编码的部分字符被认为是单个空字符;如果为FALSE,且msgBody包含非7-比特可编码字符,长度计算使用16-比特编码。
返回值:
返回一个4个元素的int数组,int[0]表示要求使用的SMS数量、int[1]表示编码单元已使用的数量、int[2]表示剩余到下个消息的编码单元数量、int[3]表示编码单元大小的指示器。 - public static int[] calculateLength (String messageBody, boolean use7bitOnly)
参数和返回值跟上面类似 - public static SmsMessage createFromPdu (byte[] pdu)
从原始的PDU(protocol description units)创建一个SmsMessage。这个方法很重要,在我们编写短信接收程序要用到,它从我们接收到的广播意图中获取的字节创建SmsMessage。 - public String getDisplayMessageBody()
返回短信消息的主体,或者Email消息主体(如果这个消息来自一个Email网关)。如果消息主体不可用,返回null。这个方法也很重要,在我们编写短信接收程序也要用到。 - public String getDisplayOriginatingAddress ()
返回信息来源地址,或Email地址(如果消息来自Email网关)。如果消息主体不可用,返回null。这个方法在来电显示,短信接收程序中经常用到。 - public String getEmailBody ()
如果isEmail为TRUE,即是邮件,返回通过网关发送Email的地址,否则返回null。 - public int getIndexOnIcc ()
返回消息记录在ICC上的索引(从1开始的) - public String getMessageBody ()
以一个String返回消息的主体,如果它存在且是基于文本的。 - public SmsMessage.MessageClass getMessageClass ()
返回消息的类。 - public String getOriginatingAddress ()
以String返回SMS信息的来电地址,或不可用时为null。 - public byte[] getPdu ()
返回消息的原始PDU数据。 - public int getProtocolIdentifier ()
获取协议标识符。 - public String getPseudoSubject ()
- public String getServiceCenterAddress ()
返回转播消息SMS服务中心的地址,如果没有的话为null。 - public int getStatus ()
GSM:为一个SMS-STATUS-REPORT消息,它返回状态报告的status字段。这个字段表示之前提交的SMS消息的状态。
CDMA:为不影响来自GSM的状态码,值移动到31-16比特。这个值由一个error类(25-16比特)和一个状态码(23-16比特)组成。
如果是0,表示之前发送的消息已经被收到。 - public int getStatusOnIcc ()
返回消息在ICC上的状态(已读、未读、已发送、未发送)。有下面的几个值:SmsManager.STATUS_ON_ICC_FREE、 SmsManager.STATUS_ON_ICC_READ、SmsManager.STATUS_ON_ICC_UNREAD、 SmsManager.STATUS_ON_ICC_SEND、SmsManager.STATUS_ON_ICC_UNSENT这几个值在上篇的SmsManager类介绍有讲到。 - public static SmsMessage.SubmitPdu getSubmitPdu (
String scAddress, String destinationAddress,
short destinationPort, byte[] data,
boolean statusReportRequested)
参数:scAddress - 服务中心的地址(Sercvice Centre address,为null即使用默认的)、destinationAddress - 消息的目的地址、destinationPort- 发送消息到目的的端口号、data - 消息数据。
返回值:一个包含编码了的SC地址(如果指定了的话)和消息内容的SubmitPdu,否则返回null,如果编码错误。 - public static SmsMessage.SubmitPdu getSubmitPdu (
String scAddress, String destinationAddress,
String message, boolean statusReportRequested)
和上面类似。 - public static int getTPLayerLengthForPDU (String pdu)
返回指定SMS-SUBMIT PDU的TP-Layer-Length,长度单位是字节而不是十六进字符。 - public long getTimestampMillis ()
以currentTimeMillis()格式返回服务中心时间戳。 - public byte[] getUserData ()
返回用户数据减去用户数据头部(如果有的话) - public boolean isCphsMwiMessage ()
判断是否是CPHS MWI消息 - public boolean isEmail ()
判断是否是Email,如果消息来自一个Email网关且Email发送者(sender)、主题(subject)、解析主体(parsed body)可用,则返回TRUE。 - public boolean isMWIClearMessage ()
判断消息是否是一个CPHS 语音邮件或消息等待MWI清除(clear)消息。 - public boolean isMWISetMessage ()
判断消息是否是一个CPHS 语音邮件或消息等待MWI设置(set)消息。 - public boolean isMwiDontStore ()
如果消息是一个“Message Waiting Indication Group:Discard Message”通知且不应该保存,则返回TRUE,否则返回FALSE。 - public boolean isReplace ()
判断是否是一个“replace short message”SMS - public boolean isReplyPathPresent ()
判断消息的TP-Reply-Path位是否在消息中设置了。 - public boolean isStatusReportMessage ()
判断是否是一个SMS-STATUS-REPORT消息。
常量值:
- public static final int ENCODING_16BIT :值为3(0x00000003)
- public static final int ENCODING_8BIT :值为2 (0x00000002)
- public static final int ENCODING_UNKNOWN :值为0 (0x00000000) ,用户数据编码单元的大小。
- public static final int MAX_USER_DATA_BYTES :值为140 (0x0000008c),表示每个消息的最大负载字节数。
- public static final int MAX_USER_DATA_BYTES_WITH_HEADER :134 (0x00000086),如果一个用户数据有头部,该值表示它的最大负载字节数,该值假定头部仅包含CONCATENATED_8_BIT_REFENENCE元素。
- public static final int MAX_USER_DATA_SEPTETS :值为160 (0x000000a0) ,表示每个消息的最大负载septets数。
- public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER :值为153 (0x00000099),如果存在用户数据头部,则该值表示最大负载septets数该值假定头部仅包含CONCATENATED_8_BIT_REFENENCE元素。
嵌套枚举成员SmsMessage.MessageClass的枚举值:
- public static final SmsMessage.MessageClass CLASS_0
- public static final SmsMessage.MessageClass CLASS_1
- public static final SmsMessage.MessageClass CLASS_2
- public static final SmsMessage.MessageClass CLASS_3
- public static final SmsMessage.MessageClass CLASS_UNKNOWN
嵌套枚举成员SmsMessage.MessageClass的公有方法:
- public static SmsMessage.MessageClass valueOf (String name):返回值的字符串的值
- public static final MessageClass[] values ():返回MessageClass的值数组
嵌套类成员SmsMessage.SubmitPdu的字段:
- public byte[] encodedMessage :编码了的消息
- public byte[] encodedScAddress :编码的服务中心地址
嵌套类成员SmsMessage.SubmitPdu的公有方法:
- public String toString ()
返回一个包含简单的、可读的这个对象的描述字符串。鼓励子类去重写这个方法,并提供实现对象的类型和数据。默认实现简单地连接类名、@、十六进制表示的对 象哈希码,即下面的形式: getClass().getName() + '@' + Integer.toHexString(hashCode())
8、SMS接收程序
当一个SMS消息被接收时,一个新的广播意图由android.provider.Telepony.SMS_RECEIVED动作触发。注意:这 个一个字符串字面量(string literal),但是SDK当前并没有包括这个字符串的引用,因此当要在应用程序中使用它时必须自己显示的指定它。现在我们就开始构建一个SMS接收程 序:
1)、跟SMS发送程序类似,要在清单文件AndroidManifest.xml中指定权限允许接收SMS:<uses-permission android:name="android.permission.RECEIVER_SMS"/>
为了能够回发短信,还应该加上发送的权限。
2)、应用程序监听SMS意图广播,SMS广播意图包含了到来的SMS细节。我们要从其中提取出SmsMessage对象,这样就要用到pdu键提取一个SMS PDUs数组(protocol description units—封装了一个SMS消息和它的元数据),每个元素表示一个SMS消息。为了将每个PDU byte数组转化为一个SMS消息对象,需要调用SmsMessage.createFromPdu。
每个SmsMessage包含SMS消息的详细信息,包括起始地址(电话号码)、时间戳、消息体。下面编写一个接收短信的类SmsReceiver代码如下:
package skynet.com.conblogs.www; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.telephony.SmsManager; import android.telephony.SmsMessage; import android.widget.Toast; public class SmsReceiver extends BroadcastReceiver { @Override public void onReceive(Context _context, Intent _intent) { if (_intent.getAction().equals(SMS_RECEIVER)) { SmsManager sms = SmsManager.getDefault(); Bundle bundle = _intent.getExtras(); if (bundle != null) { Object[] pdus = (Object[]) bundle.get("pdus"); SmsMessage[] messages = new SmsMessage[pdus.length]; for (int i = 0; i < pdus.length; i++) messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); for (SmsMessage message : messages) { String msg = message.getMessageBody(); String to = message.getOriginatingAddress(); if (msg.toLowerCase().startsWith(queryString)) { String out = msg.substring(queryString.length()); sms.sendTextMessage(to, null, out, null, null); Toast.makeText(_context, "success", Toast.LENGTH_LONG).show(); } } } } } private static final String queryString="@echo"; private static final String SMS_RECEIVER= "android.provider.Telephony.SMS_RECEIVED"; }
上面代码的功能是从接收到的广播意图中提取来电号码、短信内容,然后将短信加上@echo头部回发给来电号码,并在屏幕上显示一个Toast消息提示成功。
9、另一种发送短信的方式:使用Intent
上篇我们使用SmsManager类实现了发送SMS的功能,且并没有用到内置的客户端。实际上,我们很少这样做,自己在应用程序中去完全实现一个完整的SMS客户端。相反我们会去利用它,将需要发送的内容和目的手机号传递给内置的SMS客户端,然后发送。
下面我就向大家介绍如何利用Intent实现利用将我们的东西传递给内置SMS客户端发送我们SMS。为了实现这个功能,就要用到startActivity("指定一个Intent")方法,且指定Intent的动作为Intent.ACTION_SENDTO,用sms:指定目标手机号,用sms_body指定信息内容。java源文件如下所示:
package skynet.com.cnblogs.www; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class TextMessage extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btnSend = (Button) findViewById(R.id.btnSend); edtPhoneNo = (EditText) findViewById(R.id.edtPhoneNo); edtContent = (EditText) findViewById(R.id.edtContent); btnSend.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { String phoneNo = edtPhoneNo.getText().toString(); String message = edtContent.getText().toString(); if (phoneNo.length() > 0 && message.length() > 0) { Intent smsIntent=new Intent(Intent.ACTION_SENDTO, Uri.parse("sms:"+edtPhoneNo.getText().toString())); smsIntent.putExtra("sms_body", edtContent.getText().toString()); TextMessage.this.startActivity(smsIntent); } else Toast.makeText(getBaseContext(), "Please enter both phone number and message.", Toast.LENGTH_SHORT).show(); } }); } private Button btnSend; private EditText edtPhoneNo; private EditText edtContent; }
注意代码中的红色粗体部分,就是实现这个功能的核心代码!布局文件maim.xml和值文件string.xml跟上篇中的一样,这里不再累述。运行结果如下图:
图2、程序主界面
点击send按钮之后,转到内置的SMS客户端并且将我们输入的值传入了,如下图:
图3、内容传至内置SMS客户端
发送之后,5556号android模拟器会收到我们发送的消息,如下图:
图5、发送之后5556号android模拟器收到消息
10、增强SMS为MMS
我们讲了这么多,都还只是实现了简单的发生SMS的功能,如果我们想发送图片、音频怎么办(⊙o⊙)?不急,现在我们就将第9节介绍的SMS发送程序改造为MMS。
我们可以附加一个文件到我们的消息做为附件发送,用Intent.EXTRA_STREAM和附件资源的Uri做为参数调用putExtra()方法,附加到信息。并设置Intent的类型为mime-type。要注意的是:内置的MMS并不包括一个ACTION_SENDTO动作的Intent接收器,我们需要使用的动作类型是ACTION_SEND,并且目标手机号不在是使用sms:而是address。主要代码如下:
// Get the URI of a piece of media to attach. Uri attached_Uri = Uri.parse("content://media/external/images/media/1"); // Create a new MMS intent Intent mmsIntent = new Intent(Intent.ACTION_SEND, attached_Uri); mmsIntent.putExtra("sms_body", edtContent.getText().toString()); mmsIntent.putExtra("address", edtPhoneNo.getText().toString()); mmsIntent.putExtra(Intent.EXTRA_STREAM, attached_Uri); mmsIntent.setType("image/png");startActivity(mmsIntent);
将这段代码替换第9节中的红色粗体代码,就完成而来一个MMS的构建。
width="336" height="280" frameborder="0" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" scrolling="no" allowfullscreen="true" id="aswift_1" name="aswift_1" style="display: block; margin: auto; left: 0px; position: absolute; top: 0px;"> |
id="iframeu2102391_1" src="http://pos.baidu.com/acom?sz=600x282&rdid=2102391&dc=2&di=u2102391&dri=1&dis=0&dai=4&ps=16958x844&coa=at%3D3%26rsi0%3D600%26rsi1%3D282%26pat%3D6%26tn%3DbaiduCustNativeAD%26rss1%3D%2523FFFFFF%26conBW%3D1%26adp%3D1%26ptt%3D1%26ptc%3D%2525E7%25258C%25259C%2525E4%2525BD%2525A0%2525E6%252584%25259F%2525E5%252585%2525B4%2525E8%2525B6%2525A3%26ptFS%3D%26ptFC%3D%2523000000%26ptBC%3D%2523F2F2F2%26titFF%3D%2525E5%2525BE%2525AE%2525E8%2525BD%2525AF%2525E9%25259B%252585%2525E9%2525BB%252591%26titFS%3D%26rss2%3D%2523000000%26titSU%3D0%26ptbg%3D90%26piw%3D0%26pih%3D0%26ptp%3D0&dcb=BAIDU_UNION_define&dtm=BAIDU_DUP_SETJSONADSLOT&dvi=0.0&dci=-1&dpt=none&tsr=0&tpr=1457187074828&ti=Android%20%E7%9F%AD%E4%BF%A1%E7%9A%84%E6%94%B6%E5%8F%91%E5%8F%8A%E5%9C%A8android%E6%A8%A1%E6%8B%9F%E5%99%A8%E4%B9%8B%E9%97%B4%E5%AE%9E%E8%B7%B5-android100%E5%AD%A6%E4%B9%A0%E7%BD%91&ari=1&dbv=2&drs=1&pcs=1349x557&pss=1349x17132&cfv=0&cpl=41&chi=1&cce=true&cec=GBK&tlm=1429874782<u=http%3A%2F%2Fwww.android100.org%2Fhtml%2F201303%2F01%2F1732.html<r=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DAjshfa9UQrN6SNT_bxUgMHWXuLzpg6ksTdQdAs7dUN_oe_oOEAPJQkNOvVwDx_I2jSSsVkAdU3lEr_frmZQg__%26wd%3D%26eqid%3Dbe651b4200050dd20000000256dae81a&ecd=1&psr=1366x768&par=1366x706&pis=-1x-1&ccd=24&cja=true&cmi=111&col=zh-CN&cdo=-1&tcn=1457187075&qn=688478dc59c01a63&tt=1457187074813.262.459.460" width="600" height="282" align="center,center" vspace="0" hspace="0" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" allowtransparency="true" style="display: block; margin: 0px; border-width: 0px; border-style: initial; vertical-align: bottom;">
|