android進階3step1:Android元件通訊——廣播接收者BroadCast
轉:https://www.jianshu.com/p/ca3d87a4cdf3
前言
BroadcastReceiver
(廣播接收器),屬於Android
四大元件之一Broadcast
是一種廣泛應用在程式之間傳輸資訊的機制,BroadcastReceiver
是對傳送出來的廣播進行過濾接收並響應的元件
- 在
Android
開發中,BroadcastReceiver
的應用場景非常多 - 今天,我將詳細講解關於
BroadcastReceiver
的一切相關知識
目錄
示意圖
1. 定義
即 廣播,是一個全域性的監聽器,屬於Android
Android
廣播分為兩個角色:廣播發送者、廣播接收者
2. 作用
監聽 / 接收 應用 App
發出的廣播訊息,並 做出響應
3. 應用場景
Android
不同元件間的通訊(含 :應用內 / 不同應用之間)- 多執行緒通訊
- 與
Android
系統在特定情況下的通訊
如:電話呼入時、網路可用、監控電量時
4. 實現原理
4.1 採用的模型
Android
中的廣播使用了設計模式中的觀察者模式:基於訊息的釋出 / 訂閱事件模型
因此,Android將廣播的傳送者 和 接收者 解耦
,使得系統方便整合,更易擴充套件
4.2 模型講解
-
模型中有3個角色:
- 訊息訂閱者(廣播接收者)
- 訊息釋出者(廣播發布者)
- 訊息中心(
AMS
,即Activity Manager Service
)
-
示意圖 & 原理如下
5. 使用流程
- 使用流程如下:
示意圖
- 下面,我將一步步介紹如何使用
BroadcastReceiver
即上圖中的 開發者手動完成部分
5.1 自定義廣播接收者BroadcastReceiver
- 繼承
BroadcastReceivre
- 必須複寫抽象方法
onReceive()
方法
- 廣播接收器接收到相應廣播後,會自動回撥
onReceive()
方法- 一般情況下,
onReceive
方法會涉及 與 其他元件之間的互動,如傳送Notification
、啟動Service
等- 預設情況下,廣播接收器執行在
UI
執行緒,因此,onReceive()
方法不能執行耗時操作(10s左右),否則將導致ANR
如果需要進行耗時操作,應該通過傳送intent給Service讓Service來完成 或者使用handler傳送訊息,觸發新的activity中的handler執行
- 程式碼範例
mBroadcastReceiver.java
// 繼承BroadcastReceivre基類
public class mBroadcastReceiver extends BroadcastReceiver {
// 複寫onReceive()方法
// 接收到廣播後,則自動呼叫該方法
@Override
public void onReceive(Context context, Intent intent) {
//寫入接收廣播後的操作
}
}
5.2 廣播接收器註冊
註冊的方式分為兩種:靜態註冊、動態註冊
5.2.1 靜態註冊(android 8.0之後取消靜態註冊!!!)
- 註冊方式:在AndroidManifest.xml裡通過<receive>標籤宣告
- 屬性說明:
<receiver
android:enabled=["true" | "false"]
//此broadcastReceiver能否接收其他App的發出的廣播
//預設值是由receiver中有無intent-filter決定的:如果有intent-filter,預設值為true,否則為false
android:exported=["true" | "false"]
android:icon="drawable resource"
android:label="string resource"
//繼承BroadcastReceiver子類的類名
android:name=".mBroadcastReceiver"
//具有相應許可權的廣播發送者傳送的廣播才能被此BroadcastReceiver所接收;
android:permission="string"
//BroadcastReceiver執行所處的程序
//預設為app的程序,可以指定獨立的程序
//注:Android四大基本元件都可以通過此屬性指定自己的獨立程序
android:process="string" >
//用於指定此廣播接收器將接收的廣播型別
//本示例中給出的是用於接收網路狀態改變時發出的廣播
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
- 註冊示例
<receiver
//此廣播接收者類是mBroadcastReceiver
android:name=".mBroadcastReceiver" >
//用於接收網路狀態改變時發出的廣播
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
當此 App
首次啟動時,系統會自動例項化mBroadcastReceiver
類,並註冊到系統中。
5.2.2 動態註冊(推薦)
-
註冊方式:在程式碼中呼叫
Context.registerReceiver()
方法 -
具體程式碼如下:
例項化BroadcastReceiver子類
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("TAG", intent.getAction());
}
};
// 選擇在Activity生命週期方法中的onResume()中註冊
@Override
protected void onResume(){
super.onResume();
// 1. IntentFilter
IntentFilter intentFilter = new IntentFilter();
// 2. 設定接收廣播的型別
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
// 3. 動態註冊:呼叫Context的registerReceiver()方法
registerReceiver(mBroadcastReceiver, intentFilter);
}
// 註冊廣播後,要在相應位置記得銷燬廣播
// 即在onPause() 中unregisterReceiver(mBroadcastReceiver)
// 當此Activity例項化時,會動態將MyBroadcastReceiver註冊到系統中
// 當此Activity銷燬時,動態註冊的MyBroadcastReceiver將不再接收到相應的廣播。
@Override
protected void onPause() {
super.onPause();
//銷燬在onResume()方法中的廣播
unregisterReceiver(mBroadcastReceiver);
}
}
特別注意
- 動態廣播最好在
Activity
的onResume()
註冊、onPause()
登出。 - 原因:
- 對於動態廣播,有註冊就必然得有登出,否則會導致記憶體洩露
重複註冊、重複登出也不允許
Activity
生命週期如下:
Activity生命週期
Activity生命週期的方法是成對出現的:
- onCreate() & onDestory()
- onStart() & onStop()
- onResume() & onPause()
在onResume()註冊、onPause()登出是因為onPause()在App死亡前一定會被執行,從而保證廣播在App死亡前一定會被登出,從而防止記憶體洩露。
- 不在onCreate() & onDestory() 或 onStart() & onStop()註冊、登出是因為:
當系統因為記憶體不足(優先順序更高的應用需要記憶體,請看上圖紅框)要回收Activity佔用的資源時,Activity在執行完onPause()方法後就會被銷燬,有些生命週期方法onStop(),onDestory()就不會執行。當再回到此Activity時,是從onCreate方法開始執行。- 假設我們將廣播的登出放在onStop(),onDestory()方法裡的話,有可能在Activity被銷燬後還未執行onStop(),onDestory()方法,即廣播仍還未登出,從而導致記憶體洩露。
- 但是,onPause()一定會被執行,從而保證了廣播在App死亡前一定會被登出,從而防止記憶體洩露。
BroadCast receiver的生命週期
5.2.3 兩種註冊方式的區別(所有廣播都可以動態註冊,但是靜態註冊有些廣播不支援)
示意圖
5.3 廣播發送者向AMS傳送廣播
5.3.1 廣播的傳送
- 廣播 是 用”意圖(
Intent
)“標識 - 定義廣播的本質 = 定義廣播所具備的“意圖(
Intent
)” - 廣播發送 = 廣播發送者 將此廣播的“意圖(
Intent
)”通過sendBroadcast()方法傳送出去
5.3.2 廣播的型別
廣播的型別主要分為5類:
- 普通廣播(
Normal Broadcast
) - 系統廣播(
System Broadcast
) - 有序廣播(
Ordered Broadcast
) - 粘性廣播(
Sticky Broadcast
) - App應用內廣播(
Local Broadcast
)
具體說明如下:
1. 普通廣播(Normal Broadcast)
即 開發者自身定義 intent
的廣播(最常用)。傳送廣播使用如下:
Intent intent = new Intent();
//對應BroadcastReceiver中intentFilter的action
intent.setAction(BROADCAST_ACTION);
//傳送廣播
sendBroadcast(intent);
- 若被註冊了的廣播接收者中註冊時
intentFilter
的action
與上述匹配,則會接收此廣播(即進行回撥onReceive()
)。如下mBroadcastReceiver
則會接收上述廣播
<receiver
//此廣播接收者類是mBroadcastReceiver
android:name=".mBroadcastReceiver" >
//用於接收網路狀態改變時發出的廣播
<intent-filter>
<action android:name="BROADCAST_ACTION" />
</intent-filter>
</receiver>
- 若傳送廣播有相應許可權,那麼廣播接收者也需要相應許可權
2. 系統廣播(System Broadcast)
- Android中內建了多個系統廣播:只要涉及到手機的基本操作(如開機、網路狀態變化、拍照等等),都會發出相應的廣播
- 每個廣播都有特定的Intent - Filter(包括具體的action),Android系統廣播action如下:
系統操作 | action |
---|---|
監聽網路變化 | android.net.conn.CONNECTIVITY_CHANGE |
關閉或開啟飛航模式 | Intent.ACTION_AIRPLANE_MODE_CHANGED |
充電時或電量發生變化 | Intent.ACTION_BATTERY_CHANGED(動態註冊) |
電池電量低 | Intent.ACTION_BATTERY_LOW |
電池電量充足(即從電量低變化到飽滿時會發出廣播 | Intent.ACTION_BATTERY_OKAY |
系統啟動完成後(僅廣播一次) | Intent.ACTION_BOOT_COMPLETED |
按下照相時的拍照按鍵(硬體按鍵)時 | Intent.ACTION_CAMERA_BUTTON |
螢幕鎖屏 | Intent.ACTION_CLOSE_SYSTEM_DIALOGS |
裝置當前設定被改變時(介面語言、裝置方向等) | Intent.ACTION_CONFIGURATION_CHANGED |
插入耳機時 | Intent.ACTION_HEADSET_PLUG |
未正確移除SD卡但已取出來時(正確移除方法:設定--SD卡和裝置記憶體--解除安裝SD卡) | Intent.ACTION_MEDIA_BAD_REMOVAL |
插入外部儲存裝置(如SD卡) | Intent.ACTION_MEDIA_CHECKING |
成功安裝APK | Intent.ACTION_PACKAGE_ADDED |
成功刪除APK | Intent.ACTION_PACKAGE_REMOVED |
重啟裝置 | Intent.ACTION_REBOOT |
螢幕被關閉 | Intent.ACTION_SCREEN_OFF |
螢幕被開啟 | Intent.ACTION_SCREEN_ON |
關閉系統時 | Intent.ACTION_SHUTDOWN |
重啟裝置 | Intent.ACTION_REBOOT |
注:當使用系統廣播時,只需要在註冊廣播接收者時定義相關的action即可,並不需要手動傳送廣播,當系統有相關操作時會自動進行系統廣播
3. 有序廣播(Ordered Broadcast)
- 定義
傳送出去的廣播被廣播接收者按照先後順序接收
有序是針對廣播接收者而言的
-
廣播接受者接收廣播的順序規則(同時面向靜態和動態註冊的廣播接受者)
- 按照Priority屬性值從大-小排序;
- Priority屬性相同者,動態註冊的廣播優先;
-
特點
- 接收廣播按順序接收
- 先接收的廣播接收者可以對廣播進行截斷,即後接收的廣播接收者不再接收到此廣播;
- 先接收的廣播接收者可以對廣播進行修改,那麼後接收的廣播接收者將接收到被修改後的廣播
-
具體使用
有序廣播的使用過程與普通廣播非常類似,差異於廣播的傳送方式:
sendOrderedBroadcast(intent);
兩種廣播的對比:
無序廣播(普通廣播) | 有序廣播 | |
方法呼叫 | Context.sendBroadcast() | Context.sendOrderBroadcast() |
優缺點 | Receiver優先順序不確定 | 按優先順序不同,優先receiver可以對資料進行處理 |
不可以被攔截 不可以被終止 不可以被修改 |
可以被攔截 可以被終止 可以被修改 |
|
接收者不能將結果穿過下一個接收者,也無法終止廣播 | 接收者能將結果穿過下一個接收者,也可以終止廣播 | |
效率 | 高 | 低 |
4. App應用內廣播(Local Broadcast)
-
背景
Android中的廣播可以跨App直接通訊(exported對於有intent-filter情況下預設值為true) -
衝突
可能出現的問題:- 其他App針對性發出與當前App intent-filter相匹配的廣播,由此導致當前App不斷接收廣播並處理;
- 其他App註冊與當前App一致的intent-filter用於接收廣播,獲取廣播具體資訊;
即會出現安全性 & 效率性的問題。
-
解決方案
使用App應用內廣播(Local Broadcast)
- App應用內廣播可理解為一種區域性廣播,廣播的傳送者和接收者都同屬於一個App。
- 相比於全域性廣播(普通廣播),App應用內廣播優勢體現在:安全性高 & 效率高
-
具體使用1 - 將全域性廣播設定成區域性廣播
- 註冊廣播時將exported屬性設定為false,使得非本App內部發出的此廣播不被接收;
- 在廣播發送和接收時,增設相應許可權permission,用於許可權驗證;
- 傳送廣播時指定該廣播接收器所在的包名,此廣播將只會傳送到此包中的App內與之相匹配的有效廣播接收器中。
通過intent.setPackage(packageName)指定報名
-
具體使用2 - 使用封裝好的LocalBroadcastManager類
使用方式上與全域性廣播幾乎相同,只是註冊/取消註冊廣播接收器和傳送廣播時將引數的context變成了LocalBroadcastManager的單一例項
注:對於LocalBroadcastManager方式傳送的應用內廣播,只能通過LocalBroadcastManager動態註冊,不能靜態註冊
//註冊應用內廣播接收器
//步驟1:例項化BroadcastReceiver子類 & IntentFilter mBroadcastReceiver
mBroadcastReceiver = new mBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
//步驟2:例項化LocalBroadcastManager的例項
localBroadcastManager = LocalBroadcastManager.getInstance(this);
//步驟3:設定接收廣播的型別
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
//步驟4:呼叫LocalBroadcastManager單一例項的registerReceiver()方法進行動態註冊
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
//取消註冊應用內廣播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
//傳送應用內廣播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent);
5. 粘性廣播(Sticky Broadcast)
由於在Android5.0 & API 21中已經失效,所以不建議使用,在這裡也不作過多的總結。
6. 特別注意
對於不同註冊方式的廣播接收器回撥OnReceive(Context context,Intent intent)中的context返回值是不一樣的:
- 對於靜態註冊(全域性+應用內廣播),回撥onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
- 對於全域性廣播的動態註冊,回撥onReceive(context, intent)中的context返回值是:Activity Context;
- 對於應用內廣播的動態註冊(LocalBroadcastManager方式),回撥onReceive(context, intent)中的context返回值是:Application Context。
- 對於應用內廣播的動態註冊(非LocalBroadcastManager方式),回撥onReceive(context, intent)中的context返回值是:Activity Context;
7. 總結
- 本文主要介紹了
Android
中四大元件的BroadcastReceiver
的所有知識 - 接下來,我會繼續介紹具體如何在Android中的其他知識,有興趣可以繼續關注
8. 自定義廣播的實現(應用 A點選跳到B執行開啟新的Activity)
應用A:點選button傳送一條廣播
應用B:接收A發來廣播,啟動activity
BroadcastSender的MAinAcitivity.java 廣播發送者
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
//自定義廣播
private final String CUSTOM_ACTION = "com.demo.custom.action";
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
send();
}
/**
* 傳送廣播
*/
public void send() {
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//傳送廣播給receiver
Intent intent = new Intent();
intent.setAction(CUSTOM_ACTION);
MainActivity.this.sendBroadcast(intent);
}
});
}
}
廣播接收者BroadcastReceiver的CustomReceiver.java
android8.0之後取消的靜態註冊,都要進行動態註冊
/**
* 接收者
*/
public class CustomReceiver extends AppCompatActivity {
private final String CUSTOM_ACTION = "com.demo.custom.action";
@Override
protected void onResume() {
super.onResume();
IntentFilter mFilter = new IntentFilter();
mFilter.addAction(CUSTOM_ACTION);
registerReceiver(mBroadcastReceiver, mFilter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mBroadcastReceiver);
}
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.e("TAG", "Action=" + intent.getAction());
//啟動Receiver的MainActivity
Intent startIntent = new Intent();
startIntent.setClass(context, MainActivity.class);
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(startIntent);
}
};
}
效果: 注意兩個應用都要開啟狀態才行
8. 專案案例(電池監控器)
MainActivity.java
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private BroadcastReceiver mBroadcastReceiver;
private ProgressBar bar;
private TextView tv_level;
private TextView tv_health;
private TextView tv_status;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBroadcastReceiver = new BroadcastReceiver();
initView();
}
/**
* 初始檢視
*/
private void initView() {
bar = findViewById(R.id.pb_level);
tv_level = findViewById(R.id.tv_level);
tv_health = findViewById(R.id.tv_health);
tv_status = findViewById(R.id.tv_status);
}
@Override
protected void onResume() {
super.onResume();
registerBatteryInfo();
}
@Override
protected void onPause() {
super.onPause();
unregisterBatteryInfo();
}
/**
* 註冊廣播
*/
private void registerBatteryInfo() {
IntentFilter mFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(mBroadcastReceiver, mFilter);
}
/**
* 登出廣播
*/
private void unregisterBatteryInfo() {
unregisterReceiver(mBroadcastReceiver);
}
class BroadcastReceiver extends android.content.BroadcastReceiver {
int health;
int level;
int status;
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
Log.e("TAG", "接收到電池改變的廣播啦");
//具體的更多資訊可以查BatteryManager的API
//電池健康
health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_GOOD);
//電池電量
level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
//電池狀態
status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, 1);
//改變UI的方法
changeView(health, level, status);
}
}
/**
* 實時更新UI
*
* @param health
* @param level
* @param status
*/
private void changeView(int health, int level, int status) {
String health_Info = "";
String status_Info = "";
switch (health) {
case BatteryManager.BATTERY_HEALTH_UNKNOWN:
health_Info = "未知";
break;
case BatteryManager.BATTERY_HEALTH_GOOD:
health_Info = "健康";
break;
case BatteryManager.BATTERY_HEALTH_OVERHEAT:
health_Info = "過熱";
break;
case BatteryManager.BATTERY_HEALTH_DEAD:
health_Info = "死了";
break;
case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
health_Info = "過壓";
break;
case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
health_Info = "失敗";
break;
case BatteryManager.BATTERY_HEALTH_COLD:
health_Info = "冷了";
break;
}
tv_health.setText("電池健康程度:" + health_Info);
tv_level.setText("電池電量:" + level);
bar.setProgress(level);
switch (status) {
case BatteryManager.BATTERY_STATUS_UNKNOWN:
status_Info = "未知";
break;
case BatteryManager.BATTERY_STATUS_CHARGING:
status_Info = "充電中";
break;
case BatteryManager.BATTERY_STATUS_DISCHARGING:
status_Info = "未充電";
break;
case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
status_Info = "不充電";
break;
case BatteryManager.BATTERY_STATUS_FULL:
status_Info = "充滿了";
break;
}
tv_status.setText("電池狀態:" + status_Info);
}
}
}