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()方法也基本類似,感 興趣的話你可以自己研究一下,這里就不再展開講解了。
