<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
本文以 ActivityManagerService之廣播(1): 註冊與傳送 為基礎,分析“序列”和“並行”廣播的傳送流程,並介紹廣播 ANR 的原理。
// 1. 獲取廣播佇列 final BroadcastQueue queue = broadcastQueueForIntent(intent); // 2. 建立廣播記錄 BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType, requiredPermissions, excludedPermissions, appOp, brOptions, registeredReceivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId, allowBackgroundActivityStarts, backgroundActivityStartsToken, timeoutExempt); // 3. 廣播記錄加入到並行佇列中 queue.enqueueParallelBroadcastLocked(r); // 4. 排程傳送廣播 queue.scheduleBroadcastsLocked();
第3步,把廣播記錄儲存到並行佇列中
// BroadcastQueue.java public void enqueueParallelBroadcastLocked(BroadcastRecord r) { // mParallelBroadcasts 型別為 ArrayList<BroadcastRecord> mParallelBroadcasts.add(r); enqueueBroadcastHelper(r); }
第4步,排程傳送廣播,最終會呼叫如下方法
// BroadcastQueue.java // 此時,引數 fromMsg 為 true,skipOomAdj 為 false final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) { BroadcastRecord r; mService.updateCpuStats(); if (fromMsg) { mBroadcastsScheduled = false; } // 遍歷"並行"廣播佇列 while (mParallelBroadcasts.size() > 0) { r = mParallelBroadcasts.remove(0); r.dispatchTime = SystemClock.uptimeMillis(); r.dispatchClockTime = System.currentTimeMillis(); final int N = r.receivers.size(); for (int i=0; i<N; i++) { Object target = r.receivers.get(i); // 廣播傳送給動態接收器 deliverToRegisteredReceiverLocked(r, (BroadcastFilter) target, false, i); } addBroadcastToHistoryLocked(r); } // ... 省略"序列"廣播的傳送 ... }
雖然名為“並行”廣播,但是仍然是從佇列取出廣播,然後逐個傳送給動態接收器。很顯然,這裡的行為與“並行”的含義並不一致?那麼廣播的“並行”傳送究竟是什麼意思?
接著看“並行”廣播如何傳送給動態接收器的
private void deliverToRegisteredReceiverLocked(BroadcastRecord r, BroadcastFilter filter, boolean ordered, int index) { // ... 省略一大堆的許可權或者異常檢測 ... // 一個廣播可能有多個接收者,因此需要一個陣列來儲存傳送的狀態 r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED; // ordered 目前為 false if (ordered) { // ... } } else if (filter.receiverList.app != null) { // 馬上要傳送廣播給接收方,因此要暫時解凍接收方的程序 mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app); } try { if (filter.receiverList.app != null && filter.receiverList.app.isInFullBackup()) { // ... 處於備份狀態中 ... } else { r.receiverTime = SystemClock.uptimeMillis(); // 儲存允許從後臺啟動activity的token maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r); // 新增到省電模式白名單中 maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options); // 執行廣播的傳送 performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky, r.userId); // parallel broadcasts are fire-and-forget, not bookended by a call to // finishReceiverLocked(), so we manage their activity-start token here if (filter.receiverList.app != null && r.allowBackgroundActivityStarts && !r.ordered) { postActivityStartTokenRemoval(filter.receiverList.app, r); } } // ordered 目前為 false if (ordered) { r.state = BroadcastRecord.CALL_DONE_RECEIVE; } } catch (RemoteException e) { // ... } }
拋開一些細節,直接看 performReceiveLocked()
// BroadcastQueue.java void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver, Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) throws RemoteException { // 動態廣播接收器的程序,應該是存在的 if (app != null) { final IApplicationThread thread = app.getThread(); if (thread != null) { try { // 傳送廣播給接收方程序 thread.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras, ordered, sticky, sendingUser, } catch (RemoteException ex) { // ... } } else { throw new RemoteException("app.thread must not be null"); } } else { // ... } }
很簡單,就是通過程序 attach 的 IApplicationThread 介面,傳送廣播給程序。這個過程,暫時先不分析,後面會分析到。
那麼,現在來回答一下,何為“並行”廣播?其實這個答案,我也是對比了序列廣播的傳送過程,才得出來的。所謂的"並行"傳送,實際上就是把廣播逐個傳送給動態接收器,但是不需要等待前一個接收器反饋處理結果,就可以傳送下一個。而“序列”廣播的傳送,是需要等待前一個廣播接收器反饋處理結果後,才能排程傳送下一個廣播。
// 1.獲取廣播佇列 BroadcastQueue queue = broadcastQueueForIntent(intent); // 2.建立廣播記錄 BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType, requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions, receivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId, allowBackgroundActivityStarts, backgroundActivityStartsToken, timeoutExempt); // 3.廣播記錄加入到序列佇列中 queue.enqueueOrderedBroadcastLocked(r); // 4.排程傳送廣播 queue.scheduleBroadcastsLocked();
第3步,廣播加入到序列佇列中
// BroadcastQueue.java public void enqueueOrderedBroadcastLocked(BroadcastRecord r) { mDispatcher.enqueueOrderedBroadcastLocked(r); enqueueBroadcastHelper(r); }
// BroadcastDispatcher.java void enqueueOrderedBroadcastLocked(BroadcastRecord r) { mOrderedBroadcasts.add(r); }
並行傳送的廣播儲存到 BroadcastQueue#mParallelBroadcasts 中,而序列傳送的廣播儲存到 BroadcastDispatcher#mOrderedBroadcasts 中,為何要這樣設計呢?有興趣的讀者可以研究下。
第4步,“序列”廣播的排程傳送,仍然使用的是 processNextBroadcastLocked() 方法,但是程式碼量是非常的大,下面將把函數分段解析。
processNextBroadcastLocked() 函數有400多行程式碼,這個函數裡有很多東西都可以抽出來的,但是隨著版本的更新,這塊程式碼一直沒有優化過。
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) { BroadcastRecord r; mService.updateCpuStats(); if (fromMsg) { mBroadcastsScheduled = false; } // 把並行廣播傳送給動態接收器 while (mParallelBroadcasts.size() > 0) { // ... } // 1. 處理 receiver 程序正在啟動的情況 if (mPendingBroadcast != null) { // 檢測 receiver 程序是否死亡 boolean isDead; if (mPendingBroadcast.curApp.getPid() > 0) { synchronized (mService.mPidsSelfLocked) { ProcessRecord proc = mService.mPidsSelfLocked.get( mPendingBroadcast.curApp.getPid()); isDead = proc == null || proc.mErrorState.isCrashing(); } } else { final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get( mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid); isDead = proc == null || !proc.isPendingStart(); } if (!isDead) { // 程序仍然存活,結束此次廣播的處理流程,繼續等待 // 等待什麼呢?等待廣播程序起來,並與 AMS 完成 attach application // 在 attach application 的過程中,會完成廣播的傳送 return; } else { // 程序死亡,繼續處理下一個廣播 mPendingBroadcast.state = BroadcastRecord.IDLE; mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex; mPendingBroadcast = null; } }
當傳送一個廣播給 receiver 時,如果 receiver 程序沒有啟動,那麼會先 fork 一個 receiver 程序,然後用 mPendingBroadcast 儲存待傳送的廣播。當 receiver 程序起來的時候,會與 AMS 執行 attach application 過程,在這個過程中,會自動把 mPendingBroadcast 儲存的廣播傳送給 receiver 程序。
因此,這裡檢測到 mPendingBroadcast 不為 null 時,那麼 receiver 程序肯定在啟動中,只要 receiver 程序沒有死亡,就什麼也不用做,因為廣播會自動傳送給 receiver 程序。
接著看下一步的處理
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) { // ... // 把並行廣播傳送給動態接收器 while (mParallelBroadcasts.size() > 0) { // ... } // 1. 處理 receiver 程序正在啟動的情況 if (mPendingBroadcast != null) { // ... } boolean looped = false; // 2. 通過 do-while 迴圈,找到一個現在可以處理的廣播 do { final long now = SystemClock.uptimeMillis(); // 獲取一個待處理的廣播 r = mDispatcher.getNextBroadcastLocked(now); if (r == null) { // ... 沒有廣播需要處理 ... return; } boolean forceReceive = false; // 處理嚴重超時的廣播,有兩種情況 // 一種情況是,在系統還沒有起來前,傳送的廣播得不到執行,發生嚴重超時 // 另外一種情況是,在系統起來後,有一些超時豁免的廣播,發生了嚴重超時 int numReceivers = (r.receivers != null) ? r.receivers.size() : 0; if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) { if ((numReceivers > 0) && (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) { broadcastTimeoutLocked(false); // forcibly finish this broadcast forceReceive = true; r.state = BroadcastRecord.IDLE; } } if (r.state != BroadcastRecord.IDLE) { return; } // 當前廣播因為某種原因,終止處理,然後處理下一個廣播 if (r.receivers == null || r.nextReceiver >= numReceivers || r.resultAbort || forceReceive) { // ... // 通知 BroadcastDispatcher ,不處理這個廣播了 mDispatcher.retireBroadcastLocked(r); r = null; looped = true; // 下一次迴圈,獲取下一個廣播來處理 continue; } // 處理推遲傳送廣播的情況 if (!r.deferred) { final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver)); if (mDispatcher.isDeferringLocked(receiverUid)) { // ... // 儲存推遲傳送的廣播 mDispatcher.addDeferredBroadcast(receiverUid, defer); r = null; looped = true; // 下一次迴圈時,獲取下一個廣播來處理 continue; } } } while (r == null);
先從整體看,通過一個 do-while 迴圈,最終是為了找到下一個處理的廣播。為何要用一個迴圈來尋找呢? 因為廣播可能沒有接收器,或者已經嚴重超時,又或者廣播需要推遲傳送。所以要通過一個迴圈,找到一個能立即傳送的廣播。
由於本文主要是為了分析廣播傳送的整體流程,對於有些細節,只做註釋而不做細緻分析。需要深入研究的讀者,可以在本文的基礎上繼續分析。
繼續接著看下一步
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) { BroadcastRecord r; mService.updateCpuStats(); if (fromMsg) { mBroadcastsScheduled = false; } // 把並行廣播傳送給動態接收器 while (mParallelBroadcasts.size() > 0) { // ... } // 1. 處理 receiver 程序正在啟動的情況 if (mPendingBroadcast != null) { // ... } boolean looped = false; // 2. 通過 do-while 迴圈,找到一個現在可以處理的廣播 do { final long now = SystemClock.uptimeMillis(); // 獲取一個待處理的廣播 r = mDispatcher.getNextBroadcastLocked(now); // ... } while (r == null); // 走到這裡,表示已經獲取了一個現在可以處理的廣播 int recIdx = r.nextReceiver++; // 3. 在傳送廣播之前,先傳送一個超時訊息 r.receiverTime = SystemClock.uptimeMillis(); if (recIdx == 0) { // 在廣播開始傳送給第一個接收器時,記錄傳送的時間 r.dispatchTime = r.receiverTime; r.dispatchClockTime = System.currentTimeMillis(); } if (! mPendingBroadcastTimeoutMessage) { long timeoutTime = r.receiverTime + mConstants.TIMEOUT; setBroadcastTimeoutLocked(timeoutTime); }
在廣播傳送給一個 receiver 之前,會先傳送一個超時訊息。從廣播準備傳送給一個 receiver 算起,到 receiver 處理完廣播,並反饋給 AMS,如果這個時間段超過了一個時間閾值,就會引發 ANR。觸發 ANR 的程式碼設計非常巧妙,後面會具體分析這個過程。
接著看下一步
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) { // ... // 把並行廣播傳送給動態接收器 while (mParallelBroadcasts.size() > 0) { // ... } // 1. 處理 receiver 程序正在啟動的情況 if (mPendingBroadcast != null) { // ... } boolean looped = false; // 2. 通過 do-while 迴圈,找到一個現在可以處理的廣播 do { // ... } while (r == null); int recIdx = r.nextReceiver++; // ... // 3. 在傳送廣播之前,先傳送一個超時訊息 // 當廣播處理超時時,會觸發 ANR if (! mPendingBroadcastTimeoutMessage) { long timeoutTime = r.receiverTime + mConstants.TIMEOUT; setBroadcastTimeoutLocked(timeoutTime); } final BroadcastOptions brOptions = r.options; // 4. 獲取一個 receiver final Object nextReceiver = r.receivers.get(recIdx); // 5. 如果這個接收器是動態接收器,先把廣播傳送給它 // 注意,這裡處理的是有序廣播傳送給動態接收器的情況 if (nextReceiver instanceof BroadcastFilter) { BroadcastFilter filter = (BroadcastFilter)nextReceiver; // 傳送廣播給動態接收器 deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx); if (r.receiver == null || !r.ordered) { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing [" + mQueueName + "]: ordered=" + r.ordered + " receiver=" + r.receiver); r.state = BroadcastRecord.IDLE; scheduleBroadcastsLocked(); } else { if (filter.receiverList != null) { maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r); } } // 注意,把廣播傳送給 動態receiver 後,直接返回 return; }
現在一切就緒,那麼開始獲取一個 receiver,當這個 receiver 是一個動態接收器時,直接傳送廣播給它,這個傳送過程前面已經分析過。
注意,這裡處理的情況是,把有序廣播傳送給動態接收器。並且傳送完成後,直接 return, 也就是結束了此次廣播的傳送流程。
一個廣播可能有多個接收器,為何這裡只傳送給一個動態接收器,就直接返回了? 這就是從“序列”廣播的本質,需要等待當前的廣播接收器處理完廣播,並返回結果後,才能把廣播傳送給下一個廣播接收器。
接著看下一步
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) { // ... // 把並行廣播傳送給動態接收器 while (mParallelBroadcasts.size() > 0) { // ... } // 1. 處理 receiver 程序正在啟動的情況 if (mPendingBroadcast != null) { // ... } boolean looped = false; // 2. 通過 do-while 迴圈,找到一個現在可以處理的廣播 do { final long now = SystemClock.uptimeMillis(); // 獲取一個待處理的廣播 r = mDispatcher.getNextBroadcastLocked(now); // ... } while (r == null); // 走到這裡,表示已經獲取了一個現在可以處理的廣播 int recIdx = r.nextReceiver++; // 3. 在傳送廣播之前,先傳送一個超時訊息 r.receiverTime = SystemClock.uptimeMillis(); // ... if (! mPendingBroadcastTimeoutMessage) { long timeoutTime = r.receiverTime + mConstants.TIMEOUT; setBroadcastTimeoutLocked(timeoutTime); } final BroadcastOptions brOptions = r.options; // 4. 獲取一個 receiver final Object nextReceiver = r.receivers.get(recIdx); // 5. 如果這個接收器是動態接收器,先把廣播傳送給它 // 注意,這裡處理的是有序廣播傳送給動態接收器的情況 if (nextReceiver instanceof BroadcastFilter) { // ... return; } // 走到這裡,表示當前的廣播接收器,是靜態接收器 // 獲取靜態接收器的資訊 ResolveInfo info = (ResolveInfo)nextReceiver; ComponentName component = new ComponentName( info.activityInfo.applicationInfo.packageName, info.activityInfo.name); boolean skip = false; // 6. 檢測是否不需要把廣播傳送給靜態接收器 // ... 省略一大堆的檢測程式碼 ... String targetProcess = info.activityInfo.processName; ProcessRecord app = mService.getProcessRecordLocked(targetProcess, info.activityInfo.applicationInfo.uid); if (!skip) { // 檢測是否允許把廣播傳送給靜態接收器 final int allowed = mService.getAppStartModeLOSP( info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false); // 例如,大於等於 O+ 版本的 app ,不允許廣播傳送給靜態接收器 if (allowed != ActivityManager.APP_START_MODE_NORMAL) { // ephemeral app 會返回這個模式 if (allowed == ActivityManager.APP_START_MODE_DISABLED) { skip = true; } 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()); skip = true; } } } // 跳過當前廣播的傳送 if (skip) { // ... return; }
如果這個 reciever 是靜態接收器,那麼在把廣播傳送給它之前,首先得進行一大堆的檢測。最常見的就是許可權,但是這裡展示了一段 Android O+ 限制廣播傳送給靜態接收器的限制,有興趣的讀者可以詳細分析。
接著看下一步
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) { // ... // 把並行廣播傳送給動態接收器 while (mParallelBroadcasts.size() > 0) { // ... } // 1. 處理 receiver 程序正在啟動的情況 if (mPendingBroadcast != null) { // ... } boolean looped = false; // 2. 通過 do-while 迴圈,找到一個現在可以處理的廣播 do { final long now = SystemClock.uptimeMillis(); // 獲取一個待處理的廣播 r = mDispatcher.getNextBroadcastLocked(now); // ... } while (r == null); // 走到這裡,表示已經獲取了一個現在可以處理的廣播 int recIdx = r.nextReceiver++; // 3. 在傳送廣播之前,先傳送一個超時訊息 r.receiverTime = SystemClock.uptimeMillis(); // ... if (! mPendingBroadcastTimeoutMessage) { long timeoutTime = r.receiverTime + mConstants.TIMEOUT; setBroadcastTimeoutLocked(timeoutTime); } final BroadcastOptions brOptions = r.options; // 4. 獲取一個 receiver final Object nextReceiver = r.receivers.get(recIdx); // 5. 如果這個接收器是動態接收器,先把廣播傳送給它 // 注意,這裡處理的是有序廣播傳送給動態接收器的情況 if (nextReceiver instanceof BroadcastFilter) { // ... return; } // 走到這裡,表示當前的廣播接收器,是靜態接收器 ResolveInfo info = (ResolveInfo)nextReceiver; ComponentName component = new ComponentName( info.activityInfo.applicationInfo.packageName, info.activityInfo.name); boolean skip = false; // 6. 檢測是否不需要把廣播傳送給靜態接收器 // ... 省略一大堆的檢測程式碼 ... String targetProcess = info.activityInfo.processName; ProcessRecord app = mService.getProcessRecordLocked(targetProcess, info.activityInfo.applicationInfo.uid); // ... // 跳過當前廣播的傳送 if (skip) { // ... return; } // 現在可以把廣播傳送給靜態接收器了 // ... // 7. 靜態接收器的程序正在執行,那麼就把廣播傳送給它 if (app != null && app.getThread() != null && !app.isKilled()) { try { app.addPackage(info.activityInfo.packageName, info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats); maybeAddAllowBackgroundActivityStartsToken(app, r); // 傳送廣播給廣播程序 processCurBroadcastLocked(r, app); // 注意,廣播傳送給這個靜態接收器後,直接結束此次廣播的處理 return; } catch (RemoteException e) { // ... } } }
如果沒有限制,那麼現在就可以把廣播傳送給靜態接收器。
如果靜態接收器所在的程序已經執行了,那麼把廣播傳送給這個程序,這個過程與前面傳送廣播給動態接收器的過程非常類似,這裡就不分析了。
注意,這裡把廣播傳送給一個靜態接收器,也是直接 return,懂了吧?
接著往下看
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) { // ... // 把並行廣播傳送給動態接收器 while (mParallelBroadcasts.size() > 0) { // ... } // 1. 處理 receiver 程序正在啟動的情況 if (mPendingBroadcast != null) { // ... } boolean looped = false; // 2. 通過 do-while 迴圈,找到一個現在可以處理的廣播 do { final long now = SystemClock.uptimeMillis(); // 獲取一個待處理的廣播 r = mDispatcher.getNextBroadcastLocked(now); // ... } while (r == null); // 走到這裡,表示已經獲取了一個現在可以處理的廣播 int recIdx = r.nextReceiver++; // 3. 在傳送廣播之前,先傳送一個超時訊息 r.receiverTime = SystemClock.uptimeMillis(); // ... if (! mPendingBroadcastTimeoutMessage) { long timeoutTime = r.receiverTime + mConstants.TIMEOUT; setBroadcastTimeoutLocked(timeoutTime); } final BroadcastOptions brOptions = r.options; // 4. 獲取一個 receiver final Object nextReceiver = r.receivers.get(recIdx); // 5. 如果這個接收器是動態接收器,先把廣播傳送給它 // 注意,這裡處理的是有序廣播傳送給動態接收器的情況 if (nextReceiver instanceof BroadcastFilter) { // ... return; } // 走到這裡,表示當前的廣播接收器,是靜態接收器 ResolveInfo info = (ResolveInfo)nextReceiver; ComponentName component = new ComponentName( info.activityInfo.applicationInfo.packageName, info.activityInfo.name); boolean skip = false; // 6. 檢測是否不需要把廣播傳送給靜態接收器 // ... // 跳過當前廣播的傳送 if (skip) { r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED; r.receiver = null; r.curFilter = null; r.state = BroadcastRecord.IDLE; r.manifestSkipCount++; // 傳送下一個廣播 scheduleBroadcastsLocked(); return; } // 現在可以把廣播傳送給靜態接收器了 // ... // 7. 靜態接收器的程序正在執行,那麼就把廣播傳送給它 if (app != null && app.getThread() != null && !app.isKilled()) { // ... } // 8. 靜態接收器的程序沒有執行,fork it! 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, (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false); // 處理 fork 程序失敗的情況 if (r.curApp == null) { // ... return; } maybeAddAllowBackgroundActivityStartsToken(r.curApp, r); // fork 程序成功,儲存廣播資料,等待程序起來後,再處理這個廣播 mPendingBroadcast = r; mPendingBroadcastRecvIndex = recIdx; }
剛才已經處理了靜態接收器的程序存在的情況,那麼現在處理程序不存在的情況,因此首先得 fork 程序。當成功 fork 程序後,儲存待傳送的廣播的資料,例如,用 mPendingBroadcast 儲存廣播,然後當程序啟動時,與 AMS 進行 attach application 時,會自動把廣播傳送給該程序。這個過程後面會分析。
注意,此時函數已經結束,而廣播正在傳送給一個正在啟動的程序。很顯然,需要等待這個廣播的處理結果,才能繼續下一個廣播的傳送,這也符合“序列”廣播的定義。
剛才,我們分析到一個過程,當靜態接收器所在的程序沒有啟動的時候,首先 fork 程序,那麼廣播之後是如何傳送給程序的呢?
首先,我們知道當程序啟動後,會執行 attach application 過程,最終會呼叫 AMS 如下方法
// ActivityManagerService.java private boolean attachApplicationLocked(@NonNull IApplicationThread thread, int pid, int callingUid, long startSeq) { // ... try { // ... if (app.getIsolatedEntryPoint() != null) { // ... } else if (instr2 != null) { // ... } else { // 初始化程序環境 thread.bindApplication(processName, appInfo, providerList, null, profilerInfo, null, null, null, testMode, mBinderTransactionTrackingEnabled, enableTrackAllocation, isRestrictedBackupMode || !normalMode, app.isPersistent(), new Configuration(app.getWindowProcessController().getConfiguration()), app.getCompat(), getCommonServicesLocked(app.isolated), mCoreSettingsObserver.getCoreSettingsLocked(), buildSerial, autofillOptions, contentCaptureOptions, app.getDisabledCompatChanges(), serializedSystemFontMap); } // ... } catch (Exception e) { // ... } // .... // 處理正在等待宿主程序起來的廣播 if (!badApp && isPendingBroadcastProcessLocked(pid)) { try { // 傳送佇列中正在等待程序起來的廣播 didSomething |= sendPendingBroadcastsLocked(app); checkTime(startTime, "attachApplicationLocked: after sendPendingBroadcastsLocked"); } catch (Exception e) { // ... } } // ... return true; } boolean sendPendingBroadcastsLocked(ProcessRecord app) { boolean didSomething = false; for (BroadcastQueue queue : mBroadcastQueues) { didSomething |= queue.sendPendingBroadcastsLocked(app); } return didSomething; }
看到了,AMS 首先對程序進行了初始化,然後就會把等待程序啟動的廣播,傳送給它。
// BroadcastQueue.java public boolean sendPendingBroadcastsLocked(ProcessRecord app) { boolean didSomething = false; // mPendingBroadcast 儲存的就是等待程序啟動啟動後,需要傳送的廣播。 final BroadcastRecord br = mPendingBroadcast; if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) { if (br.curApp != app) { Slog.e(TAG, "App mismatch when sending pending broadcast to " + app.processName + ", intended target is " + br.curApp.processName); return false; } try { mPendingBroadcast = null; // 傳送廣播給程序 processCurBroadcastLocked(br, app); didSomething = true; } catch (Exception e) { // ... } } return didSomething; }
mPendingBroadcast 儲存的就是等待程序啟動啟動後,需要傳送的廣播。現在程序已經啟動,立即傳送廣播
// BroadcastQueue.java private final void processCurBroadcastLocked(BroadcastRecord r, ProcessRecord app) throws RemoteException { final IApplicationThread thread = app.getThread(); if (thread == null) { throw new RemoteException(); } if (app.isInFullBackup()) { skipReceiverLocked(r); return; } // 更新正在處理廣播的 receiver 資料 r.receiver = thread.asBinder(); r.curApp = app; // 儲存當前正在執行的 receiver final ProcessReceiverRecord prr = app.mReceivers; prr.addCurReceiver(r); app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER); mService.updateLruProcessLocked(app, false, null); mService.enqueueOomAdjTargetLocked(app); mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER); r.intent.setComponent(r.curComponent); boolean started = false; try { mService.notifyPackageUse(r.intent.getComponent().getPackageName(), PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER); // 通知程序啟動 receiver 來處理廣播 thread.scheduleReceiver(new Intent(r.intent), r.curReceiver, mService.compatibilityInfoForPackage(r.curReceiver.applicationInfo), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId, app.mState.getReportedProcState()); started = true; } finally { if (!started) { // ... } } }
現在 AMS 通知 receiver 所在的程序來處理廣播
// ActivityThread.java private class ApplicationThread extends IApplicationThread.Stub { private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s"; public final void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras, boolean sync, int sendingUser, int processState) { updateProcessState(processState, false); // 廣播封包裝成 ReceiverData ReceiverData r = new ReceiverData(intent, resultCode, data, extras, sync, false, mAppThread.asBinder(), sendingUser); r.info = info; r.compatInfo = compatInfo; sendMessage(H.RECEIVER, r); }
最終呼叫 handleReceiver() 處理廣播資料
private void handleReceiver(ReceiverData data) { unscheduleGcIdler(); String component = data.intent.getComponent().getClassName(); LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); IActivityManager mgr = ActivityManager.getService(); Application app; BroadcastReceiver receiver; ContextImpl context; try { // 1. 建立 Application 物件,並呼叫 Application#onCreate() app = packageInfo.makeApplication(false, mInstrumentation); // ... // 2. 建立 BroadcastReceiver 物件 receiver = packageInfo.getAppFactory() .instantiateReceiver(cl, data.info.name, data.intent); } catch (Exception e) { // ... } try { sCurrentBroadcastIntent.set(data.intent); receiver.setPendingResult(data); // 3. 執行 BroadcastReceiver#onReceive() receiver.onReceive(context.getReceiverRestrictedContext(), data.intent); } catch (Exception e) { // ... } finally { sCurrentBroadcastIntent.set(null); } // 4. 返回廣播的處理結果給 AMS if (receiver.getPendingResult() != null) { data.finish(); } }
這裡的過程很清晰明瞭吧,直接看最後一步,把廣播的處理結果反饋給 AMS
// BroadcastReceiver.java public final void finish() { if (mType == TYPE_COMPONENT) { final IActivityManager mgr = ActivityManager.getService(); if (QueuedWork.hasPendingWork()) { // ... } else { sendFinished(mgr); } } else if (mOrderedHint && mType != TYPE_UNREGISTERED) { // ... } } public void sendFinished(IActivityManager am) { synchronized (this) { if (mFinished) { throw new IllegalStateException("Broadcast already finished"); } mFinished = true; try { if (mResultExtras != null) { mResultExtras.setAllowFds(false); } if (mOrderedHint) { // 有序廣播的反饋 am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras, mAbortBroadcast, mFlags); } else { // 非有序廣播的費奎 am.finishReceiver(mToken, 0, null, null, false, mFlags); } } catch (RemoteException ex) { } } }
現在看下 AMS 如何處理這個反饋的結果
// ActivityManagerService.java public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle resultExtras, boolean resultAbort, int flags) { // ... final long origId = Binder.clearCallingIdentity(); try { boolean doNext = false; BroadcastRecord r; BroadcastQueue queue; synchronized(this) { if (isOnOffloadQueue(flags)) { queue = mOffloadBroadcastQueue; } else { queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0 ? mFgBroadcastQueue : mBgBroadcastQueue; } // 1. 匹配程序正在處理的廣播 r = queue.getMatchingOrderedReceiver(who); // 2. 完成當前 receiver 廣播的處理流程 if (r != null) { doNext = r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort, true); } // 3. 傳送廣播給下一個 receiver,或者傳送下一個廣播 if (doNext) { r.queue.processNextBroadcastLocked(/*fromMsg=*/ false, /*skipOomAdj=*/ true); } // updateOomAdjLocked() will be done here trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER); } } finally { Binder.restoreCallingIdentity(origId); } }
看到了,只有當前 receiver 處理完廣播,才會傳送廣播給下一個 receiver,這就是“序列”廣播的本質。
最後,來探討一個廣播 ANR 的原理,本來我以為很簡單的,就是傳送一個超時訊息嘛。但是當我細看的時候,我發現這個 ANR 設計的很巧妙,我覺得我們可以學習下,因此這裡單獨拿出來分析。
這裡,我得提醒大家一點,只有“序列”廣播才會發生 ANR,因為它要等待 receiver 的處理結果。
根據前面分析,“序列”廣播傳送給 receiver 前,會傳送一個超時訊息,如下
// BroadcastQueue.java if (! mPendingBroadcastTimeoutMessage) { long timeoutTime = r.receiverTime + mConstants.TIMEOUT; setBroadcastTimeoutLocked(timeoutTime); }
當這個訊息被執行的時候,會呼叫如下程式碼
// BroadcastQueue.java final void broadcastTimeoutLocked(boolean fromMsg) { if (fromMsg) { mPendingBroadcastTimeoutMessage = false; } if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) { return; } long now = SystemClock.uptimeMillis(); // 獲取當前正在處理的廣播 BroadcastRecord r = mDispatcher.getActiveBroadcastLocked(); if (fromMsg) { // 系統還沒有就緒 if (!mService.mProcessesReady) { return; } // 廣播超時被豁免 if (r.timeoutExempt) { if (DEBUG_BROADCAST) { Slog.i(TAG_BROADCAST, "Broadcast timeout but it's exempt: " + r.intent.getAction()); } return; } // 1. 廣播沒有超時 long timeoutTime = r.receiverTime + mConstants.TIMEOUT; if (timeoutTime > now) { // 傳送下一個超時訊息 setBroadcastTimeoutLocked(timeoutTime); return; } } if (r.state == BroadcastRecord.WAITING_SERVICES) { // ... return; } // 2. 走到這裡,表示廣播超時,觸發 ANR final boolean debugging = (r.curApp != null && r.curApp.isDebugging()); r.receiverTime = now; if (!debugging) { r.anrCount++; } ProcessRecord app = null; String anrMessage = null; Object curReceiver; if (r.nextReceiver > 0) { curReceiver = r.receivers.get(r.nextReceiver-1); r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT; } else { curReceiver = r.curReceiver; } logBroadcastReceiverDiscardLocked(r); // 獲取 receiver 程序 if (curReceiver != null && curReceiver instanceof BroadcastFilter) { BroadcastFilter bf = (BroadcastFilter)curReceiver; if (bf.receiverList.pid != 0 && bf.receiverList.pid != ActivityManagerService.MY_PID) { synchronized (mService.mPidsSelfLocked) { app = mService.mPidsSelfLocked.get( bf.receiverList.pid); } } } else { app = r.curApp; } if (app != null) { anrMessage = "Broadcast of " + r.intent.toString(); } if (mPendingBroadcast == r) { mPendingBroadcast = null; } // 強制結束當前廣播的傳送流程 finishReceiverLocked(r, r.resultCode, r.resultData, r.resultExtras, r.resultAbort, false); // 排程下一次的廣播傳送 scheduleBroadcastsLocked(); // app 不處於 debug 模式,引發 ANR if (!debugging && anrMessage != null) { mService.mAnrHelper.appNotResponding(app, anrMessage); } }
第2步,引發 ANR ,很簡單,就是因為整個傳送與反饋過程超時了。
而第1步,就是處理不超時的情況。這裡大家是不是很疑惑,移除超時訊息不是在接收到廣播反饋後進行的嗎? 我可以負責地告訴你,並不是!那這裡第1步怎麼理解呢?
首先這個超時訊息一定觸發,但是觸發這個超時訊息,並不代表一定會引發 ANR。
假如當前 receiver 的廣播處理流程,在超時時間之前就完成了,那麼 AMS 會排程廣播傳送給下一個 receiver。
於是,針對下一個 receiver ,會更新 r.receiverTime,那麼第一步此時計算出來的 timeoutTime 是下一個 receiver 的廣播超時時間,很顯然是大於 now 的,於是就不會走第2步的 ANR 流程。
最後利用這個 timeoutTime,為下一個 receiver 再傳送一個超時訊息,簡直是完美!
至於為何不在廣播反饋的時候,移除這個超時訊息,我心中有一點小小的想法,但是也不能確定是不是這個原因,才這樣設計的。不過,對我來說,這一招,我算是學會了。
最後,提醒讀者,廣播接收器是在主執行緒中執行的,不要執行耗時任務,或者潛在耗時的任務,我在工作中看到了無數血與淚的案例。
本文從整體上分析了“序列”和“並行”廣播的傳送流程,並以此為基礎,解析了“序列”廣播的 ANR 原理。
但是,還有一些廣播的細節,我並沒有分析,例如 Android O+ 如何限制廣播傳送給靜態接收器,又例如,什麼情況下,會把廣播延遲傳送給 app。只要你站在我的肩膀上,就可以自行分析這些細節。
另外,我在分析的時候,有個優化廣播傳送的想法,如果廣播有多個app的靜態接收器,我可以建立一個機制,優先把廣播傳送給某些 app,並且對於這些 app,我不需要等待它反饋廣播處理結果,就可以傳送廣播給下一個接收器。如果以後工作有需要,我會嘗試做一做,更多關於ActivityManagerService廣播的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45