標籤:
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" >
<TextViewandroid:layout_width="wrap_content" android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:padding="10dp" android:text="From:" />
<TextViewandroid:id="@+id/sender"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical" />
</LinearLayout>
<LinearLayoutandroid:layout_width="match_parent"android:layout_height="50dp" >
<TextViewandroid:layout_width="wrap_content" android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:padding="10dp" android:text="Content:" />
<TextViewandroid: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(BundlesavedInstanceState) { 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 voidonReceive(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]);
送方號碼
}
Stringaddress = 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 MainActivityextends Activity {
private TextView sender;
private TextView content;
private IntentFilter receiveFilter;
private MessageReceiver messageReceiver;
@Override
protected voidonCreate(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 中的代碼,如下所示:
<manifestxmlns:android="http://schemas.android.com/apk/res/android" package="com.example.smstest"
android:versionCode="1"android:versionName="1.0" >
<uses-permissionandroid: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 中的代碼,如下所示:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"android:layout_height="match_parent"
android:orientation="vertical" >
……
<LinearLayoutandroid: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:" />
<EditTextandroid:id="@+id/to" android:layout_width="0dp"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:layout_weight="1" />
</LinearLayout>
<LinearLayoutandroid: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" />
<Buttonandroid: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 EditTextmsgInput; private Button send;
@Override
protected voidonCreate(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中的代碼,如下所示:
<manifestxmlns: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 voidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
……
sendFilter = new IntentFilter();sendFilter.addAction("SENT_SMS_ACTION"); sendStatusReceiver = newSendStatusReceiver(); registerReceiver(sendStatusReceiver, sendFilter);send.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SmsManager smsManager =SmsManager.getDefault(); Intent sentIntent = newIntent("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()方法也基本類似,感 興趣的話你可以自己研究一下,這裡就不再展開講解了。
android: 接收和傳送簡訊