在 Android 系統中,廣播(Broadcast)是在組件之間傳播數據的一種機制,這些組件能夠位於不一樣的進程中,起到進程間通訊的做用android
BroadcastReceiver 是對發送出來的 Broadcast 進行過濾、接受和響應的組件。首先將要發送的消息和用於過濾的信息(Action,Category)裝入一個 Intent 對象,而後經過調用 Context.sendBroadcast() 、 sendOrderBroadcast() 方法把 Intent 對象以廣播形式發送出去。 廣播發送出去後,因此已註冊的 BroadcastReceiver 會檢查註冊時的 IntentFilter 是否與發送的 Intent 相匹配,若匹配則會調用 BroadcastReceiver 的 onReceiver() 方法git
因此當咱們定義一個 BroadcastReceiver 的時候,都須要實現 onReceiver() 方法。BroadcastReceiver 的生命週期很短,在執行 onReceiver() 方法時纔有效,一旦執行完畢,該Receiver 的生命週期就結束了github
Android中的廣播分爲兩種類型,標準廣播和有序廣播緩存
靜態註冊即在清單文件中爲 BroadcastReceiver 進行註冊,使用**< receiver >**標籤聲明,並在標籤內用 < intent-filter > 標籤設置過濾器。這種形式的 BroadcastReceiver 的生命週期伴隨着整個應用,若是這種方式處理的是系統廣播,那麼無論應用是否在運行,該廣播接收器都能接收到該廣播安全
首先,繼承 BroadcastReceiver 類建立一個用於接收標準廣播的Receiver,在 onReceive() 方法中取出 Intent 傳遞來的字符串bash
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) 方法app
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>
複製代碼
首先,繼承 BroadcastReceiver 類建立三個用於接收有序廣播的Receiver,名字依次命名爲 OrderReceiver_一、OrderReceiver_二、OrderReceiver_3。此外,既然 Receiver 在接收廣播時存在前後順序,那麼 Receiver 除了能從發送廣播使用的 Intent 接收數據外,優先級高的 Receiver 也能在處理完操做後向優先級低的 Receiver 傳送處理結果ide
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);
}
複製代碼
首先須要一個用來監測當前網絡狀態的工具類
/**
* 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" />
複製代碼
由於系統規定監聽電量變化的廣播接收器不能靜態註冊,因此這裏只能使用動態註冊的方式了
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輸出
首先,建立 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>
複製代碼