8.2 接收和发送短信
收发短信应该是每个手机最基本的功能之一了,即使是许多年前的老手机也都会具备这 项功能,而 Android 作为出色的智能手机操作系统,自然也少不了在这方面的支持。每个 Android 手机都会内置一个短信应用程序,使用它就可以轻松地完成收发短信的操作,如 图 8.4 所示。
图 8.4
不过作为一名开发者,仅仅满足于此显然是不够的。你要知道,Android 还提供了一系 列的 API,使得我们甚至可以在自己的应用程序里接收和发送短信。也就是说,只要你有足 够的信心,完全可以自己实现一个短信应用来替换掉 Android 系统自带的短信应用。那么下 面我们就来看一看,如何才能在自己的应用程序里接收和发送短信。
8.2.1 接收短信
其实接收短信主要是利用了我们在第 5 章学习过的广播机制。当手机接收到一条短信的 时候,系统会发出一条值为 android.provider.Telephony.SMS_RECEIVED 的广播,这条广播里 携带着与短信相关的所有数据。每个应用程序都可以在广播接收器里对它进行监听,收到广 播时再从中解析出短信的内容即可。
让我们通过一个具体的例子来实践一下吧,新建一个 SMSTest 项目,首先修改 activity_
main.xml 中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout android:layout_width="match_parent"
android:layout_height="50dp" >
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:padding="10dp" android:text="From:" />
<TextView android:id="@+id/sender" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" />
</LinearLayout>
<LinearLayout android:layout_width="match_parent" android:layout_height="50dp" >
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:padding="10dp" android:text="Content:" />
<TextView android:id="@+id/content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" />
</LinearLayout>
</LinearLayout>
这个布局文件里,我们在根元素下面放置了两个 LinearLayout,用于显示两行数据。第 一个 LinearLayout 中有两个 TextView,用于显示短信的发送方。第二个 LinearLayout 中也有 两个 TextView,用于显示短信的内容。
接着修改 MainActivity 中的代码,在 onCreate()方法中获取到两个 TextView 的实例,如下所示:
public class MainActivity extends Activity {
private TextView sender;
private TextView content;
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
sender = (TextView) findViewById(R.id.sender);
content = (TextView) findViewById(R.id.content);
}
}
然后我们需要创建一个广播接收器来接收系统发出的短信广播。在 MainActivity 中新建 MessageReceiver 内部类继承自 BroadcastReceiver,并在 onReceive()方法中编写获取短信数 据的逻辑,代码如下所示:
public class MainActivity extends Activity {
……
class MessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) { Bundle bundle = intent.getExtras();
Object[] pdus = (Object[]) bundle.get("pdus"); // 提取短信消息
SmsMessage[] messages = new SmsMessage[pdus.length];
for (int i = 0; i < messages.length; i++) {
messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
送方号码
}
String address = messages[0].getOriginatingAddress(); // 获取发
String fullMessage = "";
for (SmsMessage message : messages) {
fullMessage += message.getMessageBody(); // 获取短信内容
} sender.setText(address); content.setText(fullMessage);
}
}
}
可以看到,首先我们从 Intent 参数中取出了一个 Bundle 对象,然后使用 pdu 密钥来提取 一个 SMS pdus 数组,其中每一个 pdu 都表示一条短信消息。接着使用 SmsMessage 的 createFromPdu() 方法将每一个 pdu 字节数组转换为 SmsMessage 对象,调用这个对象的 getOriginatingAddress()方法就可以获取到短信的发送方号码,调用 getMessageBody()方法就 可以获取到短信的内容,然后将每一个 SmsMessage 对象中的短信内容拼接起来,就组成了 一条完整的短信。最后将获取到的发送方号码和短信内容显示在 TextView 上。
完成了 MessageReceiver 之后,我们还需要对它进行注册才能让它接收到短信广播,代 码如下所示:
public class MainActivity extends Activity {
private TextView sender;
private TextView content;
private IntentFilter receiveFilter;
private MessageReceiver messageReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
sender = (TextView) findViewById(R.id.sender); content = (TextView) findViewById(R.id.content); receiveFilter = new IntentFilter();
receiveFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
messageReceiver = new MessageReceiver();
registerReceiver(messageReceiver, receiveFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(messageReceiver);
}
……
}
这些代码你应该都已经非常熟悉了,使用的就是动态注册广播的技术。在 onCreate()方 法中对 MessageReceiver 进行注册,在 onDestroy()方法中再对它取消注册。
代码到这里就已经完成得差不多了,不过最后我们还需要给程序声明一个接收短信的权 限才行,修改 AndroidManifest.xml 中的代码,如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.smstest"
android:versionCode="1" android:versionName="1.0" >
<uses-permission android:name="android.permission.RECEIVE_SMS" />
……
</manifest>
现在可以来运行一下程序了,界面如图 8.5 所示。
图 8.5
当有短信到来时,短信的发送方和内容就会显示在界面上。不过话说回来,我们使用的是模拟器,模拟器上怎么可能会收得到短信呢?不用担心,DDMS 提供了非常充分的模拟环
境,使得我们不需要支付真正的短信费用也可以模拟收发短信的场景。将 Eclipse 切换到 DDMS 视图下,然后点击 Emulator Control 切换卡,在这里就可以向模拟器发送短信了,如 图 8.6 所示。
图 8.6
可以看到,我们指定发送方的号码是 556677,并填写了一段短信内容,然后点击 Send按钮,这样短信就发送成功了。接着我们立马查看一下 SMSTest 这个程序,结果如图 8.7 所示。
图 8.7
可以看到,短信的发送方号码和短信内容都显示到界面上了,说明接收短信的功能成功 实现了。
8.2.2 拦截短信
仔细观察图 8.7,你会发现在系统状态栏出现了一个通知图标,这个通知图标是由 Android 自带的短信程序产生的。也就是说当短信到来时,不仅我们的程序会接收到这条短信,系统 的短信程序同样也会收到。同样一条短信被重复接收两遍就会造成比较差的用户体验,那么 有没有什么办法可以屏蔽系统短信程序的接收功能呢?
在前面 5.3.2 节学习有序广播的时候我们就已经知道,有序广播的传递是可以截断的, 而系统发出的短信广播正是一条有序广播,因此这里我们的答案是肯定的。修改 MainActivity 中的代码,如下所示:
public class MainActivity extends Activity {
……
@Override
protected void onCreate(Bundle savedInstanceState) {
……
receiveFilter = new IntentFilter(); receiveFilter.addAction("android.provider.Telephony.SMS_RECEIVED"); receiveFilter.setPriority(100);
messageReceiver = new MessageReceiver();
registerReceiver(messageReceiver, receiveFilter);
}
……
class MessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
……
abortBroadcast();
}
}
}
可以看到,关键性的步骤只有两步。一是提高 MessageReceiver 的优先级,让它能够先 于系统短信程序接收到短信广播。二是在 onReceive()方法中调用 abortBroadcast()方法,中止掉广播的继续传递。
现在重新运行程序,再向模拟器发送一条短信,这时只有我们自己的程序才能收到这条 短信了。按下 Back 键将程序关闭后,系统的短信程序又会重新拥有接收短信的功能。
注意这个功能一定要慎用,随意拦截短信有可能会造成重要数据的丢失,所以你在拦截 之前一定要先想清楚这种功能是不是你想要的。
8.2.3 发送短信
下面我们继续对 SMSTest 项目进行扩展,给它加上发送短信的功能。那么还是先来编写 一下布局文件吧,修改 activity_main.xml 中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical" >
……
<LinearLayout android:layout_width="match_parent" android:layout_height="50dp" >
<TextView
android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:padding="10dp"
android:text="To:" />
<EditText android:id="@+id/to" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" />
</LinearLayout>
<LinearLayout android:layout_width="match_parent" android:layout_height="50dp" >
<EditText
android:id="@+id/msg_input" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" />
<Button android:id="@+id/send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:text="Send" />
</LinearLayout>
</LinearLayout>
这里我们又新增了两个 LinearLayout,分别处于第三和第四行的位置。第三行中放置了 一个 EditText,用于输入接收方的手机号码。第四行中放置了一个 EditText 和一个 Button, 分别用于输入短信内容和发送短信。
然后修改 MainActivity 中的代码,在里面加入发送短信的处理逻辑,代码如下所示:
public class MainActivity extends Activity {
……
private EditText to; private EditText msgInput; private Button send;
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
……
to = (EditText) findViewById(R.id.to);
msgInput = (EditText) findViewById(R.id.msg_input); send = (Button) findViewById(R.id.send); send.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(to.getText().toString(), null,
}
});
}
……
}
msgInput.getText().toString(), null, null);
可以看到,首先我们获取到了布局文件中新增控件的实例,然后在 Send 按钮的点击事 件里面处理了发送短信的具体逻辑。当 Send 按钮被点击时,会先调用 SmsManager 的 getDefault()方法获取到 SmsManager 的实例,然后再调用它的 sendTextMessage()方法就可以 去发送短信了。sendTextMessage()方法接收五个参数,其中第一个参数用于指定接收人的手 机号码,第三个参数用于指定短信的内容,其他的几个参数我们暂时用不到,直接传入 null 就可以了。
接下来也许你已经猜到了,发送短信也是需要声明权限的,因此修改 AndroidManifest.xml中的代码,如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.smstest"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission. SEND_SMS" />
……
</manifest>
现在重新运行程序之后,SMSTest 就拥有了发送短信的能力。不过点击 Send 按钮虽然 可以将短信发送出去,但是我们并不知道到底发送成功了没有,这个时候就可以利用 sendTextMessage()方法的第四个参数来对短信的发送状态进行监控。修改 MainActivity 中的 代码,如下所示:
public class MainActivity extends Activity {
……
private IntentFilter sendFilter;
private SendStatusReceiver sendStatusReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
……
sendFilter = new IntentFilter(); sendFilter.addAction("SENT_SMS_ACTION"); sendStatusReceiver = new SendStatusReceiver(); registerReceiver(sendStatusReceiver, sendFilter); send.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SmsManager smsManager = SmsManager.getDefault(); Intent sentIntent = new Intent("SENT_SMS_ACTION"); PendingIntent pi = PendingIntent.getBroadcast
(MainActivity.this, 0, sentIntent, 0);
smsManager.sendTextMessage(to.getText().toString(), null, msgInput.getText().toString(), pi, null);
}
});
}
@Override
protected void onDestroy() { super.onDestroy(); unregisterReceiver(messageReceiver);
unregisterReceiver(sendStatusReceiver);
}
……
class SendStatusReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (getResultCode() == RESULT_OK) {
// 短信发送成功
Toast.makeText(context, "Send succeeded", Toast.LENGTH_LONG).show();
} else {
// 短信发送失败
Toast.makeText(context, "Send failed", Toast.LENGTH_LONG).show();
}
}
}
}
可以看到,在 Send 按钮的点击事件里面我们调用了 PendingIntent 的 getBroadcast()方法 获取到了一个 PendingIntent 对象,并将它作为第四个参数传递到 sendTextMessage()方法中。 然后又注册了一个新的广播接收器 SendStatusReceiver,这个广播接收器就是专门用于监听 短信发送状态的,当 getResultCode()的值等于 RESULT_OK 就会提示发送成功,否则提示发 送失败。
现在重新运行一下程序,在文本输入框里输入接收方的手机号码以及短信内容,然后点 击 Send 按钮,结果如图 8.8 所示。
图 8.8
注意,这里虽然提示发送成功了,但实际上使用模拟器来发送短信对方是不可能收得到 的,只有把这个项目运行在手机上,才能真正地实现发送短信的功能。
另外,根据国际标准,每条短信的长度不得超过 160 个字符,如果想要发送超出这个长 度的短信,则需要将这条短信分割成多条短信来发送,使用 SmsManager 的 sendMultipart- TextMessage()方法就可以实现上述功能。它的用法和 sendTextMessage()方法也基本类似,感 兴趣的话你可以自己研究一下,这里就不再展开讲解了。