Android入門——Broadcast Receiver詳解與應用
引言
作為Android四大元件之一——BroadcastReceive。BroadcastReceive廣泛運用在APP的開發中,其他的三大應用元件不同一樣,是唯一需要被動接收,即負責接收的廣播接收器永遠不知道什麼時候可以接收到廣播,這種機制其實有點像Event,你永遠無法預知什麼時候Button的Click方法會被執行,執行在程序的主執行緒中。廣播可以看成是一種事件機制,也可以看成是全域性事件,Android 定義了很多這種全域性事件,當Android系統發生某些系統事件時,就會向整個系統發出一個全域性資訊即廣播。比如收到簡訊,來電、電池電量變化等等廣播,只要我們實現了他們的action的廣播,那麼我們就能接收他們的資料了,以便做出一些處理。比如說攔截系統簡訊,攔截騷擾電話等等 。實現了不同的程式之間的資料傳輸與共享,因為只要是和傳送廣播的action相同的接受者都能接受這個廣播。
一 服務BroadcastReceiver的概念
Android四大元件中只有廣播接收器是需要被動接收,即廣播接收器不能主動呼叫,換言之負責接收的廣播接收器永遠不知道什麼時候可以接收到廣播,一個APP可以有多個廣播接收器,只要繼承BroadcastReceiver即可。廣播主要涉及接收和優先順序, 只要將廣播接收器註冊到某個訊息上,不管有多少廣播接收器,這些的接收器都會按照不同的優先順序接收到這個廣播,雖然廣播接收器和服務一樣沒有互動UI,但可以在廣播接收器中啟動一個Activity來響應廣播動作。
二 定義廣播接收器
通常一個BroadcastReceiver物件的生命週期不超過5秒
- 繼承BroadcastReceiver,重寫onReceive方法
- 在manifest清單檔案中宣告,作用就是指明瞭你這個接收器是要接收那個具體的廣播
//定義一個電池電量變化廣播接收器
public class BroadcastReceiverDemo extends BroadcastReceiver{
@Override
public void onReceive (Context context,Intent intent){
//當收到廣播的時候會觸發
...
}
}
/*接收電池電量變化廣播Intent ,在AndroidManifest.xml檔案中的<application>節點裡訂閱此Intent:
<receiver android:name=".IncomingSMSReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
</intent-filter>
</receiver>
*/
三 廣播的分類及優先順序
BroadcastReceiver 用於非同步接收廣播Intent。主要有兩大類,用於接收廣播的:
1. 正常廣播 Normal broadcasts(用 Context.sendBroadcast()傳送)是完全非同步的。它們都執行在一個未定義的順序,通常是在同一時間。這樣會更有效,但意味著receiver不能包含所要使用的結果或中止的API。
2. 有序廣播 Ordered broadcasts(用 Context.sendOrderedBroadcast()傳送)每次被髮送到一個receiver。所謂有序,就是每個receiver執行後可以傳播到下一個receiver,也可以完全中止傳播–不傳播給其他receiver。 而receiver執行的順序可以通過匹配intent-filter 裡面的android:priority來控制,當priority優先順序相同的時候,Receiver以任意的順序執行。priority值越大優先順序越高。
3. 要注意的是,即使是Normal broadcasts,系統在某些情況下可能會恢復到一次傳播給一個receiver。 特別是receiver可能需要建立一個程序,為了避免系統超載,只能一次執行一個receiver。Broadcast Receiver 並沒有提供視覺化的介面來顯示廣播資訊。可以使用Notification和Notification Manager來實現視覺化的資訊的介面,顯示廣播資訊的內容,圖示及震動資訊。
四 廣播接收器的訂閱
建立完了廣播接收器,繼承BroadcastReceiver並實現相關方法之後,還不能正常使用,必須還要進行訂閱工作,訂閱有兩種方式,靜態訂閱方式通過manifest清單檔案註冊和通過registerReceiver方法訂閱和unregisterReceiver取消訂閱。
1. 通過清單檔案在AndroidManifest.xml檔案中的application節點裡訂閱此Intent:
<receiver android:name=".IncomingSMSReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
</intent-filter>
</receiver>
- 通過registerReceiver方法訂閱和unregisterReceiver取消訂閱
BroadcastReceiverDemo receiver=new BroadcastReceiverDemo ();//建立接收器物件
//訂閱對應的廣播
regeisterReceiver(receiver,new IntentFilter("android.provider.Telephoy.SMS_RECEIVED"));
//取消訂閱
unregeisterReceiver(receiver);
五 幾種常見的廣播Intent
- 接收電池電量變化廣播Intent
<receiver android:name=".YourCastReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
</intent-filter>
</receiver>
- 接收開機啟動廣播Intent
<receiver android:name=".BootSMSReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<!--並且要進行許可權宣告:-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
- 接收簡訊息廣播Intent
<receiver android:name=".SMSBroadcastReceiver">
<intent-filter android:priority="1000"> <action android:name="android.provider.Telephony.SMS_RECEIVED" />
<action android:name="android.intent.action.PHONE_STATE"/>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
- 接收來去電廣播Intent
<receiver android:name=".PhoneBroadcastReceiver">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.PHONE_STATE"/>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
六 廣播接收器BroadcastReceiver的應用
5.1 接收Android預定義的廣播
正如前面所說的系統預定義了很多廣播,諸如來去電、接收到簡訊、電池電量變化、開關機等等,我們要想接收到這些廣播,只需要兩步:
- 繼承BroadcastReceiver,重寫onReceive方法(onReceive() 方法執行完後,BroadcastReceiver 的例項就會被銷燬。當onReceive() 方法在10秒內沒有執行完畢,Android會認為該程式無響應。所以在BroadcastReceiver裡不能做一些比較耗時的操作,否側會彈出ANR的對話方塊)
/*通常一個BroadcastReceiver物件的生命週期不超過10秒,所以在BroadcastReceiver裡不能做一些比較耗時的操作,如果需要完成一項比較耗時的工作,可以通過傳送Intent給Activity或Service,由Activity或Service來完成。*/
public class IncomingSMSReceiver extends BroadcastReceiver {
@Override public void onReceive(Context context, Intent intent) {
//傳送Intent啟動服務,由服務來完成比較耗時的操作
Intent service = new Intent(context, XxxService.class);
context.startService(service);
//傳送Intent啟動Activity,由Activity來完成比較耗時的操作
Intent newIntent = new Intent(context, XxxActivity.class);
context.startActivity(newIntent);
}
}
- 在清單檔案中宣告對應的許可權和設定對應的action值或者利用程式碼手動訂閱廣播和取消訂閱廣播
下面模擬一個監聽簡訊廣播,接收到簡訊之後的處理,靜態訂閱廣播,第一步實現廣播接收器
package cmo.test.broadcast;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.SmsMessage;
import android.util.Log;
public class SMSBroadcastReceiver extends BroadcastReceiver {
/**
* 接收Sms的廣播接收器
* onReceive() 方法執行完後,BroadcastReceiver 的例項就會被銷燬。當onReceive() 方法在10秒內沒有執行完畢,Android會認為該程式無響應。
* 所以在BroadcastReceiver裡不能做一些比較耗時的操作,否側會彈出ANR(Application No Response)的對話方塊。
* @author cmo
*/
@Override
public void onReceive(Context context, Intent intent) {
Log.d("SMSBroadcastReceiver","接收到了簡訊");
String sms=monitorSMS(intent);
……
}
private String monitorSMS(Intent intent){
Log.d("SMSBroadcastReceiver","接收到了簡訊ing");
Object[] msgs=(Object[])intent.getExtras().get("pdus");
String content="";
String receiveDate="";
String sendrNumber="";
for(Object obj: msgs){
byte[] sms=(byte[]) obj;
SmsMessage message=SmsMessage.createFromPdu(sms);
content=message.getMessageBody();
Date date=new Date(message.getTimestampMillis());
receiveDate=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
sendrNumber=message.getOriginatingAddress();
}
if(sendrNumber.("1001")){
abortBroadcast();//終止廣播的繼續傳遞,其他應用就無法接收到了,前提是你的獲取簡訊和監聽廣播的許可權沒有被系統本身遮蔽或者其他安全軟體
}
return "發信人:"+sendrNumber+"發信時間:"+receiveDate+"簡訊內容:"+content;
}
}
第二,在清單檔案中宣告許可權和註冊廣播
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cmo.test.broadcast"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="22" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"></uses-permission>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<receiver android:name=".SMSBroadcastReceiver">
<!-- 設定廣播接收器的優先等級priority(-1000————1000?)為 最大1000 肯定能超過手機自帶的簡訊功能,但是還會首先被其他諸如360、LBE首先攔截,不知道為什麼? -->
<intent-filter android:priority="1000">
<!-- 配置action對應的值 就指定了該接收什麼型別的廣播 -->
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>
但出現一個問題,在有些系統上不能正常讀取到簡訊或者攔截簡訊,也許是讀取簡訊的許可權被系統或者安全軟體遮蔽了靜態註冊吧!
還是模擬一個監聽簡訊廣播,接收到簡訊之後的處理,與前面一個例子不同的是這次改為利用程式碼訂閱廣播
5.2 利用程式碼動態訂閱廣播和取消訂閱
//首先定義一個Activity,在Activity的宣告週期中程式碼訂閱廣播
package cmo.test.costomcastreceiver;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends Activity {
public static final String SMS_ACTION = "android.provider.Telephony.SMS_RECEIVED";
BroadcastReceiver mReceiver;
static final String Tag = "SMSBROADCAST";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onStart() {
super.onStart();
Log.d(Tag, " onStart ");
//註冊廣播
mReceiver = new SmsBroadcastRecevier();
IntentFilter filter = new IntentFilter(SMS_ACTION);
filter.setPriority(1000);
registerReceiver(mReceiver, filter);
}
@Override
protected void onStop() {
super.onStop();
Log.d(Tag, " onStop ");
//取消廣播
unregisterReceiver(mReceiver);
}
}
實現廣播接收器,接收到了簡訊,並且遮蔽了所有簡訊
package cmo.test.costomcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;
import android.widget.Toast;
public class SmsBroadcastRecevier extends BroadcastReceiver {
public static final String SMS_ACTION = "android.provider.Telephony.SMS_RECEIVED";
static final String Tag = "SMSBROADCAST";
String addr,body;
@Override
public void onReceive(Context arg0, Intent arg1) {
if(arg1.getAction().equals(SmsBroadcastRecevier.SMS_ACTION)){
Toast.makeText(arg0, "監測到系統簡訊", Toast.LENGTH_SHORT).show();
//獲取intent引數
Bundle bundle=arg1.getExtras();
//判斷bundle內容
if (bundle!=null) {
//獲得並解析簡訊
Object[] pdus=(Object[])bundle.get("pdus");//取 pdus內容
SmsMessage[] messages = new SmsMessage[pdus.length];//解析簡訊
for(int i=0;i<messages.length;i++)
{
Log.d(Tag, " receive data ");
byte[] pdu=(byte[])pdus[i];
messages[i]=SmsMessage.createFromPdu(pdu);
addr = messages[0].getDisplayOriginatingAddress().toString();
body = messages[0].getDisplayMessageBody().toString();
System.out.println("\n"+"addr :"+addr + "body:"+body);
}
Log.d(Tag, " abortBroadcast ");
//取消系統簡訊廣播
abortBroadcast();
}
}
}
}
在清單檔案中宣告許可權
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cmo.test.costomcastreceiver"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="14" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".SmsBroadcastRecevier">
</receiver>
</application>
</manifest>
執行結果:成功擷取到了簡訊並且攔截了
5.3 模擬廣播機制,傳送自定義廣播和接收廣播資訊
- 繼承BroadcastReceiver,重寫廣播接收器
- 初始化廣播接收器物件,定義意圖過濾器action
- 註冊自定義廣播registerReceiver(myReceiver);
- 傳送自定義廣播 sendBroadcast(new Intent(myAction)
- 取消廣播的訂閱unregisterReceiver(myReceiver);
//接收到了自定義廣播的時候,就會執行onReceive方法
package cmo.test.broadcast;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class MonitorBroadcast extends BroadcastReceiver {
/**
* 定義接收模擬廣播的接收器
*/
final String TAG="MONITOR_BROAD" ;
@Override
public void onReceive(Context arg0, Intent arg1) {
Log.d(TAG,"接收到了"+arg1.getAction()+"的自定義廣播");
}
}
在Activity主介面完成廣播的註冊、傳送、取消訂閱
package cmo.test.broadcast;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
public class MainActivity extends Activity {
private final String TAG="MONITOR_BROAD" ;
private final String myAction="cmo.test.broadcast.MONITOR_BROAD";
private MonitorBroadcast myReceiver;
private IntentFilter intentFilter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myReceiver=new MonitorBroadcast();
Log.d(TAG,"初始化自定義廣播物件");
intentFilter=new IntentFilter(TAG);
Log.d(TAG,"初始化意圖過濾器");
registerReceiver(myReceiver, intentFilter);//註冊自定義廣播
Log.d(TAG,"註冊自定義廣播");
}
public void sendCast(View view){
//傳送自定義myAction的廣播
Intent intnet = new Intent(myAction);
sendBroadcast(intnet);//傳送自定義myAction的廣播
Log.d(TAG,"傳送自定義廣播");
}
public void stopCast(View view){
unregisterReceiver(myReceiver);
Log.d(TAG,"取消了自定義廣播的訂閱");
}
}
執行結果
小結
廣播接收者(BroadcastReceiver)用於非同步接收廣播Intent,廣播Intent的傳送是通過呼叫Context.sendBroadcast()、Context.sendOrderedBroadcast()或者Context.sendStickyBroadcast()來實現的。通常一個廣播Intent可以被訂閱了此Intent的多個廣播接收者所接收