基礎知識
framework層廣播相關的邏輯主要在AMS.java和BroadcastQueue.java中,代表一個廣播的是BroadcastRecord,
分類
- 注冊方式:
- 靜態注冊: android manifest檔案注冊,常駐廣播,分發慢,
- 動態注冊:代碼中注冊,非常駐,分發快,
- 接收者:
- 顯示廣播:指定接收方的class型別
- 隱式廣播:只指定action,uri等, android 8.0開始限制了隱式廣播的接收
- 發送方式:
- 有序廣播:串行分發,效率低,可以通過abortBroadcast截斷,在入隊時會根據優先級對receiver進行排序,
- 無序廣播:默認無序;并行分發;不可以被攔截、終止、修改;無優先級問題;傳遞資料用intent.putExtra
靜態注冊的廣播,一律按照串行廣播處理,因為可能會設計到拉起多個行程等,串行廣播才有超時處理,
- 處理型別:
-
前臺廣播:發送時添加Intent.FLAG_RECEIVER_FOREGROUND,超時時間10s
-
后臺廣播:默認后臺,超時時間60s
AMS中前臺廣播和后臺廣播分別有一個廣播佇列,互不干擾,
常見命令
- dumpsys activity broadcasts
- Registered Receivers: 列出所有已注冊的receivers串列
- Receiver Resolver Table: receiver的決議表
- Historical broadcasts [foreground]: 前臺廣播歷史記錄
- Historical broadcasts summary [foreground]: 前臺廣播歷史記錄簡要
- Historical broadcasts [background]:后臺廣播歷史記錄
- Historical broadcasts summary [background]: 后臺廣播歷史記錄簡要
// 已注冊的receiver
* ReceiverList{7657857 3271 com.android.systemui/1000/u0 remote:5d6fdd6}
app=3271:com.android.systemui/1000 pid=3271 uid=1000 user=0 // 行程相關資訊,pid,uid,user等
Filter #0: BroadcastFilter{b28ef44} // 列出當前receiver的所有BroadcastFilter
Action: "android.intent.action.CLOSE_SYSTEM_DIALOGS"
// receiver的查詢表
android.intent.action.CLOSE_SYSTEM_DIALOGS:
BroadcastFilter{cd0b64d 1000/u0 ReceiverList{5cd41e4 2477 system/1000/u0 local:7b2f177}}
BroadcastFilter{766ac91 1000/u0 ReceiverList{b2bd6b8 2477 system/1000/u0 local:dd6451b}}
BroadcastFilter{707a39e 1000/u-1 ReceiverList{6db3dd9 2477 system/1000/u-1 local:28e4020}}
BroadcastFilter{921cf0a 1000/u0 ReceiverList{a168375 3271 com.android.systemui/1000/u0 remote:792eaac}}
BroadcastFilter{eb0f625 1000/u-1 ReceiverList{f9ba1c 3271 com.android.systemui/1000/u-1 remote:311c78f}}
BroadcastFilter{b28ef44 1000/u0 ReceiverList{**7657857** 3271 com.android.systemui/1000/u0 remote:5d6fdd6}}
BroadcastFilter{3dc5af6 1000/u0 ReceiverList{9ad5b91 2477 system/1000/u0 local:ab3e1b8}}
BroadcastFilter{84a1c2 1000/u0 ReceiverList{27a8e0d 5062 com.miui.securitycenter.remote/1000/u0 remote:49ca0a4}}
BroadcastFilter{6151148 1000/u0 ReceiverList{6ac8feb 5062 com.miui.securitycenter.remote/1000/u0 remote:17c273a}}
BroadcastFilter{1525a45 1000/u0 ReceiverList{caf9abc 5062 com.miui.securitycenter.remote/1000/u0 remote:aa646af}}
Historical Broadcast foreground #1: // 前臺廣播佇列第一條廣播
BroadcastRecord{8888912 u0 com.xiaomi.mi_connect_service.mi_mover_endpoint_found} to user 0 // user 0
Intent { act=com.xiaomi.mi_connect_service.mi_mover_endpoint_found flg=0x11000030 (has extras) } // flag有:FLAG_RECEIVER_FOREGROUND | FLAG_RECEIVER_INCLUDE_BACKGROUND | |FLAG_INCLUDE_STOPPED_PACKAGES | FLAG_EXCLUDE_STOPPED_PACKAGES
extras: Bundle[{STRIPPED=1}]
caller=com.xiaomi.mi_connect_service 9219:com.xiaomi.mi_connect_service/1000 pid=9219 uid=1000 // 發送廣播app的資訊
requiredPermissions=[com.xiaomi.mi_connect_service.permission.RECEIVE_ENDPOINT] appOp=-1 // 廣播要求receiver的權限
enqueueClockTime=2021-09-11 16:04:57.623 dispatchClockTime=2021-09-11 16:04:57.623 // 入隊時間和分發時間
dispatchTime=-1m2s207ms (0 since enq) finishTime=-1m2s164ms (+43ms since disp) // 結束時間 & 分發用時
resultTo=null resultCode=0 resultData=null // 一般resultCode為0且resultTo為null且無” ordered= “表示靜態注冊的無序廣播
nextReceiver=1 receiver=null
Deliver +42ms #0: (manifest) //派發給靜態注冊的receiver
priority=0 preferredOrder=0 match=0x108000 specificIndex=-1 isDefault=false //優先級默認為0,
ActivityInfo: // receiver的資訊
name=com.miui.huanji.ble.MiConnectBleReceiver
packageName=com.miui.huanji
enabled=true exported=true directBootAware=false
resizeMode=RESIZE_MODE_RESIZEABLE
Historical Broadcast background #29: // 后臺廣播佇列第29條
BroadcastRecord{e0ebea0 u-1 android.intent.action.PHONE_STATE{color}} to user -1 // -1代表user_all
Intent { act=android.intent.action.PHONE_STATE flg=0x1000010 (has extras) } // intent資訊
extras: Bundle[\{incoming_number=18381098208, state=IDLE}]
caller=android 1309:system/1000 pid=1309 uid=1000 // 發送廣播app的資訊
requiredPermissions=[android.permission.READ_PHONE_STATE, android.permission.READ_CALL_LOG] appOp=-1 // 廣播要求receiver的權限
enqueueClockTime=2021-06-23 11:41:05.687 dispatchClockTime=2021-06-23 11:41:05.687 // 入隊時間和分發時間
dispatchTime=-1m39s763ms (0 since enq) finishTime=-1m39s755ms (+8ms since disp) // 結束時間 & 分發用時
// 分發每一個receiver的分發資訊以及狀態
// Deliver代表當前receiver已分發到
Deliver 0 #0: BroadcastFilter{12c9c23 1000/u-1 ReceiverList{5b06152 1309 system/1000/u-1 local:4a651dd}}
Deliver 0 #1: BroadcastFilter{672acd7 1000/u0 ReceiverList{5be3456 1780 com.android.systemui/1000/u0 remote:b33e71}}
Deliver 0 #2: BroadcastFilter{f932daf 1000/u-1 ReceiverList{1ef518e 1780 com.android.systemui/1000/u-1 remote:35e2d89}}
Deliver 0 #3: BroadcastFilter{d0d467b 1001/u0 ReceiverList{b4a5e0a 1996 com.android.phone/1001/u0 remote:65bee75}}
Deliver 0 #4: BroadcastFilter{4081988 1002/u0 ReceiverList{661912b 2315 com.xiaomi.bluetooth/1002/u0 remote:34b657a}}
Deliver 0 #5: BroadcastFilter{b6bda66 1000/u0 ReceiverList{73672c1 5261 com.miui.powerkeeper/1000/u0 remote:bc317a8}}
// skip表示由于權限等原因,跳過分發到該app
Skipped 0 #6: BroadcastFilter{ebabe72 10243/u0 ReceiverList{5596f7d 21465 com.smile.gifmaker/10243/u0 remote:96d61d4}}
Skipped 0 #7: BroadcastFilter{f7d90ab 10243/u0 ReceiverList{261b2fa 21465 com.smile.gifmaker/10243/u0 remote:ef8c125}}
Skipped 0 #8: BroadcastFilter{1f05292 10250/u0 ReceiverList{396401d 12339 com.tencent.mobileqq/10250/u0 remote:e8e90f4}}
Skipped 0 #9: BroadcastFilter{d98737f 10250/u0 ReceiverList{469cc9e 12339 com.tencent.mobileqq/10250/u0 remote:b386ad9}}
Skipped 0 #10: BroadcastFilter{4e1588 10250/u0 ReceiverList{851fd2b 29070 com.tencent.mobileqq:tool/10250/u0 remote:64a017a}}
- dumpsys activity broadcast-stats :廣播的統計
android.intent.action.DROPBOX_ENTRY_ADDED:
Number received: 0, skipped: 302 // 分發0次,跳過302次
Total dispatch time: +24s731ms, max: +13s538ms // 總分發用時,和最大分發用時
Package android: 151 times
Bg Check Violation com.google.android.gms: 302 times
常見的一些Intent中的flag
- FLAG_RECEIVER_REGISTERED_ONLY
表示當前廣播只允許動態注冊的receiver接收,避免避免一些系統廣播(如TIME_TICK等)拉起靜態注冊的app行程,
-
FLAG_RECEIVER_REPLACE_PENDING
如果廣播佇列中存在沒有被處理的相同廣播,則直接替換掉之前的廣播,無序再入隊分發 -
FLAG_RECEIVER_FOREGROUND
發送廣播時添加這個flag,廣播將會被添加到前臺廣播佇列中,
AMS中的一些屬性
// 已注冊的動態廣播的receiver,receiver IBinder為key, ReceiverList為value;ReceiverList繼承自ArrayList<BroadcastFilter>
final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
// 記錄著已注冊廣播的receiver的resolver,廣播發送時可根據intent和type等查詢得到對應的動態廣播接收者
final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver
= new IntentResolver<BroadcastFilter, BroadcastFilter>();
BroadcastRecord的一些屬性
int state; //當前廣播狀態,初始是idle
final int[] delivery; // 每一個receiver的處理狀態
final List receivers; // 廣播的接收者串列,包括靜態注冊的ResolveInfo和動態注冊的BroadcastFilter
int nextReceiver; // 下一個要處理的廣播接收者的索引,對應于receivers的索引
IIntentReceiver resultTo; // 有序廣播指定的最后一個接收者
long enqueueClockTime; // 廣播的入隊時間
long dispatchTime; // 分發時間
long receiverTime; //開始派發廣播到app端的時間,用于計算anr超時時間
long finishTime; //結束廣播時間
IBinder receiver; // 當前正在處理的receiver(ReceiverDispatcher.InnerReceiver),有序廣播和靜態注冊的才會賦值
ResolveInfo的一些屬性
public int priority; // 優先級
public int preferredOrder; //偏好優先級
public int match; // 系統評估活動與IntentFilter的匹配情況,
public int specificIndex = -1; //僅當由 queryIntentActivityOptions(ComponentName, Intent[], Intent, int)回傳時設定,這告訴你這個結果來自哪個給定的特定意圖,
public boolean isDefault; //此過濾器指定了Intent.CATEGORY_DEFAULT,這意味著它將被視為用戶可以對此資料執行的默認操作,
廣播的狀態
static final int IDLE = 0; // 廣播的初始狀態,派發時只處理這種狀態的廣播
static final int APP_RECEIVE = 1; // 表示廣播正在派發到app行程,靜態receiver
static final int CALL_IN_RECEIVE = 2; // 表示廣播正在派發到app行程,有序廣播,動態receiver
static final int CALL_DONE_RECEIVE = 3; // 廣播已經派發到app行程,有序廣播,動態receiver
static final int WAITING_SERVICES = 4; // 等待后臺service啟動
廣播接收者的處理狀態
static final int DELIVERY_PENDING = 0; // 默認狀態
static final int DELIVERY_DELIVERED = 1; // 表示已經派發到app行程
static final int DELIVERY_SKIPPED = 2; // 表示由于某種原因,未派發到app行程
static final int DELIVERY_TIMEOUT = 3; // 表示派發到app端,超時未結束派發
廣播不被處理的情況
實際上廣播不被處理的情況有很多種,這里只是列出常遇到的一些情況,
接收端未宣告廣播指定權限
W/BroadcastQueue: Permission Denial: receiving Intent { act=android.intent.action.ORDER_BROADCAST flg=0x10 } to com.example.myapplication/com.example.receive.MyReceiver requires com.android.permission.ORDER_PERMISSION due to sender com.example.testapplication (uid 10255)
在BroadcastQueue的deliverToRegisteredReceiverLocked(動態注冊)或processNextBroadcastLocked(靜態注冊)方法中,會去校驗接收端是否都具備廣播要求的權限,如果不具備,會跳過此次廣播的分發到app端,
if (!skip && r.requiredPermissions != null && r.requiredPermissions.length > 0) {
for (int i = 0; i < r.requiredPermissions.length; i++) {
String requiredPermission = r.requiredPermissions[i];
int perm = mService.checkComponentPermission(requiredPermission,
filter.receiverList.pid, filter.receiverList.uid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
Slog.w(TAG, "Permission Denial: receiving " + r.intent.toString() + " to " + filter.receiverList.app + " (pid=" + filter.receiverList.pid
+ ", uid=" + filter.receiverList.uid + ")" + " requires " + requiredPermission + " due to sender " + r.callerPackage + " (uid " + r.callingUid + ")");
skip = true;
break;
}
android 8.0對隱式廣播的限制
W BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.PACKAGE_RESTARTED dat=package:com.android.soundrecorder flg=0x10 (has extras) } to com.xiaomi.misubscreenui/.receiver.LightDeviceStatusReceiver
限制如下兩種情況下的廣播分發
- Intent中添加了FLAG_RECEIVER_EXCLUDE_BACKGROUND
- Intent中無FLAG_RECEIVER_INCLUDE_BACKGROUND flag的隱式廣播
FLAG_RECEIVER_INCLUDE_BACKGROUND 添加的場景
也就是如下這些隱式廣播處于后臺清單檔案注冊的也能接收到
- ACTION_BOOT_COMPLETED
@BroadcastBehavior(includeBackground = true)
public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
allow-implicit-broadcast 串列內的intent會在broadcastIntentLocked中添加
if (action != null) {
if (getBackgroundLaunchBroadcasts().contains(action)) {
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
}
<!-- Broadcast actions that are currently exempted from O+ background
delivery restrictions. -->
<allow-implicit-broadcast action="android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED" />
<allow-implicit-broadcast action="android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED" />
<allow-implicit-broadcast action="android.intent.action.DATA_SMS_RECEIVED" />
<allow-implicit-broadcast action="android.intent.action.MEDIA_SCANNER_SCAN_FILE" />
<allow-implicit-broadcast action="android.intent.action.PACKAGE_CHANGED" />
<allow-implicit-broadcast action="android.intent.action.SIM_STATE_CHANGED" />
<allow-implicit-broadcast action="android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION" />
<allow-implicit-broadcast action="android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION" />
<allow-implicit-broadcast action="android.provider.Telephony.SECRET_CODE" />
<allow-implicit-broadcast action="android.provider.Telephony.SMS_CB_RECEIVED" />
<allow-implicit-broadcast action="android.provider.Telephony.SMS_DELIVER" />
<allow-implicit-broadcast action="android.provider.Telephony.SMS_RECEIVED" />
<allow-implicit-broadcast action="android.provider.Telephony.SMS_REJECTED" />
<allow-implicit-broadcast action="android.provider.Telephony.WAP_PUSH_DELIVER" />
<allow-implicit-broadcast action="android.provider.Telephony.WAP_PUSH_RECEIVED" />
<allow-implicit-broadcast action="android.telephony.action.CARRIER_CONFIG_CHANGED" />
<allow-implicit-broadcast action="android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED" />
<allow-implicit-broadcast action="android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED" />
<allow-implicit-broadcast action="android.telephony.action.MULTI_SIM_CONFIG_CHANGED" />
<allow-implicit-broadcast action="android.telephony.action.SECRET_CODE" />
<allow-implicit-broadcast action="android.telephony.action.SIM_APPLICATION_STATE_CHANGED" />
<allow-implicit-broadcast action="android.telephony.action.SIM_CARD_STATE_CHANGED" />
<allow-implicit-broadcast action="android.telephony.action.SIM_SLOT_STATUS_CHANGED" />
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj, boolean parallelOnce) {
.....
boolean skip = false;
if (!skip) {
// 檢查當前app的啟動權限
final int allowed = mService.getAppStartModeLocked(
info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false, r.callerPackage);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
skip = true;
// 1. FLAG_RECEIVER_EXCLUDE_BACKGROUND , 2. 隱式廣播的限制
} else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
|| (r.intent.getComponent() == null
&& r.intent.getPackage() == null
&& ((r.intent.getFlags()
& Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
&& !isSignaturePerm(r.requiredPermissions))) {
mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
component.getPackageName());
Slog.w(TAG, "Background execution not allowed: receiving " + r.intent + " to "
+ component.flattenToShortString());
skip = true;
}
}
}
}
行程頻繁crash
W BroadcastQueue: Unable to launch app com.bsp.catchlog/1000 for broadcast Intent { act=android.provider.Telephony.SECRET_CODE dat=android_secret_code://284 flg=0x1400010 }: process is bad
在AppErrors中處理當前crash時會去判斷是否標記當前行程為bad行程,
boolean handleAppCrashLocked(ProcessRecord app, String reason,
String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
.......
if (crashTime != null && now < crashTime + ProcessList.MIN_CRASH_INTERVAL) {
Slog.w(TAG, "Process " + app.info.processName
+ " has crashed too many times: killing!");
EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH,
app.userId, app.info.processName, app.uid);
mService.mAtmInternal.onHandleAppCrash(app.getWindowProcessController());
if (!app.isPersistent()) {
EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.userId, app.uid,
app.info.processName);
if (!app.isolated) {
mBadProcesses.put(app.info.processName, app.uid,
new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
mProcessCrashTimes.remove(app.info.processName, app.uid);
}
app.bad = true;
app.removed = true;
.......
}
而在啟動行程時,ProcessList的startProcessLocked中會去判斷如果時后臺啟動且行程為bad,則回傳null,終止啟動行程
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(processName, info.uid, keepIfLarge);
if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) {
if (mService.mAppErrors.isBadProcessLocked(info)) {
return null;
}
}
BroadcastQueue中processNextBroadcastLocked中行程不存在去創建行程時如果失敗,就會列印上面那行log
if ((r.curApp=mService.startProcessLocked(targetProcess,
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
new HostingRecord("broadcast", r.curComponent),
isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
// MIUI MOD
// (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
(r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false, r.callerPackage))
== null) {
// Ah, this recipient is unavailable. Finish it if necessary,
// and mark the broadcast record as ready for the next.
Slog.w(TAG, "Unable to launch app "
+ info.activityInfo.applicationInfo.packageName + "/"
+ receiverUid + " for broadcast "
+ r.intent + ": process is bad");
logBroadcastReceiverDiscardLocked(r);
finishReceiverLocked(r, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, false);
scheduleBroadcastsLocked();
r.state = BroadcastRecord.IDLE;
return;
}
廣播的三千問
- 有序廣播的receiver在什么時候進行排序?
- 廣播的入隊和分發流程是同步還是異步?
- 靜態注冊的receiver(有序&無序)對應的廣播入有序廣播佇列還是無序(并行)廣播佇列?
- 廣播有哪些狀態?分別代表什么?
- receiver有哪些狀態,分別代表什么?
- 廣播程序中涉及到哪些超時邏輯?
- 系統有幾個廣播佇列?
- 如何發送有序廣播?粘性廣播?前臺廣播?
- android O 加入了后臺啟動限制,主要限制邏輯是?
- 什么情況下發送的廣播不會入隊?
- 通過廣播Receiver拉起行程的行程優先級有什么特點?
- 如何通過log查看廣播的是否分發超時?以及廣播是否派給對應的receiver?分發狀態?
- 如果通過log查看廣播是否是有序廣播?靜態注冊?
- 哪個廣播享受總分發超時豁免?
- 哪些隱式廣播可以派發給后臺行程?
- 有序廣播可以通過什么傳遞資料?無序廣播呢?
- 有序廣播的resultTo(最終接收者)的派發時機?
- 發送受保護的廣播有什么限制?
- 發送sticky廣播有什么限制?
- 如何做到廣播只派發給動態注冊的receiver?
- 派發靜態廣播時,行程不存在,如何處理?
- 廣播入隊前如何獲取靜態receivers?如何獲取動態receivers?
為方便學習了解到更多的Handler知識點,特此我將一些 Android 開發相關的學習檔案、面試題、Android 核心筆記等檔案進行了整理,并上傳之我GitHub專案中,如有需要參考的可以直接去我 GitHub ,希望能幫助到大家學習提升,


轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/300272.html
標籤:其他
