增加 addDataScheme(file) 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略...
http://blog.csdn.net/silenceburn/article/details/6083375
===========================================
?
?
如有錯漏請不吝拍磚指正,轉載請注明出處,非常感謝
?
?
有一個問題,在網上被頻繁的問到,就是為什么自定義的Receiver總是無法接收到SD卡插拔的事件。
而此問題大部分情況下可以通過增加一句代碼解決: filter.addDataScheme("file");? // filter是IntentFilter對象
?
那么為什么增加這句代碼就可以解決了呢?這個問題盡管有人問到,但是卻沒有太好的回答。
可能是因為對于精通IntentFilter策略的高手們來說,這根本算不上問題,是一個再明顯不過的事實而已。
而對于不太了解IntentFilter策略的我們初學者來說,這個問題又暫時有點太難以理解吧。
?
因此,本文試著通過對android的事件過濾策略進行介紹和分析,結合示例程序進行驗證,
來解答此問題,并淺顯的介紹android事件過濾策略。
?
1. 編寫示例程序,創建一個自定義的BroadcastReceiver
首先我們創建一個android工程起名為SdCardTester,作為示例程序。
為了方便在后續步驟中模擬SD卡插拔,建議將目標平臺設定為2.3版本,使用2.3版本的模擬器。
此外務必注意,運行此示例程序的AVD模擬器需要增加SD卡功能支持。
?
然后為SdCardTester類增加一個BroadcastReceiver類型的成員變量 mReceiver。
在onCreate中,使用匿名類的技巧,為 mReceiver 賦值一個BroadcastReceiver子類實例。
?
[java]?view plaincopy?
注意代碼中重寫的onRecevie函數里只有一句代碼,用于記錄日志。以證明我們確實收到了事件。
?
然后創建一個IntentFilter,用于過濾SD卡插拔事件。
最后把我們自定義的Receiver和編寫好的IntentFilter注冊到系統中
?
[java]?view plaincopy?
?
最后的最后不要忘了在onDestory中注銷我們的自定義Receiver,
至此完成了示例程序的代碼編寫,SdCardTester的完整代碼如下:
?
[java]?view plaincopy?
?
2. 測試示例程序
運行SdCardTester,等看到hello World 字樣,說明onCreate完成,也就意味著我們自定義的Receiver也已經啟動了。
?
之后,不要用BACK退出,按HOME按鈕返回主屏幕,(也就是要保證我們的自定義Receiver仍然在運行)
進入手機設定,選擇Storage (如果系統語言選擇為中文了是“存儲”),
選擇 Unmount SD card / mount SD card 選項,就可以模擬SD卡插拔的動作了。如下圖所示:
?
如果我們的自定義Receiver收到了相關事件,按照代碼實現,就會使用 TAG "myLoger" 輸出日志到logCat。
因此我們通過觀察logCat輸出確定是否有相關事件發生,在cmd窗口中運行 "?adb logcat -s?myLoger:i?" ,即可持續監控日志輸出。
或者使用Eclipse ADT插件增加的DDMS視圖,在logCat 的View中增加一個filter,如下圖所示:
?
注意,按照我們當前的代碼實現,無論我們如何插拔SD卡,都不會看到日志窗口監控到事件發生。
?
OK,我們現在增加那行神奇的代碼:“???filter.addDataScheme("file");? ?”,
并在onCreate中增加一個日志輸出監控程序進入。修改之后的代碼如下:
?
[java]?view plaincopy?
?
修改完成再次運行程序,不要用BACK退出,按HOME按鈕返回主屏幕,進入Setting模擬SD卡插拔,
然后觀察logCat日志監控窗口,神奇的事情發生了,我們可以接收到事件了!如下圖所示:
?
3.事件(Intent)的分類:顯式 和 隱式
那么,為什么加上?filter.addDataScheme("file");?就可以了呢?
為了解決這個問題,我們要先學習Intent的分類。Intent分為兩大類,顯式和隱式。
?
顯式事件,就是指通過 component Name 屬性,明確指定了目標組件的事件。
比如我們新建一個Intent,指名道姓的說,此事件用于啟動名為"com.silenceburn.XXXX”的Activity,那么這就是一個顯式事件。
?
隱式事件,就是指沒有 component Name 屬性,沒有明確指定目標組件的事件。
比如系統向所有監控通話情況的程序發送的“來電話了!”的事件,由于系統不確定誰會處理這個事件,
因此系統不會明確指定目標組件,也就是說沒有目標組件,那么這就是個隱式的事件。
?
此處只是簡介顯式和隱式事件,更精確詳細的描述請查閱SDK文檔,
我們只需要記住一點,兩種事件的最大區別是?component Name 屬性是否為空。
?
4.事件過濾策略 和 IntentFilter
系統在傳送顯式事件時非常方便,因為如果把Intent比作一封信,那么component Name就是一個詳細的收件人地址,
系統可以精確的把顯式事件送達目標組件。
?
而傳送隱式事件時,就比較麻煩了。因為這封信的信封上,沒有寫收信地址!
那怎么辦呢?系統做了一個艱難的決定,就是把信拆開看看。通過信件內容里面的線索,去尋找合適的收件人。
比如信中的線索描述到:“收信人是男性,快30歲了,未婚,喜歡玩游戲”,那么系統就在小區里面去找這樣的人。
?
非常值得慶幸的事情是,這個小區的人素質非常高,每戶人家都寫了點自我介紹在門口,
比如張三寫道:“我是男性,90后,未婚,喜歡玩游戲”,李四寫道:“我是女性,快30歲了,未婚,喜歡逛街”等等等等。
有了每戶人家的自我介紹,系統就能很快的定位真正的收件人了!
?
上面是一個類比的例子,不過android系統處理隱式事件的策略,基本上就是上述這種模式了。
?
首先系統會通過觀察Intent的內容(打開信件看內容),取得匹配線索,系統所需的線索是如下三種 :
?action
?data (both URI and data type)
?category
?
其次,系統中每個組件,如果想收取隱式事件,則必須聲明自己的IntentFilter(自我介紹,我對什么樣的信件感興趣)。
至于怎么寫IntentFilter,已經相當明了了,那就是應該是這樣寫:
"我是組件XXXX,我想要接收這樣的隱式事件:它的ACTION必須是 XXX,它的 category 必須是 YYYY ,它包含的data必須是ZZZZ?"
如果組件不聲明IntentFilter,那么所有的隱式事件都不會發送給該組件。(注意,這并不影響向該組件發送顯式事件。)
?
對于系統中發生的每個隱式事件,系統都會嘗試將?action,?data?,?category 和系統中各個組件聲明的 IntentFilter 去進行匹配,
以找到合適的接收者。
?
上述是android系統的事件過濾策略的簡單原理,實際情況遠比這要復雜,考慮本文的目的,此處不再展開,SDK文檔中都有詳盡描述。
?
5. 分析SD卡插拔事件
由上節可知,對于顯式事件,系統可以精確送達。對于隱式事件,系統分析事件的 action,?data?,?category?內容,
并和各個組件聲明的IntentFilter進行匹配,找出匹配的組件進行送達。
?
因此SD卡插拔事件能否被我們自定義的Recevier收到就取決于如下子問題了:
1. SD卡插拔事件是顯式事件,還是隱式事件
2. SD卡插拔事件的action,?data?,?category?的內容是什么
3. 我們自定義的Receiver組件的IntentFilter是如何聲明的
?
為了解決上述3個問題,我們修改一下代碼,將SD卡插拔事件的內容打印到logcat中進行觀察。
修改后的代碼如下:(注意這里我們要添加好 filter.addDataScheme("file"); 以確保事件可以被接收到)
?
[java]?view plaincopy?
?
OK,讓我們再次運行程序,通過Setting模擬插拔SD卡,觀察logCat的輸出情況。
下圖是掛載SD卡時的日志輸出情況:
?
通過日志輸出我們可以得知掛載SD卡事件的?Componet 是null?,因此它是一個隱式事件。
因此能否送達,需要看事件的 action,?data?,?category?和 IntentFilter是否匹配。
?
它的ACTION是 android.intent.action.MEDIA_MOUNTED,
和我們定義的IntentFilter的 filter.addAction(Intent.ACTION_MEDIA_MOUNTED); 語句相匹配。
因此action部分是匹配的。
?
它的Categories是null,而我們定義的?IntentFilter 也沒有使用addCategory方法增加category定義,
null ==? null,因此 categories也是匹配的。
?
action,?data?,?category?中的兩個要素已經匹配,那么能否匹配成功的關鍵,就是看data是否匹配了。
?
6. data匹配規則
首先務必認識到,data是一個相對復雜的要素。 data由URI來描述和定位,URI由三部分組成,
?
scheme://host:port/path????? 模式://主機:端口/路徑
?
例如我們截獲的掛載SD卡事件,它的data的URI是?file:///mnt/sdcard
其中模式部分是 file , 主機:端口部分是空的, path部分是 mnt/sdcard
此外在事件中,還可以設置data的MIME類型,作為事件的datatype屬性。
?
綜上所述,data是一個較復雜的要素,因此其匹配規則也格外復雜,
?
首先明確一個匹配原則,就是對于URI的匹配,只比較filter中聲明的部分。
部分匹配原則:只要filter中聲明的部分匹配成功,就認為整個URI匹配成功。
舉例來說,???????????content://com.silenceburn.SdCardTester:1000/mydata/private/
和filter定義為??content://com.silenceburn.SdCardTester:1000/?????是可以匹配的。
注意filter中并沒有定義path部分,但是依然可以匹配成功,因為filter不聲明的部分不進行比較。
換句話講,任何符合content://com.silenceburn.SdCardTester:1000/的事件,無論path是什么,都可以匹配成功。
?
接下來是真正的data部分的,也就是URI的匹配規則如下:
?
1. 如果data的URI和datatype為空,則 filter 的URI和type也必須為空,才能匹配成功
2. 如果data的URI不為空,但是datatype為空,則 filter 必須定義URI并匹配成功,且type為空,才能匹配成功
3. 如果data的URI為空,但是datatype不為空,則 filter 必須URI為空,定義type并匹配成功,才能匹配成功
4. 如果data的URI和data都不為空,則 filter 的URI和type都必須定義并匹配成功,才能匹配成功。
??? 對于URI部分,有一個特殊處理,就是即使filter沒有定義URI,content和file兩種URI也作為既存的URI存在。
???(舉個例子,對于 content 和 file 兩種模式的data,只要filter定義的datatype可以和事件匹配,就認為匹配成功,
???? filter不需要顯式的增加 content 和 file 兩種模式,這兩種模式內置支持 )
?
有了規則,有了對SD卡插拔事件的內容的分析,我們就可以按圖索驥照章辦事了。
7.SD卡插拔事件的匹配
首先如第6節所述,SD卡插拔是一個隱式事件,而且 action 和 category 部分和我們的 filter 均可以匹配成功。
?
其data部分的URI為?file:///mnt/sdcard?,datatype為 null ,因此應用第6節比較規則中的 2 號規則:
??? 2. 如果data的URI不為空,但是datatype為空,則 filter 必須定義URI并匹配成功,且type為空,才能匹配成功
?
我們的filter中沒有使用 addtype 方法?,因此 filter?的type為空, datatype部分匹配成功。
data的URI為file:///mnt/sdcard?,我們使用 filter.addDataScheme("file"); 語句定義 schema 為 file,
根據部分匹配規則,data匹配成功。
?
至此,整個事件匹配成功,至此,我們就明白了文初的問題,為什么必須添加??filter.addDataScheme("file"); 語句才能收到事件!
?
我們可以嘗試把 filter.addDataScheme("file");?后面增加語句
?
[c-sharp]?view plaincopy?
依然可以匹配成功,收到SD卡插拔事件。因為這樣就組成了一個URI的完全匹配。
?
我們可以嘗試把給 filter 增加 datatype 屬性,
?
[java]?view plaincopy?
這樣就無法匹配成功了。因為SD卡插拔事件的datatype是null,
而我們定義的filter的datatype是MIME"text/*" 。
?
總結
至此文初的問題解析完畢。老生常談,事實上android平臺的intentFilter處理機制遠遠復雜于本文所述范圍。
特別是本文對action和category兩種要素的討論非常少幾乎沒有,實際上這兩種要素的處理也是比較復雜的。
?
本文只是冰山一角的講述了了一些基本原理和基本準則。
IntentFilter機制作為android平臺的重要基礎知識之一,我們大家要一起繼續努力學習,和大家共勉 :)
轉載于:https://www.cnblogs.com/senior-engineer/p/4708342.html
總結
以上是生活随笔為你收集整理的增加 addDataScheme(file) 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: -webkit-gradient web
- 下一篇: Bitset 用法(STL)