Android四大元件之BroadcastReceiver應用詳解
今天我們來講一下Android中BroadcastReceiver的相關知識。
BroadcastReceiver也就是“廣播接收者”的意思,顧名思義,它就是用來接收來自系統和應用中的廣播。
在Android系統中,廣播體現在方方面面,例如當開機完成後系統會產生一條廣播,接收到這條廣播就能實現開機啟動服務的功能;當網路狀態改變時系統會產生一條廣播,接收到這條廣播就能及時地做出提示和儲存資料等操作;當電池電量改變時,系統會產生一條廣播,接收到這條廣播就能在電量低時告知使用者及時儲存進度,等等。
Android中的廣播機制設計的非常出色,很多事情原本需要開發者親自操作的,現在只需等待廣播告知自己就可以了,大大減少了開發的工作量和開發週期。而作為應用開發者,就需要數練掌握Android系統提供的一個開發利器,那就是BroadcastReceiver。下面我們就對BroadcastReceiver逐一地分析和演練,瞭解和掌握它的各種功能和用法。
首先,我們來演示一下建立一個BroadcastReceiver,並讓這個BroadcastReceiver能夠根據我們的需要來執行。
要建立自己的BroadcastReceiver物件,我們需要繼承android.content.BroadcastReceiver,並實現其onReceive方法。下面我們就建立一個名為MyReceiver廣播接收者:
package com.scott.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; public class MyReceiver extends BroadcastReceiver { private static final String TAG = "MyReceiver"; @Override public void onReceive(Context context, Intent intent) { String msg = intent.getStringExtra("msg"); Log.i(TAG, msg); } }
在onReceive方法內,我們可以獲取隨廣播而來的Intent中的資料,這非常重要,就像無線電一樣,包含很多有用的資訊。
在建立完我們的BroadcastReceiver之後,還不能夠使它進入工作狀態,我們需要為它註冊一個指定的廣播地址。沒有註冊廣播地址的BroadcastReceiver就像一個缺少選臺按鈕的收音機,雖然功能俱備,但也無法收到電臺的訊號。下面我們就來介紹一下如何為BroadcastReceiver註冊廣播地址。
靜態註冊
靜態註冊是在AndroidManifest.xml檔案中配置的,我們就來為MyReceiver註冊一個廣播地址:
<receiver android:name=".MyReceiver"> <intent-filter> <action android:name="android.intent.action.MY_BROADCAST"/> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver>
配置了以上資訊之後,只要是android.intent.action.MY_BROADCAST這個地址的廣播,MyReceiver都能夠接收的到。注意,這種方式的註冊是常駐型的,也就是說當應用關閉後,如果有廣播資訊傳來,MyReceiver也會被系統呼叫而自動執行。
動態註冊
動態註冊需要在程式碼中動態的指定廣播地址並註冊,通常我們是在Activity或Service註冊一個廣播,下面我們就來看一下注冊的程式碼:
MyReceiver receiver = new MyReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.MY_BROADCAST");
registerReceiver(receiver, filter);
注意,registerReceiver是android.content.ContextWrapper類中的方法,Activity和Service都繼承了ContextWrapper,所以可以直接呼叫。在實際應用中,我們在Activity或Service中註冊了一個BroadcastReceiver,當這個Activity或Service被銷燬時如果沒有解除註冊,系統會報一個異常,提示我們是否忘記解除註冊了。所以,記得在特定的地方執行解除註冊操作:
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
執行這樣行程式碼就可以解決問題了。注意,這種註冊方式與靜態註冊相反,不是常駐型的,也就是說廣播會跟隨程式的生命週期。
我們可以根據以上任意一種方法完成註冊,當註冊完成之後,這個接收者就可以正常工作了。我們可以用以下方式向其傳送一條廣播:
public void send(View view) {
Intent intent = new Intent("android.intent.action.MY_BROADCAST");
intent.putExtra("msg", "hello receiver.");
sendBroadcast(intent);
}
注意,sendBroadcast也是android.content.ContextWrapper類中的方法,它可以將一個指定地址和引數資訊的Intent物件以廣播的形式傳送出去。
點擊發送按鈕,執行send方法,控制檯列印如下:
看到這樣的列印資訊,表明我們的廣播已經發出去了,並且被MyReceiver準確無誤的接收到了。
上面的例子只是一個接收者來接收廣播,如果有多個接收者都註冊了相同的廣播地址,又會是什麼情況呢,能同時接收到同一條廣播嗎,相互之間會不會有干擾呢?這就涉及到普通廣播和有序廣播的概念了。
普通廣播(Normal Broadcast)
普通廣播對於多個接收者來說是完全非同步的,通常每個接收者都無需等待即可以接收到廣播,接收者相互之間不會有影響。對於這種廣播,接收者無法終止廣播,即無法阻止其他接收者的接收動作。
為了驗證以上論斷,我們新建三個BroadcastReceiver,演示一下這個過程,FirstReceiver、SecondReceiver和ThirdReceiver的程式碼如下:
package com.scott.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class FirstReceiver extends BroadcastReceiver {
private static final String TAG = "NormalBroadcast";
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("msg");
Log.i(TAG, "FirstReceiver: " + msg);
}
}
public class SecondReceiver extends BroadcastReceiver {
private static final String TAG = "NormalBroadcast";
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("msg");
Log.i(TAG, "SecondReceiver: " + msg);
}
}
public class ThirdReceiver extends BroadcastReceiver {
private static final String TAG = "NormalBroadcast";
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("msg");
Log.i(TAG, "ThirdReceiver: " + msg);
}
}
然後再次點擊發送按鈕,傳送一條廣播,控制檯列印如下:
看來這三個接收者都接收到這條廣播了,我們稍微修改一下三個接收者,在onReceive方法的最後一行新增以下程式碼,試圖終止廣播:
abortBroadcast();
再次點擊發送按鈕,我們會發現,控制檯中三個接收者仍然都列印了自己的日誌,表明接收者並不能終止廣播。
有序廣播(Ordered Broadcast)
有序廣播比較特殊,它每次只發送到優先順序較高的接收者那裡,然後由優先順序高的接受者再傳播到優先順序低的接收者那裡,優先順序高的接收者有能力終止這個廣播。
為了演示有序廣播的流程,我們修改一下上面三個接收者的程式碼,如下:
package com.scott.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
public class FirstReceiver extends BroadcastReceiver {
private static final String TAG = "OrderedBroadcast";
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("msg");
Log.i(TAG, "FirstReceiver: " + msg);
Bundle bundle = new Bundle();
bundle.putString("msg", msg + "@FirstReceiver");
setResultExtras(bundle);
}
}
public class SecondReceiver extends BroadcastReceiver {
private static final String TAG = "OrderedBroadcast";
@Override
public void onReceive(Context context, Intent intent) {
String msg = getResultExtras(true).getString("msg");
Log.i(TAG, "SecondReceiver: " + msg);
Bundle bundle = new Bundle();
bundle.putString("msg", msg + "@SecondReceiver");
setResultExtras(bundle);
}
}
public class ThirdReceiver extends BroadcastReceiver {
private static final String TAG = "OrderedBroadcast";
@Override
public void onReceive(Context context, Intent intent) {
String msg = getResultExtras(true).getString("msg");
Log.i(TAG, "ThirdReceiver: " + msg);
}
}
我們注意到,在FirstReceiver和SecondReceiver中最後都使用了setResultExtras方法將一個Bundle物件設定為結果集物件,傳遞到下一個接收者那裡,這樣以來,優先順序低的接收者可以用getResultExtras獲取到最新的經過處理的資訊集合。
程式碼改完之後,我們需要為三個接收者註冊廣播地址,我們修改一下AndroidMainfest.xml檔案:
<receiver android:name=".FirstReceiver">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.MY_BROADCAST"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name=".SecondReceiver">
<intent-filter android:priority="999">
<action android:name="android.intent.action.MY_BROADCAST"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name=".ThirdReceiver">
<intent-filter android:priority="998">
<action android:name="android.intent.action.MY_BROADCAST"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
我們看到,現在這三個接收者的<intent-filter>多了一個android:priority屬性,並且依次減小。這個屬性的範圍在-1000到1000,數值越大,優先順序越高。
現在,我們需要修改一下發送廣播的程式碼,如下:
public void send(View view) {
Intent intent = new Intent("android.intent.action.MY_BROADCAST");
intent.putExtra("msg", "hello receiver.");
sendOrderedBroadcast(intent, "scott.permission.MY_BROADCAST_PERMISSION");
}
注意,使用sendOrderedBroadcast方法傳送有序廣播時,需要一個許可權引數,如果為null則表示不要求接收者宣告指定的許可權,如果不為null,則表示接收者若要接收此廣播,需宣告指定許可權。這樣做是從安全形度考慮的,例如系統的簡訊就是有序廣播的形式,一個應用可能是具有攔截垃圾簡訊的功能,當簡訊到來時它可以先接受到簡訊廣播,必要時終止廣播傳遞,這樣的軟體就必須宣告接收簡訊的許可權。
所以我們在AndroidMainfest.xml中定義一個許可權:
<permission android:protectionLevel="normal"
android:name="scott.permission.MY_BROADCAST_PERMISSION" />
然後宣告使用了此許可權:
<uses-permission android:name="scott.permission.MY_BROADCAST_PERMISSION" />
關於這部分如果有不明白的地方可以參考我之前寫過的一篇文章:Android宣告和使用許可權
然後我們點擊發送按鈕傳送一條廣播,控制檯列印如下:
我們看到接收是按照順序的,第一個和第二個都在結果集中加入了自己的標記,並且向優先順序低的接收者傳遞下去。
既然是順序傳遞,試著終止這種傳遞,看一看效果如何,我們修改FirstReceiver的程式碼,在onReceive的最後一行新增以下程式碼:
abortBroadcast();
然後再次執行程式,控制檯列印如下:
此次,只有第一個接收者執行了,其它兩個都沒能執行,因為廣播被第一個接收者終止了。
上面就是BroadcastReceiver的介紹,下面我將會舉幾個常見的例子加深一下大家對廣播的理解和應用:
1.開機啟動服務
我們經常會有這樣的應用場合,比如訊息推送服務,需要實現開機啟動的功能。要實現這個功能,我們就可以訂閱系統“啟動完成”這條廣播,接收到這條廣播後我們就可以啟動自己的服務了。我們來看一下BootCompleteReceiver和MsgPushService的具體實現:
package com.scott.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class BootCompleteReceiver extends BroadcastReceiver {
private static final String TAG = "BootCompleteReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, MsgPushService.class);
context.startService(service);
Log.i(TAG, "Boot Complete. Starting MsgPushService...");
}
}
package com.scott.receiver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class MsgPushService extends Service {
private static final String TAG = "MsgPushService";
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate called.");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand called.");
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
}
然後我們需要在AndroidManifest.xml中配置相關資訊:
<!-- 開機廣播接受者 -->
<receiver android:name=".BootCompleteReceiver">
<intent-filter>
<!-- 註冊開機廣播地址-->
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- 訊息推送服務 -->
<service android:name=".MsgPushService"/>
我們看到BootCompleteReceiver註冊了“android.intent.action.BOOT_COMPLETED”這個開機廣播地址,從安全形度考慮,系統要求必須宣告接收開機啟動廣播的許可權,於是我們再宣告使用下面的許可權:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
經過上面的幾個步驟之後,我們就完成了開機啟動的功能,將應用執行在模擬器上,然後重啟模擬器,控制檯列印如下:
如果我們檢視已執行的服務就會發現,MsgPushService已經執行起來了。
2.網路狀態變化
在某些場合,比如使用者瀏覽網路資訊時,網路突然斷開,我們要及時地提醒使用者網路已斷開。要實現這個功能,我們可以接收網路狀態改變這樣一條廣播,當由連線狀態變為斷開狀態時,系統就會發送一條廣播,我們接收到之後,再通過網路的狀態做出相應的操作。下面就來實現一下這個功能:
package com.scott.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import android.widget.Toast;
public class NetworkStateReceiver extends BroadcastReceiver {
private static final String TAG = "NetworkStateReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "network state changed.");
if (!isNetworkAvailable(context)) {
Toast.makeText(context, "network disconnected!", 0).show();
}
}
/**
* 網路是否可用
*
* @param context
* @return
*/
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo[] info = mgr.getAllNetworkInfo();
if (info != null) {
for (int i = 0; i < info.length; i++) {
if (info[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
return false;
}
}
再註冊一下這個接收者的資訊:
<receiver android:name=".NetworkStateReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
因為在isNetworkAvailable方法中我們使用到了網路狀態相關的API,所以需要宣告相關的許可權才行,下面就是對應的許可權宣告:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
我們可以測試一下,比如關閉WiFi,看看有什麼效果。
3.電量變化
如果我們閱讀軟體,可能是全屏閱讀,這個時候使用者就看不到剩餘的電量,我們就可以為他們提供電量的資訊。要想做到這一點,我們需要接收一條電量變化的廣播,然後獲取百分比資訊,這聽上去挺簡單的,我們就來實現以下:
package com.scott.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
import android.util.Log;
public class BatteryChangedReceiver extends BroadcastReceiver {
private static final String TAG = "BatteryChangedReceiver";
@Override
public void onReceive(Context context, Intent intent) {
int currLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); //當前電量
int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1); //總電量
int percent = currLevel * 100 / total;
Log.i(TAG, "battery: " + percent + "%");
}
}
然後再註冊一下廣播接地址資訊就可以了:
<receiver android:name=".BatteryChangedReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
當然,有些時候我們是要立即獲取電量的,而不是等電量變化的廣播,比如當閱讀軟體開啟時立即顯示出電池電量。我們可以按以下方式獲取:
Intent batteryIntent = getApplicationContext().registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
int currLevel = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
int total = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
int percent = currLevel * 100 / total;
Log.i("battery", "battery: " + percent + "%");