在 Android 系統中,廣播(Broadcast)是在元件之間傳播資料的一種機制,這些元件可以位於不同的程式中,起到程式間通訊的作用
BroadcastReceiver 是對傳送出來的 Broadcast 進行過濾、接受和響應的元件。首先將要傳送的訊息和用於過濾的資訊(Action,Category)裝入一個 Intent 物件,然後通過呼叫 Context.sendBroadcast() 、 sendOrderBroadcast() 方法把 Intent 物件以廣播形式傳送出去。 廣播傳送出去後,所以已註冊的 BroadcastReceiver 會檢查註冊時的 IntentFilter 是否與傳送的 Intent 相匹配,若匹配則會呼叫 BroadcastReceiver 的 onReceiver() 方法
所以當我們定義一個 BroadcastReceiver 的時候,都需要實現 onReceiver() 方法。BroadcastReceiver 的生命週期很短,在執行 onReceiver() 方法時才有效,一旦執行完畢,該Receiver 的生命週期就結束了
Android中的廣播分為兩種型別,標準廣播和有序廣播
- 標準廣播
標準廣播是一種完全非同步執行的廣播,在廣播發出後所有的廣播接收器會在同一時間接收到這條廣播,之間沒有先後順序,效率比較高,且無法被截斷 - 有序廣播
有序廣播是一種同步執行的廣播,在廣播發出後同一時刻只有一個廣播接收器能夠接收到, 優先順序高的廣播接收器會優先接收,當優先順序高的廣播接收器的 onReceiver() 方法執行結束後,廣播才會繼續傳遞,且前面的廣播接收器可以選擇截斷廣播,這樣後面的廣播接收器就無法接收到這條廣播了
一、靜態註冊
靜態註冊即在清單檔案中為 BroadcastReceiver 進行註冊,使用**< receiver >**標籤宣告,並在標籤內用 < intent-filter > 標籤設定過濾器。這種形式的 BroadcastReceiver 的生命週期伴隨著整個應用,如果這種方式處理的是系統廣播,那麼不管應用是否在執行,該廣播接收器都能接收到該廣播
1.1、傳送標準廣播
首先,繼承 BroadcastReceiver 類建立一個用於接收標準廣播的Receiver,在 onReceive() 方法中取出 Intent 傳遞來的字串
public class NormalReceiver extends BroadcastReceiver {
private static final String TAG = "NormalReceiver";
public NormalReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("Msg");
Log.e(TAG, msg);
}
}
複製程式碼
在清單檔案中宣告的 BroadcastReceiver ,必須包含值為 NORMAL_ACTION 字串的 action 屬性,該廣播接收器才能收到以下程式碼中發出的廣播
傳送標準廣播呼叫的是 sendBroadcast(Intent) 方法
public class MainActivity extends AppCompatActivity {
private final String NORMAL_ACTION = "com.example.normal.receiver";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void sendBroadcast(View view) {
Intent intent = new Intent(NORMAL_ACTION);
intent.putExtra("Msg", "Hi");
sendBroadcast(intent);
}
}
複製程式碼
在清單檔案中註冊 BroadcastReceiver
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".NormalReceiver">
<intent-filter>
<action android:name="com.example.normal.receiver" />
</intent-filter>
</receiver>
</application>
複製程式碼
1.2、傳送有序廣播
首先,繼承 BroadcastReceiver 類建立三個用於接收有序廣播的Receiver,名字依次命名為 OrderReceiver_1、OrderReceiver_2、OrderReceiver_3。此外,既然 Receiver 在接收廣播時存在先後順序,那麼 Receiver 除了能從傳送廣播使用的 Intent 接收資料外,優先順序高的 Receiver 也能在處理完操作後向優先順序低的 Receiver 傳送處理結果
public class OrderReceiver_1 extends BroadcastReceiver {
private final String TAG = "OrderReceiver_1";
public OrderReceiver_1() {
}
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "OrderReceiver_1被呼叫了");
//取出Intent當中傳遞來的資料
String msg = intent.getStringExtra("Msg");
Log.e(TAG, "OrderReceiver_1接收到的值: " + msg);
//向下一優先順序的Receiver傳遞資料
Bundle bundle = new Bundle();
bundle.putString("Data", "(Hello)");
setResultExtras(bundle);
}
}
複製程式碼
public class OrderReceiver_2 extends BroadcastReceiver {
private final String TAG = "OrderReceiver_2";
public OrderReceiver_2() {
}
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "OrderReceiver_2被呼叫了");
//取出上一優先順序的Receiver傳遞來的資料
String data = getResultExtras(true).getString("Data");
Log.e(TAG, "從上一優先順序的Receiver傳遞來的資料--" + data);
//向下一優先順序的Receiver傳遞資料
Bundle bundle = new Bundle();
bundle.putString("Data", "(葉應是葉)");
setResultExtras(bundle);
}
}
複製程式碼
public class OrderReceiver_3 extends BroadcastReceiver {
private final String TAG = "OrderReceiver_3";
public OrderReceiver_3() {
}
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "OrderReceiver_3被呼叫了");
//取出上一優先順序的Receiver傳遞來的資料
String data = getResultExtras(true).getString("Data");
Log.e(TAG, "從上一優先順序的Receiver傳遞來的資料--" + data);
}
}
複製程式碼
在清單檔案中對三個 Receiver 進行註冊,指定相同的 action 屬性值,Receiver 之間的優先順序使用 priority 屬性來判定,數值越大,優先順序越高
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".OrderReceiver_1">
<intent-filter android:priority="100">
<action android:name="com.example.order.receiver" />
</intent-filter>
</receiver>
<receiver android:name=".OrderReceiver_2">
<intent-filter android:priority="99">
<action android:name="com.example.order.receiver" />
</intent-filter>
</receiver>
<receiver android:name=".OrderReceiver_3">
<intent-filter android:priority="98">
<action android:name="com.example.order.receiver" />
</intent-filter>
</receiver>
</application>
複製程式碼
傳送有序廣播呼叫的是 sendOrderedBroadcast(Intent , String) 方法,String 引數值在自定義許可權時使用,下邊會有介紹
public class MainActivity extends AppCompatActivity {
private final String NORMAL_ACTION = "com.example.normal.receiver";
private final String ORDER_ACTION = "com.example.order.receiver";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void sendBroadcast(View view) {
Intent intent = new Intent(NORMAL_ACTION);
intent.putExtra("Msg", "Hi");
sendBroadcast(intent);
}
public void sendOrderBroadcast(View view) {
Intent intent = new Intent(ORDER_ACTION);
intent.putExtra("Msg", "Hi");
sendOrderedBroadcast(intent, null);
}
}
複製程式碼
執行結果是
02-20 22:52:30.135 6714-6714/com.example.zy.myapplication E/OrderReceiver_1: OrderReceiver_1被呼叫了
02-20 22:52:30.135 6714-6714/com.example.zy.myapplication E/OrderReceiver_1: OrderReceiver_1接收到的值: Hi
02-20 22:52:30.143 6714-6714/com.example.zy.myapplication E/OrderReceiver_2: OrderReceiver_2被呼叫了
02-20 22:52:30.143 6714-6714/com.example.zy.myapplication E/OrderReceiver_2: 從上一優先順序的Receiver傳遞來的資料--(Hello)
02-20 22:52:30.150 6714-6714/com.example.zy.myapplication E/OrderReceiver_3: OrderReceiver_3被呼叫了
02-20 22:52:30.150 6714-6714/com.example.zy.myapplication E/OrderReceiver_3: 從上一優先順序的Receiver傳遞來的資料--(葉應是葉)
複製程式碼
可以看出 Receiver 接收廣播時不僅因為“priority”屬性存在先後順序,且 Receiver 之間也能夠傳遞資料
此外,BroadcastReceiver 也能呼叫 abortBroadcast() 方法截斷廣播,這樣低優先順序的廣播接收器就無法接收到廣播了
二、動態註冊
動態註冊 BroadcastReceiver 是在程式碼中定義並設定好一個 IntentFilter 物件,然後在需要註冊的地方呼叫 Context.registerReceiver() 方法,呼叫 Context.unregisterReceiver() 方法取消註冊,此時就不需要在清單檔案中註冊 Receiver 了
這裡採用在 Service 中註冊廣播接收器的形式,分別在註冊廣播接收器、取消註冊廣播接受器和接收到廣播時輸出Log
public class BroadcastService extends Service {
private BroadcastReceiver receiver;
private final String TAG = "BroadcastService";
public BroadcastService() {
}
@Override
public void onCreate() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(MainActivity.ACTION);
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "BroadcastService接收到了廣播");
}
};
registerReceiver(receiver, intentFilter);
Log.e(TAG, "BroadcastService註冊了接收器");
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
Log.e(TAG, "BroadcastService取消註冊接收器");
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
複製程式碼
提供啟動服務,停止服務、傳送廣播的方法
public class MainActivity extends AppCompatActivity {
public final static String ACTION = "com.example.receiver";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startService(View view) {
Intent intent = new Intent(this, BroadcastService.class);
startService(intent);
}
public void sendBroadcast(View view) {
Intent intent = new Intent(ACTION);
sendBroadcast(intent);
}
public void stopService(View view) {
Intent intent = new Intent(this, BroadcastService.class);
stopService(intent);
}
}
複製程式碼
執行結果如下所示
02-20 23:55:20.967 22784-22784/com.example.zy.myapplication E/BroadcastService: BroadcastService註冊了接收器
02-20 23:55:22.811 22784-22784/com.example.zy.myapplication E/BroadcastService: BroadcastService接收到了廣播
02-20 23:55:23.179 22784-22784/com.example.zy.myapplication E/BroadcastService: BroadcastService接收到了廣播
02-20 23:55:23.461 22784-22784/com.example.zy.myapplication E/BroadcastService: BroadcastService接收到了廣播
02-20 23:55:23.694 22784-22784/com.example.zy.myapplication E/BroadcastService: BroadcastService接收到了廣播
02-20 23:55:23.960 22784-22784/com.example.zy.myapplication E/BroadcastService: BroadcastService接收到了廣播
02-20 23:55:24.282 22784-22784/com.example.zy.myapplication E/BroadcastService: BroadcastService接收到了廣播
02-20 23:55:24.529 22784-22784/com.example.zy.myapplication E/BroadcastService: BroadcastService接收到了廣播
02-20 23:55:24.916 22784-22784/com.example.zy.myapplication E/BroadcastService: BroadcastService取消註冊接收器
複製程式碼
三、本地廣播
之前傳送和接收到的廣播全都是屬於系統全域性廣播,即發出的廣播可以被其他應用接收到,而且也可以接收到其他應用傳送出的廣播,這樣可能會有不安全因素
因此,在某些情況下可以採用本地廣播機制,使用這個機制發出的廣播只能在應用內部進行傳遞,而且廣播接收器也只能接收本應用內自身發出的廣播
本地廣播是使用 LocalBroadcastManager 來對廣播進行管理
函式 | 作用 |
---|---|
LocalBroadcastManager.getInstance(this).registerReceiver(BroadcastReceiver, IntentFilter) | 註冊Receiver |
LocalBroadcastManager.getInstance(this).unregisterReceiver(BroadcastReceiver); | 登出Receiver |
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent) | 傳送非同步廣播 |
LocalBroadcastManager.getInstance(this).sendBroadcastSync(Intent) | 傳送同步廣播 |
首先,建立一個 BroadcastReceiver 用於接收本地廣播
public class LocalReceiver extends BroadcastReceiver {
private final String TAG = "LocalReceiver";
public LocalReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "接收到了本地廣播");
}
}
複製程式碼
之後就是使用 LocalBroadcastManager 對 LocalReceiver 進行註冊和解除註冊了
private LocalBroadcastManager localBroadcastManager;
private LocalReceiver localReceiver;
private final String LOCAL_ACTION = "com.example.local.receiver";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localReceiver = new LocalReceiver();
IntentFilter filter = new IntentFilter(LOCAL_ACTION);
localBroadcastManager.registerReceiver(localReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(batteryReceiver);
localBroadcastManager.unregisterReceiver(localReceiver);
}
public void sendLocalBroadcast(View view) {
Intent intent = new Intent(LOCAL_ACTION);
localBroadcastManager.sendBroadcast(intent);
}
複製程式碼
需要注意的是,本地廣播是無法通過靜態註冊的方式來接收的,因為靜態註冊廣播主要是為了在程式未啟動的情況下也能接收廣播,而本地廣播是應用自己傳送的,此時應用肯定是啟動的了
四、使用私有許可權
使用動態註冊廣播接收器存在一個問題,即系統內的任何應用均可監聽並觸發我們的 Receiver 。通常情況下我們是不希望如此的
解決辦法之一是在清單檔案中為 < receiver > 標籤新增一個 android:exported=”false” 屬性,標明該 Receiver 僅限應用內部使用。這樣,系統中的其他應用就無法接觸到該 Receiver 了
此外,也可以選擇建立自己的使用許可權,即在清單檔案中新增一個 < permission > 標籤來宣告自定義許可權
<permission
android:name="com.example.permission.receiver"
android:protectionLevel="signature" />
複製程式碼
自定義許可權時必須同時指定 protectionLevel 屬性值,系統根據該屬性值確定自定義許可權的使用方式
屬性值 | 限定方式 |
---|---|
normal | 預設值。較低風險的許可權,對其他應用,系統和使用者來說風險最小。系統在安裝應用時會自動批准授予應用該型別的許可權,不要求使用者明確批准(雖然使用者在安裝之前總是可以選擇檢視這些許可權) |
dangerous | 較高風險的許可權,請求該型別許可權的應用程式會訪問使用者私有資料或對裝置進行控制,從而可能對使用者造成負面影響。因為這種型別的許可引入了潛在風險,所以系統可能不會自動將其授予請求的應用。例如,系統可以向使用者顯示由應用請求的任何危險許可,並且在繼續之前需要確認,或者可以採取一些其他方法來避免使用者自動允許 |
signature | 只有在請求該許可權的應用與宣告許可權的應用使用相同的證書籤名時,系統才會授予許可權。如果證書匹配,系統會自動授予許可權而不通知使用者或要求使用者的明確批准 |
signatureOrSystem | 系統僅授予Android系統映像中與宣告許可權的應用使用相同的證書籤名的應用。請避免使用此選項,“signature”級別足以滿足大多數需求,“signatureOrSystem”許可權用於某些特殊情況 |
首先,新建一個新的工程,在它的清單檔案中建立一個自定義許可權,並宣告該許可權。protectionLevel 屬性值設為“signature”
<permission
android:name="com.example.permission.receiver"
android:protectionLevel="signature" />
<uses-permission android:name="com.example.permission.receiver" />
複製程式碼
然後,傳送含有該許可權宣告的 Broadcast 。這樣,只有使用相同證書籤名且宣告該許可權的應用才能接收到該 Broadcast 了
private final String PERMISSION_PRIVATE = "com.example.permission.receiver";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void sendPermissionBroadcast(View view) {
sendBroadcast(new Intent("Hi"), PERMISSION_PRIVATE);
}
複製程式碼
回到之前的工程
首先在清單檔案中宣告許可權
<uses-permission android:name="com.example.permission.receiver" />
複製程式碼
建立一個 BroadcastReceiver
public class PermissionReceiver extends BroadcastReceiver {
private final String TAG = "PermissionReceiver";
public PermissionReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "接收到了私有許可權廣播");
}
}
複製程式碼
然後註冊廣播接收器。因為 Android Studio 在除錯的時候會使用相同的證書為每個應用簽名,所以,在之前新安裝的App傳送出廣播後,PermissionReceiver 就會輸出 Log 日誌
private final String PERMISSION_PRIVATE = "com.example.permission.receiver";
private PermissionReceiver permissionReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IntentFilter intentFilter1 = new IntentFilter("Hi");
permissionReceiver = new PermissionReceiver();
registerReceiver(permissionReceiver, intentFilter1, PERMISSION_PRIVATE, null);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(permissionReceiver);
}
複製程式碼
五、實戰演練
5.1、監聽網路狀態變化
首先需要一個用來監測當前網路狀態的工具類
/**
* Created by 葉應是葉 on 2017/2/21.
*/
public class NetworkUtils {
/**
* 標記當前網路狀態,分別是:移動資料、Wifi、未連線、網路狀態已公佈
*/
public enum State {
MOBILE, WIFI, UN_CONNECTED, PUBLISHED
}
/**
* 為了避免因多次接收到廣播反覆提醒的情況而設定的標誌位,用於快取收到新的廣播前的網路狀態
*/
private static State tempState;
/**
* 獲取當前網路連線狀態
*
* @param context Context
* @return 網路狀態
*/
public static State getConnectState(Context context) {
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
State state = State.UN_CONNECTED;
if (networkInfo != null && networkInfo.isAvailable()) {
if (isMobileConnected(context)) {
state = State.MOBILE;
} else if (isWifiConnected(context)) {
state = State.WIFI;
}
}
if (state.equals(tempState)) {
return State.PUBLISHED;
}
tempState = state;
return state;
}
private static boolean isMobileConnected(Context context) {
return isConnected(context, ConnectivityManager.TYPE_MOBILE);
}
private static boolean isWifiConnected(Context context) {
return isConnected(context, ConnectivityManager.TYPE_WIFI);
}
private static boolean isConnected(Context context, int type) {
//getAllNetworkInfo() 在 API 23 中被棄用
//getAllNetworks() 在 API 21 中才新增
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
NetworkInfo[] allNetworkInfo = manager.getAllNetworkInfo();
for (NetworkInfo info : allNetworkInfo) {
if (info.getType() == type) {
return info.isAvailable();
}
}
} else {
Network[] networks = manager.getAllNetworks();
for (Network network : networks) {
NetworkInfo networkInfo = manager.getNetworkInfo(network);
if (networkInfo.getType() == type) {
return networkInfo.isAvailable();
}
}
}
return false;
}
}
複製程式碼
然後宣告一個 BroadcastReceiver ,在onReceive() 方法中用Log輸出當前網路狀態
public class NetworkReceiver extends BroadcastReceiver {
private static final String TAG = "NetworkReceiver";
public NetworkReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
switch (NetworkUtils.getConnectState(context)) {
case MOBILE:
Log.e(TAG, "當前連線了移動資料");
break;
case WIFI:
Log.e(TAG, "當前連線了Wifi");
break;
case UN_CONNECTED:
Log.e(TAG, "當前沒有網路連線");
break;
}
}
}
複製程式碼
在清單檔案中註冊廣播接收器,“android.net.conn.CONNECTIVITY_CHANGE”是系統預定義好的 action 值,只要系統網路狀態發生變化,NetworkReceiver 就能收到廣播
<receiver android:name=".NetworkReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
複製程式碼
此外,還要申請檢視網路狀態的許可權
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
複製程式碼
5.2、監聽電量變化
因為系統規定監聽電量變化的廣播接收器不能靜態註冊,所以這裡只能使用動態註冊的方式了
private final String TAG = "MainActivity";
private BroadcastReceiver batteryReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
batteryReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 當前電量
int currentBattery = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
// 總電量
int totalBattery = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
Log.e(TAG, "當前電量:" + currentBattery + "-總電量:" + totalBattery);
}
};
registerReceiver(batteryReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(batteryReceiver);
}
複製程式碼
在 onReceive(Context , Intent ) 中的 Intent 值包含了一些額外資訊,可以取出當前電量和總電量
為了方便檢視電量變化,可以在模擬器的“extended controls”皮膚中主動地改變模擬器的電量,檢視Log輸出
5.3、應用安裝更新解除安裝監聽
首先,建立 BroadcastReceiver
public class AppReceiver extends BroadcastReceiver {
private final String TAG = "AppReceiver";
public AppReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
//判斷廣播型別
String action = intent.getAction();
//獲取包名
Uri appName = intent.getData();
if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
Log.e(TAG, "安裝了:" + appName);
} else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
Log.e(TAG, "更新了:" + appName);
} else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
Log.e(TAG, "解除安裝了:" + appName);
}
}
}
複製程式碼
註冊廣播接收器
<receiver android:name=".train.AppReceiver">
<intent-filter>
<!--安裝應用-->
<action android:name="android.intent.action.PACKAGE_ADDED" />
<!--更新應用-->
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<!--解除安裝應用-->
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<!--攜帶包名-->
<data android:scheme="package" />
</intent-filter>
</receiver>
複製程式碼