BroadcastReceiver,似乎很简单!很多书籍上都有描述,从书中例子来看百多行代码就搞定!但是真的如此吗?
假如有个需求,要求大概如下:当蓝牙开始搜索设备时,点亮LED;搜索停止时,关掉LED。这个需求开始检讨时,认为新规一个BroadcastReceiver子类,收到蓝牙开始搜索/停止的广播通知时,点亮/关闭LED即可。可是经过一番调查后,木子发现:BroadcastReceiver没那么简单。让我们一起来看看吧!
第一, sendBroadcast / sendOrderedBroadcast / sendStickyBroadcast是异步函数,它们发出广播通知后即返回,不会等到所有receiver收到通知后才继续往下走,所以控制LED的receiver动作就不会和真正的蓝牙搜索同步,甚至可能搜索完了时,LED才开始点灯在;
涉及的代码比较多,来张时序图吧!
第二, 为什么对sendOrderedBroadcast发出的broadcast,receiver在处理时能重新赋值(通过setResultCode / setResultData / setResultExtras),但是对sendBroadcast发出的broadcast就不行;
看看源代码吧!打开BroadReceiv.java文件,可发现如下代码:
public final void setResultExtras(Bundle extras) {
checkSynchronousHint();
mResultExtras = extras;
}
void checkSynchronousHint() {
// Note that we don't assert when receiving the initial sticky value,
// since that may have come from an ordered broadcast. We'll catch
// them later when the real broadcast happens again.
if (mOrderedHint || mInitialStickyHint) {
return;
}
RuntimeException e = new RuntimeException(
"BroadcastReceiver trying to return result during a non-ordered broadcast");
e.fillInStackTrace();
Log.e("BroadcastReceiver", e.getMessage(), e);
}
在这里,我们发现可否调用setResultExtras之类的函数取决于mOrderedHint || mInitialStickyHint的值,那么它们是何时赋值的呢?
继续看代码,打开ActivityThread.java,发现以下代码:
private final void handleReceiver(ReceiverData data) {
……
ContextImpl context = (ContextImpl)app.getBaseContext();
receiver.setOrderedHint(true);
receiver.setResult(data.resultCode, data.resultData,
data.resultExtras);
receiver.setOrderedHint(data.sync);
receiver.onReceive(context.getReceiverRestrictedContext(),
data.intent);
……
}
还有,ReceiverDispatcher. InnerReceiver. Args. Run() {
……
receiver.setOrderedHint(true);
receiver.setResult(mCurCode, mCurData, mCurMap);
receiver.clearAbortBroadcast();
receiver.setOrderedHint(mCurOrdered);
receiver.setInitialStickyHint(mCurSticky);
receiver.onReceive(mContext, intent);
……
}
现在是不是很清楚原因了?
第三, 什么是sendStickyBroadcast?SDK上说的简单了点,至少木子没看懂!
打开ActivityManagerService.java文件,找到如下代码:
public Intent registerReceiver(IApplicationThread caller,
IIntentReceiver receiver, IntentFilter filter, String permission) {
……
// Look for any matching sticky broadcasts...
Iterator actions = filter.actionsIterator();
if (actions != null) {
while (actions.hasNext()) {
String action = (String)actions.next();
allSticky = getStickiesLocked(action, filter, allSticky);
}
} else {
allSticky = getStickiesLocked(null, filter, allSticky);
}
……
}
private final List getStickiesLocked(String action, IntentFilter filter,
List cur) {
final ContentResolver resolver = mContext.getContentResolver();
final ArrayList<Intent> list = mStickyBroadcasts.get(action);
if (list == null) {
return cur;
}
int N = list.size();
for (int i=0; i<N; i++) {
Intent intent = list.get(i);
if (filter.match(resolver, intent, true, TAG) >= 0) {
if (cur == null) {
cur = new ArrayList<Intent>();
}
cur.add(intent);
}
}
return cur;
}
从这里可以看出调用 Intent android.content.ContextWrapper.registerReceiver(BroadcastReceiver receiver, IntentFilter filter) 返回的intent是从mStickyBroadcasts里得到的。那么mStickyBroadcasts里的内容从何而来?
接着看:
private final int broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle map, String requiredPermission,
boolean ordered, boolean sticky, int callingPid, int callingUid) {
……
// 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 (requiredPermission != null) {
Slog.w(TAG, "Can't broadcast sticky intent " + intent
+ " and enforce permission " + requiredPermission);
return BROADCAST_STICKY_CANT_HAVE_PERMISSION;
}
if (intent.getComponent() != null) {
throw new SecurityException(
"Sticky broadcasts can't target a specific component");
}
ArrayList<Intent> list = mStickyBroadcasts.get(intent.getAction());
if (list == null) {
list = new ArrayList<Intent>();
mStickyBroadcasts.put(intent.getAction(), list);
}
int N = list.size();
int i;
for (i=0; i<N; i++) {
if (intent.filterEquals(list.get(i))) {
// This sticky already exists, replace it.
list.set(i, new Intent(intent));
break;
}
}
if (i >= N) {
list.add(new Intent(intent));
}
}
……
}
从这里,我们可以知道mStickyBroadcasts的内容是系统处理sendStickyBroadcast时添加的,现在很清楚了吧!所以如果你调用Intent android.content.ContextWrapper.registerReceiver的时机点在调用sendStickyBroadcast后,你就可以直接得到该intent,从而可以快速的得到intent的数据,继续你的处理。
注意事项,调用sendStickyBroadcast,需要在app的AndroidManifest.xml里添加
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
第四, 接收sendOrderedBroadcast发出的broadcast的receivers是如何排序的?
首先,在app安装时,PackageManager会根据intentFilter给activity、service和receiver建立列表,具体代码位于intentResolver.java文件,调用顺序如下:
addFilter 》register_mime_types
其次,broadcast发送过程中,有以下代码:
private final int broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle map, String requiredPermission,
boolean ordered, boolean sticky, int callingPid, int callingUid) {
……
// Figure out who all will receive this broadcast.
List receivers = null;
List<BroadcastFilter> registeredReceivers = null;
try {
if (intent.getComponent() != null) {
// Broadcast is going to one specific receiver class...
ActivityInfo ai = ActivityThread.getPackageManager().
getReceiverInfo(intent.getComponent(), STOCK_PM_FLAGS);
if (ai != null) {
receivers = new ArrayList();
ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
receivers.add(ri);
}
} else {
// Need to resolve the intent to interested receivers...
if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
== 0) {
receivers =
ActivityThread.getPackageManager().queryIntentReceivers(
intent, resolvedType, STOCK_PM_FLAGS);
}
registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false);
}
} catch (RemoteException ex) {
……
}
让我们看一下函数queryIntentReceivers,其核心处理在intentResolver.java文件
public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly) {
……
sortResults(finalList);
……
}
protected void sortResults(List<R> results) {
Collections.sort(results, mResolvePrioritySorter);
}
// Sorts a List of IntentFilter objects into descending priority order.
private static final Comparator mResolvePrioritySorter = new Comparator() {
public int compare(Object o1, Object o2) {
float q1 = ((IntentFilter)o1).getPriority();
float q2 = ((IntentFilter)o2).getPriority();
return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0);
}
};
从这里receiver的优先级取决于intentFilter的属性Priority,它可以在AndroidManifest.xml里指明,例如,
<receiver android:name=".MyReceiver">
<intent-filter android:priority="100">
<action android:name="com.david.broadcast001.MY_ACTION" />
</intent-filter>
</receiver>