一、註冊方式
作爲Android四大組件之一的廣播有兩種註冊方式:靜態註冊和動態註冊。在註冊之前,我們應該有自己的BroadcastReceiver,即廣播接收器,這樣我們才能接收到廣播,進行事務處理。
public class MyBroadCastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context,Intent intent) {
// 在此處處理事務
}
}
1、靜態註冊
指的是在AndroidManifest.xml中用<receiver>標籤進行註冊,並在標籤內用<intent-filter>標籤設置過濾器,例如<action>、<data>等。
<receiverandroid:name=".MyBroadCastReceiver">
<intent-filter>
<actionandroid:name="android.intent.action.PACKAGE_REMOVED"/>
<dataandroid:scheme="package"/>
</intent-filter>
</receiver>
2、動態註冊
指的是在代碼中進行註冊,例如:
IntentFilterfilter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme(“package”);
當不再需要廣播接收器時,要記得註銷unregisterReceiver(),否則可能引起內存泄露。
3、兩種註冊方式的區別和優缺點:
動態註冊:生命週期與程序的生命週期一致,程序關閉後將接收不到廣播。
靜態註冊:即使程序關閉也能接收到廣播。
4、兩種註冊方式的優缺點:
動態註冊:
(1) 優點:優先級高於靜態註冊,優先收到廣播。
(2) 缺點:註冊廣播的Activity關閉,廣播也將無法接收。
靜態註冊:
(1) 優點:無需擔心廣播接收器是否被關閉,應用若未運行,將會被喚醒並接收廣播。
(2) 缺點:優先級較低。
二、接收器匹配策略
1、Broadcast發送主流程
Broadcast發送的主流程爲先匹配action,然後匹配data,最後匹配category。
這裏不再介紹廣播接收器的註冊源碼流程,也不介紹廣播的發送源碼流程,請參看老羅的文章:Android應用程序註冊廣播接收器(registerReceiver)的過程分析和Android應用程序發送廣播(sendBroadcast)的過程分析,這裏只介紹廣播的匹配策略。代碼在android/content/IntentFilter.java中,match()方法的大致流程如下:
public final int match(String action, String type, String scheme,
Uri data, Set<String>categories, String logTag) {
if (action != null && !matchAction(action)) {//沒匹配action
returnNO_MATCH_ACTION; // -3:due todifferent actions
}
int dataMatch = matchData(type, scheme, data);
if (dataMatch < 0) {//沒匹配type或data
return dataMatch;// -1:due to different MINE types, -2:due to different data URIs
}
String categoryMismatch = matchCategories(categories);
if (categoryMismatch != null) {//沒匹配category
return NO_MATCH_CATEGORY;//-4:because it required one or more categories
}
return dataMatch;
}
該方法用於匹配intent數據,共有4項:action、type、data和category,任何一項匹配不成功都會失敗,即BroadcastReceiver收不到廣播。
1、匹配子流程
(1) match action
這個過程比較簡單,只是檢查一下action是否爲null,以及ArrayList<String>列表mActions是否包含action。
public final boolean matchAction(String action) {
return hasAction(action);
}
public final boolean hasAction(String action) {
return action != null && mActions.contains(action);
}
(2) match data和type
若action匹配,則檢查data。這個流程比較複雜,出口也比較多。
public final int matchData(String type, String scheme, Uri data) {
final ArrayList<String> types = mDataTypes;
final ArrayList<String> schemes = mDataSchemes;
int match = MATCH_CATEGORY_EMPTY;
//若IntentFilter的mDataTypes、mDataSchemes爲空,且intent的type、data也爲空,則匹配
if (types == null && schemes == null) {
return ((type == null&& data == null)
? (MATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMAL): NO_MATCH_DATA);
}
//若IntentFilter的mDataSchemes不爲空,則檢查intent的scheme
if (schemes != null) {
if (schemes.contains(scheme != null? scheme : "")) {
match =MATCH_CATEGORY_SCHEME;
} else {
return NO_MATCH_DATA;
}
final ArrayList<PatternMatcher> schemeSpecificParts = mDataSchemeSpecificParts;
if (schemeSpecificParts != null && data != null) {
// Uri格式:[scheme:]scheme-specific-part[#fragment],即scheme、scheme-specific-part、//fragment三部分
//進一步劃分是[scheme:][//authority][path][?query][#fragment]
//再進一步劃分[scheme:][//host:port][path][?query][#fragment]
match = hasDataSchemeSpecificPart(data.getSchemeSpecificPart())
? MATCH_CATEGORY_SCHEME_SPECIFIC_PART :NO_MATCH_DATA;
}
if (match !=MATCH_CATEGORY_SCHEME_SPECIFIC_PART) {
// If there isn't anymatching ssp, we need to match an authority.
final ArrayList<AuthorityEntry> authorities = mDataAuthorities;
if (authorities != null) {//匹配Authority,即匹配host:port部分
int authMatch =matchDataAuthority(data);
if (authMatch >= 0){//匹配Authority,則繼續檢查path
finalArrayList<PatternMatcher> paths = mDataPaths;
if (paths == null) {
match = authMatch;
} else if(hasDataPath(data.getPath())) {
match =MATCH_CATEGORY_PATH;
} else {
return NO_MATCH_DATA;
}
} else {
return NO_MATCH_DATA;// 不匹配Authority
}
}
}
// If neither an ssp nor an authority matched, we're done.
if (match == NO_MATCH_DATA) {
return NO_MATCH_DATA;
}
} else {
// Special case: match either an Intent with no data URI,
// or with a scheme: URI. This is to give aconvenience for
// the common case where you want to deal with data in a
// content provider, which is done by type, and we don't want
// to force everyone to say they handle content: or file: URIs.
// 若scheme不是""或"content"或"file" 就不匹配;否則繼續後續判斷
if (scheme != null && !"".equals(scheme)
&& !"content".equals(scheme)
&& !"file".equals(scheme)){
return NO_MATCH_DATA;
}
}
if (types != null) {//filter中適配了types
if (findMimeType(type)) {//檢查intent的type是否滿足mDataTypes的要求
match =MATCH_CATEGORY_TYPE;
} else {
return NO_MATCH_TYPE;
}
} else {
// If no MIME types are specified,then we will only match against
// an Intentthat does not have a MIME type.
if (type != null) {//filter要匹配types,但intent沒攜帶type,則不匹配
return NO_MATCH_TYPE;
}
}
return match + MATCH_ADJUSTMENT_NORMAL;
}
(3) match category
這個過程較爲簡單,主要檢查了3個場景:
1) 若intent的category爲空,則匹配成功;
2) 若filter的category爲空,且intent中沒有category,則匹配成功;
3) 若filter的category不爲空,同時intent中也有category,並且都包含在filter的category中,即intent中的category都包含在filter的category中,則匹配成功;
/**
* Match this filter againstan Intent's categories. Each category in
* the Intent must bespecified by the filter; if any are not in the
* filter, the match fails.
*
* @paramcategories The categories included in the intent, as returned by
* Intent.getCategories()*
* @return If all categories match(success), null; else the name of the
* first category that didn't match.
*/public final String matchCategories(Set<String>categories) {
if (categories == null) {//intent的categories爲空,則匹配成功
return null;
}
Iterator<String> it = categories.iterator();
//filter中的Categories爲空,且intent中沒有Categories,則匹配成功
if (mCategories == null) {
return it.hasNext() ?it.next() : null;
}
while (it.hasNext()) {
//filter有Categories, 且包含所有的intent裏Categories纔算成功,否則失敗
final String category =it.next();
if (!mCategories.contains(category)){
return category;
}
}
return null;
}
但是,從代碼中看,卻存在第4中場景,即若filter的mCategories不爲空,且intent中的categories也不爲空,但大小卻爲0,也能夠匹配成功。
我們先從源碼android/content/Intent.java中addCategory()方法查起:
public IntentaddCategory(String category) {
if (mCategories == null) {
mCategories = newArraySet<String>();
}
mCategories.add(category.intern());
return this;
}
過程很簡單,把category添加到mCategories中,即執行了addCategory()方法之後,Intent中的mCategories就不爲空了,且大小>0。
若想讓intent中的mCategories不爲空,且大小變爲0,可以試想刪除mCategories中的數據會發生什麼情況,即執行android/content/Intent.java中removeCategory()方法:
public voidremoveCategory(String category) {
if (mCategories != null) {
mCategories.remove(category);
if (mCategories.size() == 0) {
mCategories = null;
}
}
這次就明白了,當mCategories的大小變爲0後,它就被賦值爲空了,因此intent中的categories要麼爲空,要麼它的大小一定大於0。
/**
* Return the set of all categoriesin the intent. If there are no categories,
* returnsNULL.
*/public Set<String>getCategories() {
return mCategories;
}
所以,不存在第4中場景。