一 概述
廣播 (Broadcast) 機制用于進程或線程間通信,廣播分為廣播發送和廣播接收兩個過程,其中廣播接收者 BroadcastReceiver 是 Android 四大組件之一。BroadcastReceiver 分為兩類:
- 靜態廣播接收者:通過 AndroidManifest.xml 的標簽來聲明的 BroadcastReceiver
- 動態廣播接收者:通過 AMS.registerReceiver() 方式注冊的 BroadcastReceiver,動態注冊更為靈活,可在不需要時動態取消注冊
PS:動態 BroadcastReceiver 比較簡單,靜態的就麻煩一些了,因為在廣播發送之時,靜態 receiver 所從屬的進程可能還沒有啟動呢,這就需要先啟動新的進程,費時費力。另一方面,有些時候用戶希望廣播能夠按照一定順序發送,為此,Android 又搞出了 ordered broadcast 的概念。
從廣播發送方式可分為三類:
- 普通廣播:sendBroadcast(Intent) 可并行處理,方法會按隨機的順序向所有接收器發送廣播。這稱為常規廣播。這種方法效率更高,但也意味著接收器無法從其他接收器讀取結果,無法傳遞從廣播中收到的數據,也無法中止廣播。
- 有序廣播:sendOrderedBroadcast(Intent, String) 串行處理,一次向一個接收器發送廣播。當接收器逐個順序執行時,接收器可以向下傳遞結果,也可以完全中止廣播,使其不再傳遞給其他接收器。接收器的運行順序可以通過匹配的 intent-filter 的 android:priority 屬性來控制;具有相同優先級的接收器將按隨機順序運行。
- Sticky廣播:通過sendStickyBroadcast()發送
廣播在系統中以 BroadcastRecord 對象來記錄,該對象有幾個時間相關的成員變量需要注意。
final class BroadcastRecord extends Binder {final ProcessRecord callerApp; // 廣播發送者所在進程final String callerPackage; // 廣播發送者所在包名// 包括動態注冊的 BroadcastFilter 和靜態注冊的 ResolveInfofinal List receivers;final int callingPid; // 廣播發送者 pidint nextReceiver; // 下一個被執行的接收者IBinder receiver; // 當前正在處理的接收者int anrCount; // 廣播 ANR 次數long enqueueClockTime; // 入隊列時間,伴隨著 scheduleBroadcastsLockedlong dispatchTime; // 分發時間long dispatchClockTime;// 分發時間,伴隨著 deliverToRegisteredReceiverLockedlong receiverTime; // 接收時間(首次等于 dispatchClockTime)long finishTime; // 廣播完成時間,位于 addBroadcastToHistoryLocked 方法內
}
我們這一節主要分析廣播的注冊流程,相關代碼路徑如下:
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java
frameworks/base/services/core/java/com/android/server/am/BroadcastRecord.javaframeworks/base/core/java/android/content/BroadcastReceiver.java
frameworks/base/core/java/android/app/ContextImpl.java
frameworks/base/core/java/android/app/LoadedApk.java
frameworks/base/core/java/android/app/ActivityThread.java
二 動態BroadcastReceiver注冊
時序圖
用戶調用registerReceiver()方法,而Activity/Service都間接繼承于Context抽象類,真正干活是交給ContextImpl類。
2.1 ContextImpl.registerReceiver
frameworks/base/core/java/android/app/ContextImpl.java
@Override
public Intent registerReceiver(BroadcastReceiver receiver,IntentFilter filter) {return registerReceiver(receiver, filter, null, null);
}@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,String broadcastPermission, Handler scheduler) {return registerReceiverInternal(receiver, getUserId(), filter,broadcastPermission, scheduler, getOuterContext(), 0);
}
注冊之時,用戶會把一個自定義的 receiver 對象作為第一個參數傳入。當然,用戶的 receiver 都是繼承于 BroadcastReceiver 的。
其中 broadcastPermission 擁有廣播的權限控制,scheduler 用于指定接收到廣播時 onRecive 執行線程,當 scheduler=null 則默認代表在主線程中執行,這也是最常見的用法。
使用過廣播機制的同學,對這個 BroadcastReceiver 應該都不陌生,這里就不多說了。我們需要關心的是,這個 registerReceiverInternal() 內部還包含了什么重要的細節。
2.2 ContextImpl.registerReceiverInternal
private Intent registerReceiverInternal(BroadcastReceiver receiver,int userId, IntentFilter filter, String broadcastPermission,Handler scheduler, Context context, int flags) {// 獲取 InnerReceiver, 基本思路是從 mReceivers 映射表中查找和// BroadcastReceiver 一一對應的 ReceiverDispatcher// 進而獲取 InnerReceiver. 如果沒有則新建.IIntentReceiver rd = null;if (receiver != null) {if (mPackageInfo != null && context != null) {if (scheduler == null) {scheduler = mMainThread.getHandler();}// 查找和 context 對應的“子哈希表”里的 ReceiverDispatcher// 如果找不到,就重新 new 一個rd = mPackageInfo.getReceiverDispatcher(receiver, context, scheduler,mMainThread.getInstrumentation(), true);} else {if (scheduler == null) {scheduler = mMainThread.getHandler();}rd = new LoadedApk.ReceiverDispatcher(receiver, context,scheduler, null, true).getIIntentReceiver();}}try {// AMS.registerReceiver 注冊廣播.final Intent intent = ActivityManager.getService().registerReceiver(mMainThread.getApplicationThread(), mBasePackageName, rd, filter, broadcastPermission, userId, flags);if (intent != null) {intent.setExtrasClassLoader(getClassLoader());intent.prepareToEnterProcess();}return intent;} catch (RemoteException e) {throw e.rethrowFromSystemServer();}
}
ContextImpl.registerReceiver 過程:
1.根據 BroadcastReceiver 獲取對應的 ReceiverDispatcher
- 先根據 LoadedApk.getReceiverDispatcher,從 ArrayMap<BroadcastReceiver, ReceiverDispatcher> 映射表中得到和 BroadcastReceiver 對應的 ReceiverDispatcher
- 如果 LoadedApk 為空,則新建 LoadedApk.ReceiverDispatcher
2.執行 ReceiverDispatcher.getIIntentReceiver() 得到 InnerReceiver (即 IIntentReceiver.Stub )
3.調用 AMS.registerReceiver 注冊廣播
請大家注意那個 rd 對象(IIntentReceiver)。我們知道,在 Android 架構中,廣播動作最終其實都是由 AMS 遞送出來的。AMS 利用 binder 機制,將語義傳遞給各個應用進程,應用進程再輾轉調用到 receiver 的 onReceive(),完成這次廣播。而此處的 rd 對象正是承擔“語義傳遞工作“的 binder 實體。
為了管理這個重要的 binder 實體,Android 搞出了一個叫做 ReceiveDispatcher 的類。該類的定義截選如下
2.3 LoadedApk.ReceiveDispatcher
?LoadedApk.java
static final class ReceiverDispatcher {final static class InnerReceiver extends IIntentReceiver.Stub {final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;final LoadedApk.ReceiverDispatcher mStrongRef;InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);mStrongRef = strong ? rd : null;}// 核心回調函數@Overridepublic void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered,boolean sticky, int sendingUser) {final LoadedApk.ReceiverDispatcher rd;if (intent == null) {rd = null;} else {rd = mDispatcher.get();}......if (rd != null) {// 調用 LoadedApk.ReceiverDispatcher 的 performReceive 方法rd.performReceive(intent, resultCode, data, extras,ordered, sticky, sendingUser);} else {......}}}// 請注意這個域!它就是傳到外面的rdfinal IIntentReceiver.Stub mIIntentReceiver;@UnsupportedAppUsage// 每一個 ReceiverDispatcher 對應一個 BroadcastReceiverfinal BroadcastReceiver mReceiver;final Context mContext;final Handler mActivityThread;final Instrumentation mInstrumentation;final boolean mRegistered;final IntentReceiverLeaked mLocation;RuntimeException mUnregisterLocation;boolean mForgotten;final class Args extends BroadcastReceiver.PendingResult {private Intent mCurIntent;private final boolean mOrdered;private boolean mDispatched;private boolean mRunCalled;public Args(Intent intent, int resultCode, ......) {super(resultCode, resultData, resultExtras, ......);mCurIntent = intent;mOrdered = ordered;}
// 這個 getRunnable 返回的 Runnable,會被發送到主線程的消息隊列里執行public final Runnable getRunnable() {return () -> {final BroadcastReceiver receiver = mReceiver;final boolean ordered = mOrdered;...... try {ClassLoader cl = mReceiver.getClass().getClassLoader();intent.setExtrasClassLoader(cl);intent.prepareToEnterProcess();setExtrasClassLoader(cl);receiver.setPendingResult(this);// 關鍵調用:回調 receiver 的 onReceive 方法receiver.onReceive(mContext, intent);} catch (Exception e) {......}if (receiver.getPendingResult() != null) {finish();}};}}// ReceiverDispatcher 構造函數ReceiverDispatcher(BroadcastReceiver receiver, Context context,Handler activityThread, Instrumentation instrumentation,boolean registered) {if (activityThread == null) {throw new NullPointerException("Handler must not be null");}// 生成一個 InnerReceivermIIntentReceiver = new InnerReceiver(this, !registered);mReceiver = receiver;mContext = context;mActivityThread = activityThread;mInstrumentation = instrumentation;mRegistered = registered;mLocation = new IntentReceiverLeaked(null);mLocation.fillInStackTrace();}......// 返回這個 mIIntentReceiverIIntentReceiver getIIntentReceiver() {return mIIntentReceiver;}......public void performReceive(Intent intent, int resultCode,String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) {// 把參數封裝到 Args 中final Args args = new Args(intent, resultCode, data, extras,ordered, sticky, sendingUser);......// 通過 args 獲取一個 Runnable,并把這個 Runnable post 到 主線程的消息隊列// 其中的 mActivityThread 是一個 Handler,默認屬于主線程if (intent == null || !mActivityThread.post(args.getRunnable())) {......}}
}
這樣看來,“動態注冊的 BroadcastReceiver ” 和 “ ReceiverDispatcher 節點” 具有一一對應的關系。示意圖如下:
一個應用里可能會注冊多個動態 receiver,所以這種一一對應關系最好整理成表,這個表就位于 LoadedApk 中。前文 mPackageInfo.getReceiverDispatcher() 一句中的 mPackageInfo 就是 LoadedApk 對象。
在 Android 的架構里,應用進程里是用 LoadedApk 來對應一個 apk 的,進程里加載了多少個 apk,就會有多少 LoadedApk。每個 LoadedApk 里會有一張 “關于本 apk 動態注冊的所有 receiver ” 的哈希表(mReceivers)。當然,在 LoadedApk 初創之時,這張表只是個空表。
mReceivers 表的定義如下:
private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers = new ArrayMap<>();
該表的 key 項是我們比較熟悉的 Context,也就是說可以是 Activity、Service 或 Application。而 value 項則是另一張“子哈希表”。這是個 “表中表” 的形式。
言下之意就是,每個 Context(比如一個 activity),是可以注冊多個 receiver 的,這個很好理解。mReceivers 里的“子哈希表”的 key 值為 BroadcastReceiver,value 項為 ReceiverDispatcher,示意圖如下:
接下來我們繼續看 LoadedApk.getReceiverDispatcher
2.3.1 LoadedApk.getReceiverDispatcher
frameworks/base/core/java/android/app/LoadedApk.java
public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,Context context, Handler handler,Instrumentation instrumentation, boolean registered) {synchronized (mReceivers) {LoadedApk.ReceiverDispatcher rd = null;ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;if (registered) {// 這個 mReceivers 就是我們上節說的哈希表map = mReceivers.get(context);if (map != null) {rd = map.get(r);}}// 如果沒有對應的 ReceiverDispatcher,那么就創建一個if (rd == null) {rd = new ReceiverDispatcher(r, context, handler,instrumentation, registered);if (registered) {if (map == null) {map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();mReceivers.put(context, map);}map.put(r, rd);}}rd.mForgotten = false;return rd.getIIntentReceiver();}
}
?ReceiverDispatcher 作用小結:
- 它是負責將廣播 Broadcast 派發給 BroadcastReceiver 執行的廣播派發器
- BroadcastReceiver 和 ReceiverDispatcher 是一一對應的,一個廣播接收器對應一個廣播派發器
- 它主要是通過內部類 InnerReceiver 和 Args 執行廣播派發過程
2.4 AMS.ReceiverList
我們繼續看 registerReceiverInternal() 的后半部分,最終調用到了 AMS 的 registerReceiver。
try {final Intent intent = ActivityManager.getService().registerReceiver(mMainThread.getApplicationThread(), mBasePackageName, rd, filter,broadcastPermission, userId, flags);if (intent != null) {intent.setExtrasClassLoader(getClassLoader());intent.prepareToEnterProcess();}return intent;
} catch (RemoteException e) {throw e.rethrowFromSystemServer();
}
?registerReceiver() 函數的 filter 參數指明了 receiver 對哪些 intent 感興趣。
對同一個 BroadcastReceiver 對象來說,可以注冊多個感興趣的 filter,就好像聲明靜態 receiver 時,也可以為一個 receiver 編寫多個 <intent-filter> 一樣。這些 IntentFilter 信息會匯總到 AMS 的 mRegisteredReceivers 表中。在 AMS 端,我們可以這樣訪問相應的匯總表:
ActivityManagerService.java
final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();// 這里的 receiver 就是應用進程中傳遞過來的 IIntentReceiver
ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
其中的 receiver 參數為 IIntentReceiver 類型,正對應著 ReceiverDispatcher 中那個 binder 實體。也就是說,每個客戶端的 ReceiverDispatcher,會對應 AMS 端的一個 ReceiverList。
ReceiverList 的定義截選如下:
ReceiverList.java
final class ReceiverList extends ArrayList<BroadcastFilter>implements IBinder.DeathRecipient {final ActivityManagerService owner;public final IIntentReceiver receiver;public final ProcessRecord app;public final int pid;public final int uid;public final int userId;BroadcastRecord curBroadcast = null;boolean linkedToDeath = false;String stringName;ReceiverList(ActivityManagerService _owner, ProcessRecord _app,int _pid, int _uid, int _userId, IIntentReceiver _receiver) {owner = _owner;receiver = _receiver;app = _app;pid = _pid;uid = _uid;userId = _userId;}......public void binderDied() {linkedToDeath = false;owner.unregisterReceiver(receiver);}public boolean containsFilter(IntentFilter filter) {final int N = size();for (int i = 0; i < N; i++) {final BroadcastFilter f = get(i);if (IntentResolver.filterEquals(f, filter)) {return true;}}return false;}......
}
ReceiverList 繼承于?ArrayList<BroadcastFilter>
,而 BroadcastFilter 又繼承于 IntentFilter,所以 ReceiverList 可以被理解為一個 IntentFilter 數組列表。
final class BroadcastFilter extends IntentFilter {// Back-pointer to the list this filter is in.final ReceiverList receiverList;final String packageName;final String requiredPermission;final int owningUid;final int owningUserId;final boolean instantApp;final boolean visibleToInstantApp;BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,......) {super(_filter);receiverList = _receiverList;packageName = _packageName;requiredPermission = _requiredPermission;......}......
}
現在,我們可以繪制一張完整一點兒的圖:
這張圖只畫了一個用戶進程,在實際的系統里當然會有很多用戶進程了,不過其關系是大致統一的,所以我們不再重復繪制。
關于動態 receiver 的注冊,我們就先說這么多。至于激發廣播時,又會做什么動作,我們會在后文闡述,現在我們先接著說明和動態 receiver 相對的靜態 receiver。
三 靜態BroadcastReceiver
靜態 receiver 是指那些在 AndroidManifest.xml 文件中聲明的 receiver,它們的信息會在系統啟動時,由 Package Manager Service(PKMS)解析并記錄下來。以后,當 AMS 調用 PKMS 的接口來查詢 “和 intent 匹配的組件” 時,PKMS 內部就會去查詢當初記錄下來的數據,并把結果返回 AMS。
有的同學認為靜態 receiver 是常駐內存的,這種說法并不準確。因為常駐內存的只是靜態 receiver 的描述性信息,并不是 receiver 實體本身。
?
在 PKMS 內部,會有一個針對 receiver 而設置的 Resolver(決策器),其示意圖如下:
?需要說明的是以上的示意圖是之前的 android 版本實現的,在 android 10.0 中,PKMS 中的 mActivities,mReceivers,mServices 等相關組件已經被封裝到了 ComponentResolver 中了。
ComponentResolver.java
private final ActivityIntentResolver mActivities = new ActivityIntentResolver();private final ProviderIntentResolver mProviders = new ProviderIntentResolver();private final ActivityIntentResolver mReceivers = new ActivityIntentResolver();private final ServiceIntentResolver mServices = new ServiceIntentResolver();
3.1 ActivityIntentResolver
ComponentResolver.java # ActivityIntentResolver
private static final class ActivityIntentResolverextends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {@Overridepublic List<ResolveInfo> queryIntent(Intent intent, String resolvedType,boolean defaultOnly, int userId) {......}List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,int userId) {......}List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,int flags, List<PackageParser.Activity> packageActivities, int userId) {......}private void addActivity(PackageParser.Activity a, String type,List<PackageParser.ActivityIntentInfo> newIntents) {......}private void removeActivity(PackageParser.Activity a, String type) {mActivities.remove(a.getComponentName());......}......// Keys are String (activity class name), values are Activity.private final ArrayMap<ComponentName, PackageParser.Activity> mActivities =new ArrayMap<>();private int mFlags;
}
IntentResolver 是對 IntentFilter 操作的封裝。
3.3 PackageParser.ActivityIntentInfo
PackageParser.java # ActivityIntentInfo
public final static class ActivityIntentInfo extends IntentInfo {@UnsupportedAppUsagepublic Activity activity;public ActivityIntentInfo(Activity _activity) {activity = _activity;}......public ActivityIntentInfo(Parcel in) {super(in);}
}
而 IntentInfo 又繼承自 IntentFilter,代碼如下:
PackageParser.java # IntentInfo
public static abstract class IntentInfo extends IntentFilter {......@UnsupportedAppUsageprotected IntentInfo() {}protected IntentInfo(Parcel dest) {super(dest);hasDefault = (dest.readInt() == 1);labelRes = dest.readInt();nonLocalizedLabel = dest.readCharSequence();icon = dest.readInt();logo = dest.readInt();banner = dest.readInt();preferred = dest.readInt();}......
}
3.4 ResolveInfo
ResolveInfo.java
public class ResolveInfo implements Parcelable {private static final String TAG = "ResolveInfo";public ActivityInfo activityInfo;public ServiceInfo serviceInfo;public ProviderInfo providerInfo;public IntentFilter filter;public String resolvePackageName;......public ResolveInfo(ResolveInfo orig) {activityInfo = orig.activityInfo;serviceInfo = orig.serviceInfo;providerInfo = orig.providerInfo;filter = orig.filter;priority = orig.priority;preferredOrder = orig.preferredOrder;match = orig.match;specificIndex = orig.specificIndex;labelRes = orig.labelRes;nonLocalizedLabel = orig.nonLocalizedLabel;icon = orig.icon;resolvePackageName = orig.resolvePackageName;noResourceId = orig.noResourceId;iconResourceId = orig.iconResourceId;system = orig.system;targetUserId = orig.targetUserId;handleAllWebDataURI = orig.handleAllWebDataURI;isInstantAppAvailable = orig.isInstantAppAvailable;instantAppAvailable = isInstantAppAvailable;}private ResolveInfo(Parcel source) {activityInfo = null;serviceInfo = null;providerInfo = null;switch (source.readInt()) {case 1:activityInfo = ActivityInfo.CREATOR.createFromParcel(source);break;case 2:serviceInfo = ServiceInfo.CREATOR.createFromParcel(source);break;case 3:providerInfo = ProviderInfo.CREATOR.createFromParcel(source);break;default:Slog.w(TAG, "Missing ComponentInfo!");break;}if (source.readInt() != 0) {filter = IntentFilter.CREATOR.createFromParcel(source);}priority = source.readInt();preferredOrder = source.readInt();match = source.readInt();specificIndex = source.readInt();labelRes = source.readInt();nonLocalizedLabel= TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);icon = source.readInt();resolvePackageName = source.readString();targetUserId = source.readInt();system = source.readInt() != 0;noResourceId = source.readInt() != 0;iconResourceId = source.readInt();handleAllWebDataURI = source.readInt() != 0;instantAppAvailable = isInstantAppAvailable = source.readInt() != 0;}
}
以上是涉及到到的各個核心數據結構,關于 PKMS 的查詢動作的細節,可參考 PKMS 的相關文檔。
目前我們只需知道,PKMS 向外界提供了 queryIntentReceivers(Intent intent, String resolvedType,…) 函數,該函數可以返回一個 ParceledListSlice<ResolveInfo> 列表。這個列表的所包含的信息為:有多少 receiver 對相關 Intent 的 Action 感興趣。
總之就是,當系統發出一個廣播時,PKMS 必須能夠決策出,有多少靜態 receiver 對這個廣播感興趣,而且這些 receiver 的信息分別又是什么。
關于 receiver 的注冊動作,我們就先說這么多。下面我們來看看激發廣播時的動作。
四 廣播發送
時序圖:
大家常見的激發廣播的函數有哪些呢?從 ContextImpl.java 文件中,我們可以看到一系列發送廣播的接口,列舉如下:
- public void sendBroadcast(Intent intent)
- public void sendBroadcast(Intent intent, String receiverPermission)
- public void sendBroadcast(Intent intent, String receiverPermission, Bundle options)
- public void sendOrderedBroadcast(Intent intent, String receiverPermission)
- public void sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)
- public void sendStickyBroadcast(Intent intent)
- public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)
其中 sendBroadcast() 是最簡單的發送廣播的動作。而 sendOrderedBroadcast(),則是用來向系統發出有序廣播 (Ordered broadcast) 的。這種有序廣播對應的所有接收器只能按照一定的優先級順序,依次接收 intent。這些優先級一般記錄在 AndroidManifest.xml 文件中,具體位置在 <intent-filter> 元素的 android:priority 屬性中,其數值越大表示優先級越高,取值范圍為 -1000 到 1000。另外,有時候我們也可以調用 IntentFilter 對象的 setPriority() 方法來設置優先級。
對于有序廣播而言,前面的接收者可以對接收到的廣播 intent 進行處理,并將處理結果放置到廣播 intent 中,然后傳遞給下一個接收者。需要注意的是,前面的接收者有權終止廣播的進一步傳播。也就是說,如果廣播被前面的接收者終止了,那么后面的接收器就再也無法接收到廣播了。
還有一個怪東西,叫做 sticky 廣播,它又是什么呢?簡單地說,sticky 廣播可以保證 “在廣播遞送時尚未注冊的 receiver”,一旦日后注冊進系統,就能夠馬上接到 “錯過” 的 sticky 廣播。有關它的細節,我們在后文再說。
在發送方,我們熟悉的調用 sendBroadcast() 的代碼片段如下:
4.1 ContextImpl.sendBroadcast
ContextImpl.java
public void sendBroadcast(Intent intent) {warnIfCallingFromSystemProcess();String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());try {intent.prepareToLeaveProcess(this);ActivityManager.getService().broadcastIntent(mMainThread.getApplicationThread(), intent, resolvedType,null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,getUserId());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}
}
通過 Binder 機制,跨進程調用 AMS 的 broadcastIntent()。
4.2 AMS.broadcastIntent
public final int broadcastIntent(IApplicationThread caller,Intent intent, String resolvedType, IIntentReceiver resultTo,int resultCode, String resultData, Bundle resultExtras,String[] requiredPermissions, int appOp, Bundle bOptions,boolean serialized, boolean sticky, int userId) {enforceNotIsolatedCaller("broadcastIntent");synchronized(this) {intent = verifyBroadcastLocked(intent);final ProcessRecord callerApp = getRecordForAppLocked(caller);final int callingPid = Binder.getCallingPid();final int callingUid = Binder.getCallingUid();final long origId = Binder.clearCallingIdentity();try {return broadcastIntentLocked(callerApp,callerApp != null ? callerApp.info.packageName : null,intent, resolvedType, resultTo, resultCode, resultData,resultExtras,requiredPermissions, appOp, bOptions, serialized, sticky,callingPid, callingUid, callingUid, callingPid, userId);} finally {Binder.restoreCallingIdentity(origId);}}
}
進而調用 broadcastIntentLocked。
4.3 AMS.broadcastIntentLocked
final int broadcastIntentLocked(ProcessRecord callerApp,String callerPackage, Intent intent, String resolvedType,IIntentReceiver resultTo, int resultCode, ......) {return broadcastIntentLocked(callerApp, callerPackage, intent, resolvedType, resultTo, resultCode, resultData, resultExtras,requiredPermissions, appOp, bOptions, ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId,false /* allowBackgroundActivityStarts */);
}
繼續調用 broadcastIntentLocked
final int broadcastIntentLocked(ProcessRecord callerApp,String callerPackage, Intent intent, String resolvedType,IIntentReceiver resultTo, int resultCode, String resultData,Bundle resultExtras, String[] requiredPermissions, int appOp,Bundle bOptions, boolean ordered, boolean sticky, int callingPid,int callingUid, int realCallingUid, int realCallingPid,int userId, boolean allowBackgroundActivityStarts) {intent = new Intent(intent);......// 為 intent 添加 FLAG_EXCLUDE_STOPPED_PACKAGES 標記intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);......final boolean isProtectedBroadcast;try {isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);} catch (RemoteException e) {Slog.w(TAG, "Remote exception", e);return ActivityManager.BROADCAST_SUCCESS;}final boolean isCallerSystem;switch (UserHandle.getAppId(callingUid)) {case ROOT_UID:case SYSTEM_UID:case PHONE_UID:case BLUETOOTH_UID:case NFC_UID:case SE_UID:case NETWORK_STACK_UID:isCallerSystem = true;break;default:isCallerSystem = (callerApp != null) &&callerApp.isPersistent();break;}if (!isCallerSystem) {if (isProtectedBroadcast) {String msg = "Permission Denial: not allowed to send broadcast "+ action + " from pid="+ callingPid + ", uid=" + callingUid;Slog.w(TAG, msg);throw new SecurityException(msg);} else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)|| AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {if (callerPackage == null) {......} else if (intent.getComponent() != null) {......} else {// Limit broadcast to their own package.intent.setPackage(callerPackage);}}}......if (action != null) {......switch (action) {case Intent.ACTION_UID_REMOVED:case Intent.ACTION_PACKAGE_REMOVED:case Intent.ACTION_PACKAGE_CHANGED:case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:case Intent.ACTION_PACKAGES_SUSPENDED:case Intent.ACTION_PACKAGES_UNSUSPENDED: if (checkComponentPermission(android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,callingPid, callingUid, -1, true)!= PackageManager.PERMISSION_GRANTED) {String msg = "Permission Denial: " + intent.getAction()+ " broadcast from " + callerPackage + " (pid=" + callingPid+ ", uid=" + callingUid + ")"+ " requires "+ android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;Slog.w(TAG, msg);throw new SecurityException(msg);}switch (action) {case Intent.ACTION_UID_REMOVED:......case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:......break;case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);break;case Intent.ACTION_PACKAGE_REMOVED:case Intent.ACTION_PACKAGE_CHANGED:......break;case Intent.ACTION_PACKAGES_SUSPENDED:case Intent.ACTION_PACKAGES_UNSUSPENDED:......break;}break;case Intent.ACTION_PACKAGE_REPLACED:{......break;}case Intent.ACTION_PACKAGE_ADDED:{......break;}case Intent.ACTION_PACKAGE_DATA_CLEARED:{......break;}case Intent.ACTION_TIMEZONE_CHANGED:mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);break;case Intent.ACTION_TIME_CHANGED:......break;case Intent.ACTION_CLEAR_DNS_CACHE:mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);break;case Proxy.PROXY_CHANGE_ACTION:mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG));break;case android.hardware.Camera.ACTION_NEW_PICTURE:case android.hardware.Camera.ACTION_NEW_VIDEO: intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);break;case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);break;case "com.android.launcher.action.INSTALL_SHORTCUT":Log.w(TAG, "Broadcast " + action+ " no longer supported. It will not be delivered.");return ActivityManager.BROADCAST_SUCCESS;case Intent.ACTION_PRE_BOOT_COMPLETED:timeoutExempt = true;break;}......}// Add to the sticky list if requested.if (sticky) {if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,callingPid, callingUid)!= PackageManager.PERMISSION_GRANTED) {String msg = "Permission Denial: broadcastIntent() requesting a sticky broadcast from pid="+ callingPid + ", uid=" + callingUid+ " requires " + android.Manifest.permission.BROADCAST_STICKY;Slog.w(TAG, msg);throw new SecurityException(msg);}if (requiredPermissions != null && requiredPermissions.length > 0) {Slog.w(TAG, "Can't broadcast sticky intent " + intent+ " and enforce permissions " + Arrays.toString(requiredPermissions));return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;}if (intent.getComponent() != null) {throw new SecurityException("Sticky broadcasts can't target a specific component");} if (userId != UserHandle.USER_ALL) {ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(UserHandle.USER_ALL);if (stickies != null) {ArrayList<Intent> list = stickies.get(intent.getAction());if (list != null) {int N = list.size();int i;for (i=0; i<N; i++) {if (intent.filterEquals(list.get(i))) {throw new IllegalArgumentException("Sticky broadcast " + intent + " for user "+ userId + " conflicts with existing global broadcast");}}}}}ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);if (stickies == null) {stickies = new ArrayMap<>();mStickyBroadcasts.put(userId, stickies);}ArrayList<Intent> list = stickies.get(intent.getAction());if (list == null) {list = new ArrayList<>();stickies.put(intent.getAction(), list);}final int stickiesCount = list.size();int i;for (i = 0; i < stickiesCount; i++) {if (intent.filterEquals(list.get(i))) {// This sticky already exists, replace it.list.set(i, new Intent(intent));break;}}if (i >= stickiesCount) {list.add(new Intent(intent));}}......// Figure out who all will receive this broadcast.List receivers = null;List<BroadcastFilter> registeredReceivers = null;// Need to resolve the intent to interested receivers...if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)== 0) {receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);}if (intent.getComponent() == null) {if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {// Query one target user at a time, excluding shell-restricted usersfor (int i = 0; i < users.length; i++) {if (mUserController.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {continue;}List<BroadcastFilter> registeredReceiversForUser =mReceiverResolver.queryIntent(intent,resolvedType, false /*defaultOnly*/, users[i]);if (registeredReceivers == null) {registeredReceivers = registeredReceiversForUser;} else if (registeredReceiversForUser != null) {registeredReceivers.addAll(registeredReceiversForUser);}}} else {registeredReceivers = mReceiverResolver.queryIntent(intent,resolvedType, false /*defaultOnly*/, userId);}}final boolean replacePending =(intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing broadcast: " + intent.getAction()+ " replacePending=" + replacePending);int NR = registeredReceivers != null ? registeredReceivers.size() : 0;if (!ordered && NR > 0) {if (isCallerSystem) {checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,isProtectedBroadcast, registeredReceivers);}final BroadcastQueue queue = broadcastQueueForIntent(intent);BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,callerPackage, callingPid, callingUid, callerInstantApp, resolvedType,requiredPermissions, appOp, brOptions, registeredReceivers, resultTo,resultCode, resultData, resultExtras, ordered, sticky, false, userId,allowBackgroundActivityStarts, timeoutExempt);if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);final boolean replaced = replacePending&& (queue.replaceParallelBroadcastLocked(r) != null);// Note: We assume resultTo is null for non-ordered broadcasts.if (!replaced) {queue.enqueueParallelBroadcastLocked(r);queue.scheduleBroadcastsLocked();}registeredReceivers = null;NR = 0;}// Merge into one list.int ir = 0;if (receivers != null) {......int NT = receivers != null ? receivers.size() : 0;int it = 0;ResolveInfo curt = null;BroadcastFilter curr = null;while (it < NT && ir < NR) {if (curt == null) {curt = (ResolveInfo)receivers.get(it);}if (curr == null) {curr = registeredReceivers.get(ir);}if (curr.getPriority() >= curt.priority) {// Insert this broadcast record into the final list.receivers.add(it, curr);ir++;curr = null;it++;NT++;} else {// Skip to the next ResolveInfo in the final list.it++;curt = null;}}}while (ir < NR) {if (receivers == null) {receivers = new ArrayList();}receivers.add(registeredReceivers.get(ir));ir++;}if (isCallerSystem) {checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,isProtectedBroadcast, receivers);}if ((receivers != null && receivers.size() > 0)|| resultTo != null) {BroadcastQueue queue = broadcastQueueForIntent(intent);BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,callerPackage, callingPid, callingUid, callerInstantApp, resolvedType,requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,resultData, resultExtras, ordered, sticky, false, userId,allowBackgroundActivityStarts, timeoutExempt);if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);final BroadcastRecord oldRecord =replacePending ? queue.replaceOrderedBroadcastLocked(r) : null;if (oldRecord != null) {// Replaced, fire the result-to receiver.if (oldRecord.resultTo != null) {final BroadcastQueue oldQueue = broadcastQueueForIntent(oldRecord.intent);try {oldQueue.performReceiveLocked(oldRecord.callerApp, oldRecord.resultTo,oldRecord.intent,Activity.RESULT_CANCELED, null, null,false, false, oldRecord.userId);} catch (RemoteException e) {Slog.w(TAG, "Failure ["+ queue.mQueueName + "] sending broadcast result of "+ intent, e);}}} else {queue.enqueueOrderedBroadcastLocked(r);queue.scheduleBroadcastsLocked();}} else {// There was nobody interested in the broadcast, but we still want to record// that it happened.if (intent.getComponent() == null && intent.getPackage() == null&& (intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {// This was an implicit broadcast... let's record it for posterity.addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);}}return ActivityManager.BROADCAST_SUCCESS;}
broadcastIntentLocked() 是廣播發送的核心函數,內容很多,函數很長,其中主要考慮的是以下方面的技術細節:
首先,有些廣播 intent 只能由具有特定權限的進程發送,而有些廣播 intent 在發送之前需要做一些其他動作。當然,如果發送方進程是系統進程、phone 進程、shell 進程等具有 root 權限的進程,那么必然有權發出廣播。
另外,有時候用戶希望發送 sticky 廣播,以便日后注冊的 receiver 可以收到 “錯過” 的 sticky 廣播。要達到這個目的,系統必須在內部維護一張 sticky 廣播表,在具體的實現中,AMS 會把 sticky 類型的廣播 intent 加入到 mStickyBroadcasts 映射表中。
mStickyBroadcasts 是一張哈希映射表,其 key 值為 intent 的 action 字符串,value 值為 “與這個 action 對應的 intent 數組列表” 的引用。當我們發送 sticky 廣播時,新的廣播 intent 要么替換掉 intent 數組列表中的某項,要么作為一個新項被添加進數組列表,以備日后使用。
發送廣播時,還需要考慮所發送的廣播是否需要有序(ordered)遞送。而且,receiver 本身又分為動態注冊和靜態聲明的,這讓我們面對的情況更加復雜。從目前的代碼來看,靜態 receiver 一直是按照有序方式遞送的,而動態 receiver 則需要根據 ordered 參數的值,做不同的處理。當我們需要有序遞送時,AMS 會把動態 receivers 和靜態 receivers 合并到一張表中,這樣才能依照 receiver 的優先級,做出正確的處理,此時動態 receivers 和靜態 receivers 可能呈現一種交錯順序。
另一方面,有些廣播是需要發給特定目標組件的,這個也要加以考慮。
現在我們來分析 broadcastIntentLocked() 函數。我們可以將其邏輯大致整理成以下幾步:
- 為 intent 添加 FLAG_EXCLUDE_STOPPED_PACKAGES 標記
- 判斷進程是否有權限發出廣播
- 處理和package相關的廣播
- 處理其他一些系統廣播
- 更新系統中的sticky列表
- 查詢和 intent 匹配的靜態 receivers
- 查詢和 intent 匹配的動態 receivers
- 向并行 receivers 遞送廣播
- 整合(剩下的)并行 receivers,以及靜態 receivers,形成一個串行 receivers 表
- 逐個向串行 receivers 遞送廣播
4.3.1 為intent添加FLAG_EXCLUDE_STOPPED_PACKAGES標記
intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
為什么 intent 要添加 FLAG_EXCLUDE_STOPPED_PACKAGES 標記呢?
原因是這樣的,在 Android 3.1 之后,PKMS 加強了對 “處于停止狀態的” 應用的管理。
- 如果一個應用在安裝后從來沒有啟動過
- 或者這個應用已經被用戶強制停止了
符合以上兩個條件的應用,我們稱這個應用處于停止狀態(stopped state)。
為了達到精細調整的目的,Android 增加了2個 flag:FLAG_INCLUDE_STOPPED_PACKAGES 和 FLAG_EXCLUDE_STOPPED_PACKAGES,以此來表示 intent 是否要激活 “處于停止狀態的” 應用。
從上面的 broadcastIntentLocked() 函數可以看到,在默認情況下,AMS 是不會把 intent 廣播發給 “處于停止狀態的” 應用的。
據說 Google 這樣做是為了防止一些流氓軟件或病毒干壞事。當然,如果廣播的發起者認為自己的確需要廣播到 “處于停止狀態的” 應用的話,它可以讓 intent 攜帶 INCLUDE_STOPPED_PACKAGES 標記,從這個標記的注釋可以了解到,如果這兩個標記同時設置的話,那么 INCLUDE 標記會 “取勝”,它會覆蓋掉 framework 自動添加的 FLAG_EXCLUDE_STOPPED_PACKAGES 標記。
4.3.2 判斷進程是否有權限發出廣播
接著,broadcastIntentLocked() 會判斷發送廣播的進程是否有權限發出廣播,代碼截選如下:
// Verify that protected broadcasts are only being sent by system code,// and that system code is only sending protected broadcasts.final boolean isProtectedBroadcast; // 保護性廣播try {isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);} catch (RemoteException e) {Slog.w(TAG, "Remote exception", e);return ActivityManager.BROADCAST_SUCCESS;}final boolean isCallerSystem;switch (UserHandle.getAppId(callingUid)) {case ROOT_UID:case SYSTEM_UID:case PHONE_UID:case BLUETOOTH_UID:case NFC_UID:case SE_UID:case NETWORK_STACK_UID:isCallerSystem = true;break;default:isCallerSystem = (callerApp != null) &&callerApp.isPersistent();break;}if (!isCallerSystem) {if (isProtectedBroadcast) {String msg = "Permission Denial: not allowed to send broadcast "+ action + " from pid="+ callingPid + ", uid=" + callingUid;Slog.w(TAG, msg);throw new SecurityException(msg);} else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)|| AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {if (callerPackage == null) {String msg = "Permission Denial: not allowed to send broadcast "+ action + " from unknown caller.";......} else if (intent.getComponent() != null) {......} else {// Limit broadcast to their own package.intent.setPackage(callerPackage);}}}
?
如果發起方的 UID 為 SYSTEM_UID、PHONE_UID 或 BLUETOOTH_UID 等,或者發起方具有 root 權限,那么它一定有權限發送廣播。
另外,還有一個 “保護性廣播” 的概念,也要考慮進來。有些同學會詢問 AndroidManifest.xml 中的一級標記 <protected-broadcast> 是什么意思。簡單地說,Google 認為有一些廣播是只能由系統發送,如果某個系統級 AndroidManifest.xml 中寫了這個標記,那么在 PKMS 解析該文件時,就會把 “保護性廣播” 標記中的名字(一般是 Action 字符串)記錄下來。在系統運作起來之后,如果某個不具有系統權限的應用試圖發送系統中的 “保護性廣播”,那么到 AMS 的 broadcastIntentLocked() 處就會被攔住,AMS 會拋出異常,提示 “Permission Denial: not allowed to send broadcast”。
以下是 framework/base/core/res/AndroidManifest.xml 文件中部分保護性廣播示例:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="android" coreApp="true" android:sharedUserId="android.uid.system"android:sharedUserLabel="@string/android_system_label"><!-- ================================================ --><!-- Special broadcasts that only the system can send --><!-- ================================================ --><eat-comment /><protected-broadcast android:name="android.intent.action.SCREEN_OFF" /><protected-broadcast android:name="android.intent.action.SCREEN_ON" /><protected-broadcast android:name="android.intent.action.USER_PRESENT" /><protected-broadcast android:name="android.intent.action.TIME_SET" /><protected-broadcast android:name="android.intent.action.TIME_TICK" /><protected-broadcast android:name="android.intent.action.TIMEZONE_CHANGED" />......
</manifest>
4.3.3 處理和package相關的廣播
接下來需要處理一些系統級的 “Package 廣播”,這些主要從 PKMS 處發來。
比如,當 PKMS 處理 APK 的添加、刪除或改動時,一般會發出類似下面的廣播:
- ACTION_PACKAGE_ADDED
- ACTION_PACKAGE_REMOVED
- ACTION_PACKAGE_CHANGED
- ACTION_PACKAGE_REPLACED 等廣播。
AMS 必須確保發送 “包廣播” 的發起方具有 BROADCAST_PACKAGE_REMOVED 權限,如果沒有,那么 AMS 會拋出異常(SecurityException)。
接著,AMS 判斷如果是某個用戶 id 被刪除了的話(Intent.ACTION_UID_REMOVED),那么必須把這件事通知給 “電池狀態服務”。
另外,如果是 SD 卡等外部設備上的應用不可用了,這常常是因為卡被 unmount 了,此時 PKMS 會發出 Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE,而 AMS 則需要把 SD 卡上的所有包都強制停止,并立即發出另一個 “Package 廣播”:EXTERNAL_STORAGE_UNAVAILABLE。
如果是某個外部包被刪除或改動了(ACTION_PACKAGE_REMOVED / REPLACED),則要進一步判斷 intent 里是否攜帶了 EXTRA_DONT_KILL_APP 額外數據,如果沒有攜帶,說明需要立即強制結束 package,否則,不強制結束 package。看來有些應用即使在刪除或改動了包后,還會在系統(內存)中保留下來并繼續運行。另外,如果是刪除包的話,此時要發出 PACKAGE_REMOVED 廣播。
等等,以上只是簡單列舉了一些典型的包處理廣播,還有很多針對其它的包廣播的處理,再次不再贅述,同學們可以自行查看代碼研究。
4.3.4 處理其他一些系統廣播
broadcastIntentLocked() 不但要對 “Package 廣播” 進行處理,還要關心其他一些系統廣播。比如:
ACTION_TIMEZONE_CHANGED
ACTION_TIME_CHANGED
ACTION_CLEAR_DNS_CACHE
PROXY_CHANGE_ACTION
等等,感興趣的同學可以自行研究這些廣播的意義。
4.3.5 更新系統中的sticky列表
接著,broadcastIntentLocked() 中會判斷當前發出的是否是 sticky 廣播,如果是的話,必須把廣播 intent 記錄下來。
一開始會判斷一下發起方是否具有發出 sticky 廣播的權限,比如說要擁有 BROADCAST_STICKY 權限等等。判斷合格后,broadcastIntentLocked() 會更新 AMS 里的一張表:mStickyBroadcasts,其處理代碼如下:
// Add to the sticky list if requested.
if (sticky) {// 檢查權限if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,callingPid, callingUid)!= PackageManager.PERMISSION_GRANTED) {String msg = "Permission Denial: broadcastIntent() requesting a" +" sticky broadcast from pid="+ callingPid + ", uid=" + callingUid+ " requires " + android.Manifest.permission.BROADCAST_STICKY;Slog.w(TAG, msg);throw new SecurityException(msg);}if (requiredPermissions != null && requiredPermissions.length > 0) {// sticky 廣播不能 enforece permissionsSlog.w(TAG, "Can't broadcast sticky intent " + intent+ " and enforce permissions " + Arrays.toString(requiredPermissions));return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;}// sticky 廣播不能指定特定的目標組件if (intent.getComponent() != null) {throw new SecurityException("Sticky broadcasts can't target a specific component");}// 如果用戶 id 不是全局的, 也就是給特定用戶發送的 sticky 廣播// 檢查這個廣播是否和全局的 mStickyBroadcasts 列表中的廣播有沖突 if (userId != UserHandle.USER_ALL) {ArrayMap<String, ArrayList<Intent>> stickies =mStickyBroadcasts.get(UserHandle.USER_ALL);if (stickies != null) {ArrayList<Intent> list = stickies.get(intent.getAction());if (list != null) {int N = list.size();int i;for (i=0; i<N; i++) {if (intent.filterEquals(list.get(i))) {throw new IllegalArgumentException("Sticky broadcast " + intent + " for user "+ userId + " conflicts with existing global broadcast");}}}}}// 真正開始更新 mStickyBroadcasts 列表信息ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);if (stickies == null) { // 空表的話就創建一個新的stickies = new ArrayMap<>();mStickyBroadcasts.put(userId, stickies);}ArrayList<Intent> list = stickies.get(intent.getAction());if (list == null) { // 表中沒有對應的 action,則創建并添加list = new ArrayList<>();stickies.put(intent.getAction(), list);}final int stickiesCount = list.size();int i;// 遍歷 action 對應 intent 列表,如果不存在則添加,如果存在則更新for (i = 0; i < stickiesCount; i++) {if (intent.filterEquals(list.get(i))) {// This sticky already exists, replace it.list.set(i, new Intent(intent));break;}}if (i >= stickiesCount) {list.add(new Intent(intent));}
}
mStickyBroadcasts 的定義是這樣的:
ActivityManagerService.java
final SparseArray<ArrayMap<String, ArrayList<Intent>>> mStickyBroadcasts =new SparseArray<ArrayMap<String, ArrayList<Intent>>>();
上面代碼的 filterEquals() 函數會比較兩個 intent 的 action、data、type、identifier、package、component 以及 categories 等信息,但不會比較 extra 數據。
如果兩個 intent 的 action 是一樣的,但其他信息不同,那么它們在 ArrayList<Intent> 中會被記成兩個不同的 intent。而如果發現新發送的 intent 在 ArrayList 中已經有個 “相等的” 舊 intent 時,則會用新的替掉舊的。
以后,每當 AMS 收到注冊新的動態 receiver 時,注冊動作中都會遍歷一下 mStickyBroadcast 表,看哪些 intent 可以和新 receiver 的 filter 匹配,只有匹配的 intent 才會遞送給新 receiver,示意圖如下:
圖中通過動態注冊的新 receiver 的 filter 只對 a1 和 a3 這兩個 action 感興趣,所以遍歷時就不會考慮 mStickyBroadcast 表中的 a2 表項對應的子表,而 a1、a3 子表所對應的若干 intent 中又只有一部分可以和 filter 匹配,比如 a1 的 intent1 以及 a3 的 intent2,所以圖中只選擇了這兩個 intent 遞送給新 receiver。
除了更新 mStickyBoradcast 表的動作以外,sticky 廣播和普通廣播在 broadcastIntentLocked() 中的代碼是一致的,并沒有其他什么不同了。
4.3.6 向并行receivers遞送廣播
然后 broadcastIntentLocked() 會嘗試向并行 receivers 遞送廣播。
此時會調用到 queue.scheduleBroadcastsLocked()。相關代碼截選如下:
// Figure out who all will receive this broadcast.
// 先找出所有接收此廣播的 receives 列表List receivers = null;List<BroadcastFilter> registeredReceivers = null;// Need to resolve the intent to interested receivers...if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)== 0) {receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);}if (intent.getComponent() == null) {......registeredReceivers = mReceiverResolver.queryIntent(intent,resolvedType, false /*defaultOnly*/, userId);......}......int NR = registeredReceivers != null ? registeredReceivers.size() : 0;if (!ordered && NR > 0) { // 不是有序的廣播if (isCallerSystem) {checkBroadcastFromSystem(intent, callerApp, callerPackage,callingUid, isProtectedBroadcast, registeredReceivers);}// 獲取廣播隊列 BroadcastQueue,可能是前臺廣播隊列,也可能是后臺廣播隊列final BroadcastQueue queue = broadcastQueueForIntent(intent);// 構建廣播的數據結構 BroadcastRecordBroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,callerPackage, callingPid, callingUid, ......);final boolean replaced = replacePending&& (queue.replaceParallelBroadcastLocked(r) != null);// Note: We assume resultTo is null for non-ordered broadcasts.if (!replaced) {queue.enqueueParallelBroadcastLocked(r);// 關鍵部分:執行具體的廣播的發送操作queue.scheduleBroadcastsLocked();}registeredReceivers = null;NR = 0;}
簡單地說,就是 new 一個 BroadcastRecord 節點,并插入 BroadcastQueue 內的并行處理隊列,最后發起實際的廣播調度:scheduleBroadcastsLocked()。關于上面代碼中的 registeredReceivers 列表,我們會在后文說明,這里先跳過。
其實不光并行處理部分需要一個 BroadcastRecord 節點,串行處理部分也需要 BroadcastRecord 節點。也就是說,要發送一次廣播,AMS 必須構造一個或兩個 BroadcastRecord 節點,并將之插入合適的廣播隊列(mFgBroadcastQueue 或 mBgBroadcastQueue)。插入成功后,再執行隊列的 scheduleBroadcastsLocked() 動作,進行實際的派發調度。示意圖如下:
請注意圖中 BroadcastRecord 節點所攜帶的節點鏈。
在 mParallelBroadcasts 表中,每個 BroadcastRecord 只可能攜帶 BroadcastFilter,因為平行處理的節點只會對應動態 receiver,而所有靜態 receiver 只能是串行處理的
另一方面,在 mOrderedBroadcasts 表中,BroadcastRecord 中則既可能攜帶 BroadcastFilter,也可能攜帶 ResolveInfo。這個其實很容易理解,首先,ResolveInfo 對應靜態 receiver,放到這里自不待言,其次,如果用戶在發送廣播時明確指定要按 ordered 方式發送的話,那么即使目標方的 receiver 是動態注冊的,它對應的 BroadcastFilter 也會被強制放到這里
現在讓我們再整合一下思路,做下小結:
- BroadcastRecord 節點內部的 receivers 列表,記錄著和這個廣播動作相關的目標 receiver 信息,該列表內部的子節點可能是 ResolveInfo 類型的,也可能是 BroadcastFilter 類型的。
- ResolveInfo 是從 PKMS 處查到的靜態 receiver 的描述信息,它的源頭是 PKMS 分析的那些 AndroidManifest.xml 文件。
而 BroadcastFilter,來自于一開始闡述動態 receiver 時,提到的 AMS 端的 mRegisteredReceivers 哈希映射表。現在,我們再畫一張示意圖:
?
因為 BroadcastRecord 里的 BroadcastFilter,和 AMS 的 mRegisteredReceivers 表中(間接)所指的對應 BroadcastFilter 是同一個對象,所以我是用虛線將它們連起來的。
Ok,我們接著看 scheduleBroadcastsLocked() 動作。scheduleBroadcastsLocked() 的代碼如下:
BroadcastQueue.java
public void scheduleBroadcastsLocked() {if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["+ mQueueName + "]: current="+ mBroadcastsScheduled);if (mBroadcastsScheduled) {return;}mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));mBroadcastsScheduled = true;}
發出 BROADCAST_INTENT_MSG 消息。我們接下來看對這個消息的處理:
BroadcastQueue.java
private final class BroadcastHandler extends Handler {public BroadcastHandler(Looper looper) {super(looper, null, true);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case BROADCAST_INTENT_MSG: {if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["+ mQueueName + "]");processNextBroadcast(true);} break;case BROADCAST_TIMEOUT_MSG: {synchronized (mService) {broadcastTimeoutLocked(true);}} break;}}}
?也就是說,AMS 端會在 BroadcastQueue.java 中的 processNextBroadcast() 具體處理廣播。我們在下一篇文章中重點分析這個函數。
4.3.7 整理兩個receiver列表
我們前文已經說過,有些廣播是需要有序遞送的。為了合理處理 “有序遞送” 和 “平行遞送”,broadcastIntentLocked() 函數內部搞出了兩個 list:
List receivers = null;
List<BroadcastFilter> registeredReceivers = null;
其中,receivers 主要用于記錄 “有序遞送” 的 receiver,而 registeredReceivers 則用于記錄與 intent 相匹配的動態注冊的 receiver。
關于這兩個 list 的大致運作是這樣的:我們先利用包管理器的 queryIntentReceivers() 接口,查詢出和 intent 匹配的所有靜態 receivers,此時所返回的查詢結果本身已經排好序了,因此,該返回值被直接賦值給了 receivers 變量,代碼如下:
receivers = AppGlobals.getPackageManager().queryIntentReceivers(intent, resolvedType, pmFlags, user).getList();
而對于動態注冊的 receiver 信息,就不是從包管理器獲取了,這些信息本來就記錄在 AMS 之中,此時只需調用
registeredReceivers = mReceiverResolver.queryIntent(intent,resolvedType, false /*defaultOnly*/, userId);
就可以了。注意,此時返回的 registeredReceivers 中的子項是沒有經過排序的。而關于 PKMS 的 queryIntentReceivers(),我們可以參考 PKMS 的專題文檔,此處不再贅述。
- 如果是 “并行遞送” 廣播, registeredReceivers 中的各個 receiver 會在 scheduleBroadcastsLocked() 函數中被并行處理掉
- 大家回頭看看向并行 receivers 發送廣播的代碼,會發現在調用完 scheduleBroadcastsLocked() 后,registeredReceivers 會被強制賦值成 null 值
- 如果是 “串行遞送” 廣播,那么必須考慮把 registeredReceivers 表合并到 receivers 表中去
- 我們知道,receivers 列表中只記錄了一些靜態 receiver,這些 receiver 將會被 “有序遞送”。現在我們只需再遍歷一下 registeredReceivers 列表,并將其中的每個子項插入到 receivers 列表的合適地方,就可以合并出一條順序列表了。當然,如果 registeredReceivers 已經被設為 null 了,就無所謂合并了
為什么靜態聲明的 receiver 只會 “有序遞送” 呢?我想也許和這種 receiver 的復雜性有關系,因為在需要遞送廣播時,receiver 所屬的進程可能還沒有啟動呢,所以也許會涉及到啟動進程的流程,這些都是比較復雜的流程。
當然,上面所說的是沒有明確指定目標組件的情況,如果 intent 里含有明確的目標信息,那么就不需要調用包管理器的 queryIntentReceivers() 了,只需 new 一個 ArrayList,并賦值給 receivers,然后把目標組件對應的 ResolveInfo 信息添加進 receivers 數組列表即可。
4.3.8 逐個向串行receivers遞送廣播
當 receivers 列表整理完畢之后,現在要開始嘗試逐個向串行 receivers 遞送廣播了。正如前文所說,這里要重新 new 一個新的 BroadcastRecord 節點:
if ((receivers != null && receivers.size() > 0)|| resultTo != null) {BroadcastQueue queue = broadcastQueueForIntent(intent);BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,callerPackage, callingPid, callingUid, callerInstantApp, resolvedType,requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,resultData, resultExtras, ordered, sticky, false, userId,allowBackgroundActivityStarts, timeoutExempt);if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,"Enqueueing ordered broadcast " + r);final BroadcastRecord oldRecord =replacePending ? queue.replaceOrderedBroadcastLocked(r) : null;if (oldRecord != null) {// Replaced, fire the result-to receiver.if (oldRecord.resultTo != null) {final BroadcastQueue oldQueue =broadcastQueueForIntent(oldRecord.intent);try {oldQueue.performReceiveLocked(oldRecord.callerApp,oldRecord.resultTo, oldRecord.intent,Activity.RESULT_CANCELED, null, null,false, false, oldRecord.userId);} catch (RemoteException e) {Slog.w(TAG, "Failure ["+ queue.mQueueName + "] sending broadcast result of "+ intent, e);}}} else {queue.enqueueOrderedBroadcastLocked(r);queue.scheduleBroadcastsLocked();}
}
而 scheduleBroadcastsLocked() 最終會間接導致走到 BroadcastQueue.java 中的 processNextBroadcast()。這一點和前文所說的 “向并行 receivers 遞送廣播” 的動作基本一致。
4.4 BroadcastQueue.processNextBroadcast
從 processNextBroadcast() 的代碼,我們就可以看清楚前面說的 “平行廣播”、“有序廣播” 和 “動態 receiver”、“靜態 receiver” 之間的關系了。
可以說:processNextBroadcast 函數是廣播處理的核心。
我們在前文已經說過,所有的靜態 receiver 都是串行處理的,而動態 receiver 則會按照發廣播時指定的方式,進行 “并行” 或 “串行” 處理。
能夠并行處理的廣播,其對應的若干 receiver 一定都已經存在了,不會牽扯到啟動新進程的操作,所以可以在一個 while 循環中,一次性全部 deliver。
而有序廣播,則需要一個一個地處理,其循環處理的手段是發送事件,也就是說,在一個 receiver 處理完畢后,會利用廣播隊列(BroadcastQueue)的 mHandler,發送一個 BROADCAST_INTENT_MSG 事件,從而執行下一次的 processNextBroadcast()。
BroadcastQueue.java
final void processNextBroadcast(boolean fromMsg) {synchronized (mService) {processNextBroadcastLocked(fromMsg, false);}
}
繼續調用 processNextBroadcastLocked,這個函數很長,我們先看下全部的代碼,然后下面進行詳細分析。
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {BroadcastRecord r;if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast ["+ mQueueName + "]: "+ mParallelBroadcasts.size() + " parallel broadcasts; "+ mDispatcher.describeStateLocked());mService.updateCpuStats();if (fromMsg) {mBroadcastsScheduled = false;}// First, deliver any non-serialized broadcasts right away.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);}// Now take care of the next serialized one...// If we are waiting for a process to come up to handle the next// broadcast, then do nothing at this point. Just in case, we// check that the process we're waiting for still exists.if (mPendingBroadcast != null) {if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,"processNextBroadcast [" + mQueueName + "]: waiting for "+ mPendingBroadcast.curApp);boolean isDead;if (mPendingBroadcast.curApp.pid > 0) {synchronized (mService.mPidsSelfLocked) {ProcessRecord proc = mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.pid);isDead = proc == null || proc.isCrashing();}} else {final ProcessRecord proc = mService.mProcessList.mProcessNames.get(mPendingBroadcast.curApp.processName,mPendingBroadcast.curApp.uid);isDead = proc == null || !proc.pendingStart;}if (!isDead) {// It's still alive, so keep waitingreturn;} else {Slog.w(TAG, "pending app ["+ mQueueName + "]" + mPendingBroadcast.curApp+ " died before responding to broadcast");mPendingBroadcast.state = BroadcastRecord.IDLE;mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;mPendingBroadcast = null;}}boolean looped = false;do {final long now = SystemClock.uptimeMillis();r = mDispatcher.getNextBroadcastLocked(now);if (r == null) {// No more broadcasts are deliverable right now, so all done!mDispatcher.scheduleDeferralCheckLocked(false);mService.scheduleAppGcsLocked();if (looped) {// If we had finished the last ordered broadcast, then// make sure all processes have correct oom and sched// adjustments.mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);}// when we have no more ordered broadcast on this queue, stop loggingif (mService.mUserController.mBootCompleted &&mLogLatencyMetrics) {mLogLatencyMetrics = false;}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))) {Slog.w(TAG, "Hung broadcast ["+ mQueueName + "] discarded after timeout failure:"+ " now=" + now+ " dispatchTime=" + r.dispatchTime+ " startTime=" + r.receiverTime+ " intent=" + r.intent+ " numReceivers=" + numReceivers+ " nextReceiver=" + r.nextReceiver+ " state=" + r.state);broadcastTimeoutLocked(false); // forcibly finish this broadcastforceReceive = true;r.state = BroadcastRecord.IDLE;}}if (r.state != BroadcastRecord.IDLE) {if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,"processNextBroadcast("+ mQueueName + ") called when not idle (state="+ r.state + ")");return;}// Is the current broadcast is done for any reason?if (r.receivers == null || r.nextReceiver >= numReceivers|| r.resultAbort || forceReceive) {// Send the final result if requestedif (r.resultTo != null) {boolean sendResult = true;if (r.splitToken != 0) {int newCount = mSplitRefcounts.get(r.splitToken) - 1;if (newCount == 0) {mSplitRefcounts.delete(r.splitToken);} else {sendResult = false;mSplitRefcounts.put(r.splitToken, newCount);}}if (sendResult) {try {performReceiveLocked(r.callerApp, r.resultTo,new Intent(r.intent), r.resultCode,r.resultData, r.resultExtras, false,false, r.userId);r.resultTo = null;} catch (RemoteException e) {r.resultTo = null;Slog.w(TAG, "Failure ["+ mQueueName + "] sending broadcast result of "+ r.intent, e);}}}cancelBroadcastTimeoutLocked();if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,"Finished with ordered broadcast " + r);// ... and on to the next...addBroadcastToHistoryLocked(r);if (r.intent.getComponent() == null && r.intent.getPackage() == null&& (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {mService.addBroadcastStatLocked(r.intent.getAction(),r.callerPackage, r.manifestCount, r.manifestSkipCount,r.finishTime-r.dispatchTime);}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)) {BroadcastRecord defer;if (r.nextReceiver + 1 == numReceivers) {defer = r;mDispatcher.retireBroadcastLocked(r);} else {defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);if (DEBUG_BROADCAST_DEFERRAL) {Slog.i(TAG_BROADCAST, "Post split:");Slog.i(TAG_BROADCAST, "Original broadcast receivers:");for (int i = 0; i < r.receivers.size(); i++) {Slog.i(TAG_BROADCAST, " " + r.receivers.get(i));}Slog.i(TAG_BROADCAST, "Split receivers:");for (int i = 0; i < defer.receivers.size(); i++) {Slog.i(TAG_BROADCAST, " " + defer.receivers.get(i));}}// Track completion refcount as well if relevantif (r.resultTo != null) {int token = r.splitToken;if (token == 0) {r.splitToken = defer.splitToken = nextSplitTokenLocked();mSplitRefcounts.put(r.splitToken, 2);} else {final int curCount = mSplitRefcounts.get(token);mSplitRefcounts.put(token, curCount + 1);}}}mDispatcher.addDeferredBroadcast(receiverUid, defer);r = null;looped = true;continue;}}} while (r == null);// Get the next receiver...int recIdx = r.nextReceiver++;// Keep track of when this receiver started, and make sure there// is a timeout message pending to kill it if need be.r.receiverTime = SystemClock.uptimeMillis();if (recIdx == 0) {r.dispatchTime = r.receiverTime;r.dispatchClockTime = System.currentTimeMillis();if (mLogLatencyMetrics) {StatsLog.write(StatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,r.dispatchClockTime - r.enqueueClockTime);}if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),System.identityHashCode(r));Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),System.identityHashCode(r));}}if (! mPendingBroadcastTimeoutMessage) {long timeoutTime = r.receiverTime + mConstants.TIMEOUT;if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,"Submitting BROADCAST_TIMEOUT_MSG ["+ mQueueName + "] for " + r + " at " + timeoutTime);setBroadcastTimeoutLocked(timeoutTime);}final BroadcastOptions brOptions = r.options;final Object nextReceiver = r.receivers.get(recIdx);if (nextReceiver instanceof BroadcastFilter) {// Simple case: this is a registered receiver who gets// a direct call.BroadcastFilter filter = (BroadcastFilter)nextReceiver;if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,"Delivering ordered ["+ mQueueName + "] to registered "+ filter + ": " + r);deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);if (r.receiver == null || !r.ordered) {// The receiver has already finished, so schedule to// process the next one.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);}if (brOptions != null &&brOptions.getTemporaryAppWhitelistDuration() > 0) {scheduleTempWhitelistLocked(filter.owningUid,brOptions.getTemporaryAppWhitelistDuration(), r);}}return;}// Hard case: need to instantiate the receiver, possibly// starting its application process to host it.ResolveInfo info =(ResolveInfo)nextReceiver;ComponentName component = new ComponentName(info.activityInfo.applicationInfo.packageName,info.activityInfo.name);boolean skip = false;if (brOptions != null &&(info.activityInfo.applicationInfo.targetSdkVersion< brOptions.getMinManifestReceiverApiLevel() ||info.activityInfo.applicationInfo.targetSdkVersion> brOptions.getMaxManifestReceiverApiLevel())) {skip = true;}if (!skip && !mService.validateAssociationAllowedLocked(r.callerPackage,r.callingUid, component.getPackageName(),info.activityInfo.applicationInfo.uid)) {......skip = true;}if (!skip) {skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);if (skip) {......}}int perm = mService.checkComponentPermission(info.activityInfo.permission,r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,info.activityInfo.exported);if (!skip && perm != PackageManager.PERMISSION_GRANTED) {if (!info.activityInfo.exported) {......} else {......}skip = true;} else if (!skip && info.activityInfo.permission != null) {final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission);if (opCode != AppOpsManager.OP_NONE&& mService.mAppOpsService.noteOperation(opCode, r.callingUid,r.callerPackage) != AppOpsManager.MODE_ALLOWED) {......skip = true;}}if (!skip && info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID && r.requiredPermissions != null &&r.requiredPermissions.length > 0) {for (int i = 0; i < r.requiredPermissions.length; i++) {String requiredPermission = r.requiredPermissions[i];try {perm = AppGlobals.getPackageManager().checkPermission(requiredPermission,info.activityInfo.applicationInfo.packageName,UserHandle.getUserId(info.activityInfo.applicationInfo.uid));} catch (RemoteException e) {perm = PackageManager.PERMISSION_DENIED;}if (perm != PackageManager.PERMISSION_GRANTED) {Slog.w(TAG, "Permission Denial: receiving "+ r.intent + " to "+ component.flattenToShortString()+ " requires " + requiredPermission+ " due to sender " + r.callerPackage+ " (uid " + r.callingUid + ")");skip = true;break;}int appOp = AppOpsManager.permissionToOpCode(requiredPermission);if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp&& mService.mAppOpsService.noteOperation(appOp,info.activityInfo.applicationInfo.uid, info.activityInfo.packageName)!= AppOpsManager.MODE_ALLOWED) {Slog.w(TAG, "Appop Denial: receiving "+ r.intent + " to "+ component.flattenToShortString()+ " requires appop " + AppOpsManager.permissionToOp(requiredPermission)+ " due to sender " + r.callerPackage+ " (uid " + r.callingUid + ")");skip = true;break;}}}if (!skip && r.appOp != AppOpsManager.OP_NONE&& mService.mAppOpsService.noteOperation(r.appOp,info.activityInfo.applicationInfo.uid, info.activityInfo.packageName)!= AppOpsManager.MODE_ALLOWED) {Slog.w(TAG, "Appop Denial: receiving "+ r.intent + " to "+ component.flattenToShortString()+ " requires appop " + AppOpsManager.opToName(r.appOp)+ " due to sender " + r.callerPackage+ " (uid " + r.callingUid + ")");skip = true;}boolean isSingleton = false;try {isSingleton = mService.isSingleton(info.activityInfo.processName,info.activityInfo.applicationInfo,info.activityInfo.name, info.activityInfo.flags);} catch (SecurityException e) {Slog.w(TAG, e.getMessage());skip = true;}if ((info.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {if (ActivityManager.checkUidPermission(android.Manifest.permission.INTERACT_ACROSS_USERS,info.activityInfo.applicationInfo.uid)!= PackageManager.PERMISSION_GRANTED) { skip = true;}}if (!skip && info.activityInfo.applicationInfo.isInstantApp()&& r.callingUid != info.activityInfo.applicationInfo.uid) {Slog.w(TAG, "Instant App Denial: receiving "+ r.intent+ " to " + component.flattenToShortString()+ " due to sender " + r.callerPackage+ " (uid " + r.callingUid + ")"+ " Instant Apps do not support manifest receivers");skip = true;}if (!skip && r.callerInstantApp&& (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0&& r.callingUid != info.activityInfo.applicationInfo.uid) {Slog.w(TAG, "Instant App Denial: receiving "+ r.intent+ " to " + component.flattenToShortString()+ " requires receiver have visibleToInstantApps set"+ " due to sender " + r.callerPackage+ " (uid " + r.callingUid + ")");skip = true;}if (r.curApp != null && r.curApp.isCrashing()) {// If the target process is crashing, just skip it.Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r+ " to " + r.curApp + ": process crashing");skip = true;}if (!skip) {boolean isAvailable = false;try {isAvailable = AppGlobals.getPackageManager().isPackageAvailable(info.activityInfo.packageName,UserHandle.getUserId(info.activityInfo.applicationInfo.uid));} catch (Exception e) {// all such failures mean we skip this receiverSlog.w(TAG, "Exception getting recipient info for "+ info.activityInfo.packageName, e);}if (!isAvailable) {skip = true;}} if (!skip) {if (!requestStartTargetPermissionsReviewIfNeededLocked(r,info.activityInfo.packageName, UserHandle.getUserId(info.activityInfo.applicationInfo.uid))) {skip = true;}}// This is safe to do even if we are skipping the broadcast, and we need// this information now to evaluate whether it is going to be allowed to run.final int receiverUid = info.activityInfo.applicationInfo.uid;// If it's a singleton, it needs to be the same app or a special appif (r.callingUid != Process.SYSTEM_UID && isSingleton&& mService.isValidSingletonCall(r.callingUid, receiverUid)) {info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0);}String targetProcess = info.activityInfo.processName;ProcessRecord app = mService.getProcessRecordLocked(targetProcess,info.activityInfo.applicationInfo.uid, false);if (!skip) {final int allowed = mService.getAppStartModeLocked(info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,info.activityInfo.applicationInfo.targetSdkVersion,-1, true, false, false);if (allowed != ActivityManager.APP_START_MODE_NORMAL) {// We won't allow this receiver to be launched if the app has been// completely disabled from launches, or it was not explicitly sent// to it and the app is in a state that should not receive it// (depending on how getAppStartModeLocked has determined that).if (allowed == ActivityManager.APP_START_MODE_DISABLED) {Slog.w(TAG, "Background execution disabled: receiving "+ r.intent + " to "+ component.flattenToShortString());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());Slog.w(TAG, "Background execution not allowed: receiving "+ r.intent + " to "+ component.flattenToShortString());skip = true;}}}if (!skip && !Intent.ACTION_SHUTDOWN.equals(r.intent.getAction())&& !mService.mUserController.isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid), 0 /* flags */)) {skip = true;}if (skip) {if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,"Skipping delivery of ordered [" + mQueueName + "] "+ r + " for reason described above");r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;r.receiver = null;r.curFilter = null;r.state = BroadcastRecord.IDLE;r.manifestSkipCount++;scheduleBroadcastsLocked();return;}r.manifestCount++;r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;r.state = BroadcastRecord.APP_RECEIVE;r.curComponent = component;r.curReceiver = info.activityInfo;if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "+ info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "+ receiverUid);}if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) {scheduleTempWhitelistLocked(receiverUid,brOptions.getTemporaryAppWhitelistDuration(), r);}// Broadcast is being executed, its package can't be stopped.try {AppGlobals.getPackageManager().setPackageStoppedState(r.curComponent.getPackageName(), false, r.userId);} catch (RemoteException e) {} catch (IllegalArgumentException e) {Slog.w(TAG, "Failed trying to unstop package "+ r.curComponent.getPackageName() + ": " + e);}// Is this receiver's application already running?if (app != null && app.thread != null && !app.killed) {try {app.addPackage(info.activityInfo.packageName,info.activityInfo.applicationInfo.longVersionCode,mService.mProcessStats);maybeAddAllowBackgroundActivityStartsToken(app, r);processCurBroadcastLocked(r, app, skipOomAdj);return;} catch (RemoteException e) {Slog.w(TAG, "Exception when sending broadcast to "+ r.curComponent, e);} catch (RuntimeException e) {Slog.wtf(TAG, "Failed sending broadcast to "+ r.curComponent + " with " + r.intent, e);// If some unexpected exception happened, just skip// this broadcast. At this point we are not in the call// from a client, so throwing an exception out from here// will crash the entire system instead of just whoever// sent the broadcast.logBroadcastReceiverDiscardLocked(r);finishReceiverLocked(r, r.resultCode, r.resultData,r.resultExtras, r.resultAbort, false);scheduleBroadcastsLocked();// We need to reset the state if we failed to start the receiver.r.state = BroadcastRecord.IDLE;return;}// If a dead object exception was thrown -- fall through to// restart the application.}// Not running -- get it started, to be executed when the app comes up.if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,"Need to start app ["+ mQueueName + "] " + targetProcess + " for broadcast " + r);if ((r.curApp=mService.startProcessLocked(targetProcess,info.activityInfo.applicationInfo, true,r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,new HostingRecord("broadcast", r.curComponent),(r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0,false, false)) == 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;}maybeAddAllowBackgroundActivityStartsToken(r.curApp, r);mPendingBroadcast = r;mPendingBroadcastRecvIndex = recIdx;}
processNextBroadcastLocked() 的代碼邏輯大體是這樣的:先嘗試處理 BroadcastQueue 中的 “平行廣播” 部分。這需要遍歷并行列表(mParallelBroadcasts)的每一個 BroadcastRecord 以及其中的 receivers 列表。對于平行廣播而言,receivers 列表中的每個子節點是個 BroadcastFilter。我們直接將廣播遞送出去即可:
// First, deliver any non-serialized broadcasts right away.
// 遍歷 mParallelBroadcasts 取出其中的每一個 BroadcastRecord
while (mParallelBroadcasts.size() > 0) {r = mParallelBroadcasts.remove(0);r.dispatchTime = SystemClock.uptimeMillis();r.dispatchClockTime = System.currentTimeMillis();......// 取出廣播 BroadcastRecord 中對應的所有 receivers,然后發送廣播final int N = r.receivers.size();for (int i=0; i<N; i++) {Object target = r.receivers.get(i);if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,"Delivering non-ordered on [" + mQueueName + "] to registered "+ target + ": " + r);// 發送廣播deliverToRegisteredReceiverLocked(r,(BroadcastFilter)target, false, i);}addBroadcastToHistoryLocked(r);if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast [" + mQueueName + "] " + r);
}
4.4.1 deliverToRegisteredReceiverLocked()
private void deliverToRegisteredReceiverLocked(BroadcastRecord r,BroadcastFilter filter, boolean ordered, int index) {boolean skip = false;if (!mService.validateAssociationAllowedLocked(r.callerPackage,r.callingUid, filter.packageName, filter.owningUid)) {skip = true;}if (!skip && !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, r.callingPid, r.resolvedType,filter.receiverList.uid)) {skip = true;}......// 一堆判斷條件,決定是否 skip,如果跳過了,則不會發送廣播if (skip) {r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;return;}......// 到這里就開始執行廣播的發送了r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;if (ordered) {// 此時發送的是并行廣播,這個為 false,不會走此分支......}try {if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,"Delivering to " + filter + " : " + r);if (filter.receiverList.app != null && filter.receiverList.app.inFullBackup) {......} else {r.receiverTime = SystemClock.uptimeMillis();maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);// 重點關注 filter.receiverList.receiver 參數,這個就是// IIntentReceiver,用來跨進程通信的performReceiveLocked(filter.receiverList.app,filter.receiverList.receiver,new Intent(r.intent), r.resultCode, r.resultData,r.resultExtras, r.ordered, r.initialSticky, r.userId);if (r.allowBackgroundActivityStarts && !r.ordered) {postActivityStartTokenRemoval(filter.receiverList.app, r);}}......} catch (RemoteException e) {......}
}
調用 performReceiveLocked 正式完成廣播的發送。需要注意的是要想理解廣播是怎么發送的,必須了解 BroadcastFilter 的數據結構組成,這個里邊包含了 receiverList 這個變量,它里邊的 app 是代表注冊 BroadcastReceiver 應用的 ProcessRecord,而 receiverList 里邊還有個變量 receiver 是最重要的,代表的是應用中注冊的 BroadcastReceiver 對應的 ReceiverDispatcher 中的 IIntentReceiver 類型的 mIIntentReceiver,用來實現進程間的數據傳輸。
4.4.1.1 performReceiveLocked
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) { // 進程存在if (app.thread != null) {try {// 完成 AMS 跨進程到客戶端的調用app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras, ordered, sticky,sendingUser, app.getReportedProcState());} catch (RemoteException ex) {......}} else {// Application has died. Receiver doesn't exist.throw new RemoteException("app.thread must not be null");}} else {receiver.performReceive(intent, resultCode, data, extras, ordered,sticky, sendingUser);}}
4.4.1.2 ActivityThread#ApplicationThread.scheduleRegisteredReceiver
ActivityThread.java
public void scheduleRegisteredReceiver(IIntentReceiver receiver,Intent intent, int resultCode, String dataStr, Bundle extras,boolean ordered, boolean sticky, int sendingUser,int processState) throws RemoteException {updateProcessState(processState, false);// 廣播發送到具體的 receiver 中receiver.performReceive(intent, resultCode, dataStr, extras, ordered,sticky, sendingUser);
}
最后調用到 LoadedApk.java 中的 ReceiverDispatcher 中
4.4.2 靜態receiver的遞送
說完動態遞送,我們再來看靜態遞送。對于靜態 receiver,情況會復雜很多,因為靜態 receiver 所從屬的進程有可能還沒有運行起來呢。此時 BroadcastRecord 節點中記錄的子列表的節點是 ResolveInfo 對象。
do {final long now = SystemClock.uptimeMillis();// 從 mDispatcher 中獲取有序廣播列表中的一個 BroadcastRecordr = mDispatcher.getNextBroadcastLocked(now);if (r == null) {......return; // 如果為空,則返回}......// 如果廣播對應的 receiver 為空,或者其它條件成立,則跳過這次循環if (r.receivers == null || r.nextReceiver >= numReceivers|| r.resultAbort || forceReceive) {......r = null;continue;}
} while (r == null);// Get the next receiver...int recIdx = r.nextReceiver++;// Keep track of when this receiver started, and make sure there// is a timeout message pending to kill it if need be.r.receiverTime = SystemClock.uptimeMillis();......final Object nextReceiver = r.receivers.get(recIdx);......// 靜態廣播 receiver 是 ResolveInfo 形式的ResolveInfo info = (ResolveInfo)nextReceiver;ComponentName component = new ComponentName(info.activityInfo.applicationInfo.packageName,info.activityInfo.name);......// 獲取目標 receiver 所在的進程信息String targetProcess = info.activityInfo.processName;ProcessRecord app = mService.getProcessRecordLocked(targetProcess,info.activityInfo.applicationInfo.uid, false);......if (skip) { // 如果skip了,則調用 scheduleBroadcastsLocked 執行下次消息循環......scheduleBroadcastsLocked();return;}......// 廣播發送前的賦值操作r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;r.state = BroadcastRecord.APP_RECEIVE;r.curComponent = component;r.curReceiver = info.activityInfo;......// Broadcast is being executed, its package can't be stopped.try {AppGlobals.getPackageManager().setPackageStoppedState(r.curComponent.getPackageName(), false, r.userId);} catch (RemoteException e) {}......// Is this receiver's application already running?// 靜態 receiver 所在進程是 runnting 狀態if (app != null && app.thread != null && !app.killed) {try {app.addPackage(info.activityInfo.packageName,info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);maybeAddAllowBackgroundActivityStartsToken(app, r);// 關鍵點1: 靜態 receiver 廣播的發送processCurBroadcastLocked(r, app, skipOomAdj);return;} catch (RemoteException e) {Slog.w(TAG, "Exception when sending broadcast to "+ r.curComponent, e);} catch (RuntimeException e) {......return;}}// Not running -- get it started, to be executed when the app comes up.if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,"Need to start app ["+ mQueueName + "] " + targetProcess + " for broadcast " + r);// 關鍵點2:調用 mService.startProcessLocked 啟動新進程if ((r.curApp=mService.startProcessLocked(targetProcess,info.activityInfo.applicationInfo, true,r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,new HostingRecord("broadcast", r.curComponent),(r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) !=0, false, false)) == null) {......return;}maybeAddAllowBackgroundActivityStartsToken(r.curApp, r);mPendingBroadcast = r;mPendingBroadcastRecvIndex = recIdx;
如果目標進程已經存在了,那么 app.thread 肯定不為 null,直接調用 processCurBroadcastLocked() 即可,否則就需要啟動新進程了。啟動的過程是異步的,可能很耗時,所以要把 BroadcastRecord 節點記入 mPendingBroadcast。
4.4.2.1 processCurBroadcastLocked()
private final void processCurBroadcastLocked(BroadcastRecord r,ProcessRecord app, boolean skipOomAdj) throws RemoteException {......r.receiver = app.thread.asBinder();r.curApp = app;app.curReceivers.add(r);app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);......// Tell the application to launch this receiver.r.intent.setComponent(r.curComponent);boolean started = false;try {......// 跨進程調用到 ActivityThread 中app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,mService.compatibilityInfoForPackage(r.curReceiver.applicationInfo),r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,app.getReportedProcState());......started = true;} finally {......}
}
其中 ActivityInfo info 參數,記錄著目標 receiver 的信息。
4.4.2.2 ApplicationThread.scheduleReceiver()
ActivityThread.java
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);
}
然后發送消息 H.RECEIVER,接收到此消息后,調用 handleReceiver((ReceiverData)msg.obj)
4.4.2.3 ActivityThread.handleReceiver
private void handleReceiver(ReceiverData data) {......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 {app = packageInfo.makeApplication(false, mInstrumentation);context = (ContextImpl) app.getBaseContext();......// 通過反射機制生成 receiverjava.lang.ClassLoader cl = context.getClassLoader();data.intent.setExtrasClassLoader(cl);data.intent.prepareToEnterProcess();data.setExtrasClassLoader(cl);receiver = packageInfo.getAppFactory().instantiateReceiver(cl, data.info.name, data.intent);} catch (Exception e) {......}try {......sCurrentBroadcastIntent.set(data.intent);receiver.setPendingResult(data);// 回調 receiver 的 onReceive 函數receiver.onReceive(context.getReceiverRestrictedContext(),data.intent);} catch (Exception e) {......} finally {sCurrentBroadcastIntent.set(null);}if (receiver.getPendingResult() != null) {data.finish();}}
handleReceiver 中,會運用反射機制,創建出 BroadcastReceiver 對象,而后回調該對象的 onReceive() 成員函數。
4.4.2.4 必要時啟動新進程
現在我們回過頭來看,在目標進程尚未啟動的情況下,是如何完成遞送的。剛剛我們已經看到調用 mService.startProcessLocked 的句子了,只要不出問題,目標進程成功啟動后就會調用 AMS 的 attachApplication()。
有關 attachApplication() 的詳情,請參考 Android系統啟動進程創建流程,此處我們只需知道它里面又會調用 attachApplicationLocked() 函數。
AMS.java
private boolean attachApplicationLocked(@NonNull IApplicationThread thread,int pid, int callingUid, long startSeq) {......// Check if a next-broadcast receiver is in this process...if (!badApp && isPendingBroadcastProcessLocked(pid)) {try {didSomething |= sendPendingBroadcastsLocked(app);......} catch (Exception e) {......}}......
}
它們的意思是,如果新啟動的進程就是剛剛 mPendingBroadcast 所記錄的進程的話,此時 AMS 就會執行 sendPendingBroadcastsLocked(app) 一句。
boolean sendPendingBroadcastsLocked(ProcessRecord app) {boolean didSomething = false;for (BroadcastQueue queue : mBroadcastQueues) {didSomething |= queue.sendPendingBroadcastsLocked(app);}return didSomething;
}
BroadcastQueue 的 sendPendingBroadcastsLocked() 函數如下:
public boolean sendPendingBroadcastsLocked(ProcessRecord app) {boolean didSomething = false;final BroadcastRecord br = mPendingBroadcast;if (br != null && br.curApp.pid > 0 && br.curApp.pid == app.pid) {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;// 發送廣播,參考 #### 4.4.2.1processCurBroadcastLocked(br, app, false);didSomething = true;} catch (Exception e) {......}}return didSomething;
}
可以看到,既然目標進程已經成功啟動了,那么 mPendingBroadcast 就可以賦值為 null 了。接著調用 sendPendingBroadcastsLocked() 和前文剛剛闡述的 processCurBroadcastLocked(),其內再通過 app.thread.scheduleReceiver(),將語義發送到用戶進程,完成真正的廣播遞送。這部分在上一小節已有闡述,這里就不多說了。