增长 addDataScheme(

 

 

若有错漏请不吝拍砖指正,转载请注明出处,很是感谢java

 


 

有一个问题,在网上被频繁的问到,就是为何自定义的Receiver老是没法接收到SD卡插拔的事件。android

而此问题大部分状况下能够经过增长一句代码解决: filter.addDataScheme("file");  // filter是IntentFilter对象app

 

那么为何增长这句代码就能够解决了呢?这个问题尽管有人问到,可是却没有太好的回答。ide

多是由于对于精通IntentFilter策略的高手们来讲,这根本算不上问题,是一个再明显不过的事实而已。函数

而对于不太了解IntentFilter策略的咱们初学者来讲,这个问题又暂时有点太难以理解吧。学习

 

所以,本文试着经过对android的事件过滤策略进行介绍和分析,结合示例程序进行验证,测试

来解答此问题,并浅显的介绍android事件过滤策略。插件

 


1. 编写示例程序,建立一个自定义的BroadcastReceiver

首先咱们建立一个android工程起名为SdCardTester,做为示例程序。日志

为了方便在后续步骤中模拟SD卡插拔,建议将目标平台设定为2.3版本,使用2.3版本的模拟器。component

此外务必注意,运行此示例程序的AVD模拟器须要增长SD卡功能支持。

 

而后为SdCardTester类增长一个BroadcastReceiver类型的成员变量 mReceiver。

在onCreate中,使用匿名类的技巧,为 mReceiver 赋值一个BroadcastReceiver子类实例。

 

[java]  view plain copy
 
 
  1.      mReceiver = new BroadcastReceiver() {  
  2. @Override  
  3. public void onReceive(Context context, Intent intent) {  
  4.         Log.i("myLoger"," Receive SDCard Mount/UnMount!");  
  5. }  
  6. ;  

 

注意代码中重写的onRecevie函数里只有一句代码,用于记录日志。以证实咱们确实收到了事件。

 

而后建立一个IntentFilter,用于过滤SD卡插拔事件。

最后把咱们自定义的Receiver和编写好的IntentFilter注册到系统中

 

[java]  view plain copy
 
 
  1. IntentFilter filter = new IntentFilter();  
  2. filter.addAction(Intent.ACTION_MEDIA_MOUNTED);  
  3. filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);  
  4. registerReceiver(mReceiver, filter);  

 

 

最后的最后不要忘了在onDestory中注销咱们的自定义Receiver,

至此完成了示例程序的代码编写,SdCardTester的完整代码以下:

 

[java]  view plain copy
 
 
  1. package com.silenceburn;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.BroadcastReceiver;  
  5. import android.content.Context;  
  6. import android.content.Intent;  
  7. import android.content.IntentFilter;  
  8. import android.os.Bundle;  
  9. import android.util.Log;  
  10.   
  11. public class SdCardTester extends Activity {  
  12.       
  13.     BroadcastReceiver mReceiver;  
  14.       
  15.     /** Called when the activity is first created. */  
  16.     @Override  
  17.     public void onCreate(Bundle savedInstanceState) {  
  18.         super.onCreate(savedInstanceState);  
  19.         setContentView(R.layout.main);  
  20.           
  21.         mReceiver = new BroadcastReceiver() {  
  22.             @Override  
  23.             public void onReceive(Context context, Intent intent) {  
  24.                     Log.i("myLoger"," Receive SDCard Mount/UnMount!");  
  25.             }  
  26.         };  
  27.           
  28.         IntentFilter filter = new IntentFilter();  
  29.         filter.addAction(Intent.ACTION_MEDIA_MOUNTED);  
  30.         filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);  
  31.         registerReceiver(mReceiver, filter);  
  32.          
  33.     }  
  34.       
  35.     @Override  
  36.     protected void onDestroy() {  
  37.         // TODO Auto-generated method stub  
  38.         super.onDestroy();  
  39.         unregisterReceiver(mReceiver);  
  40.     }  
  41. }  

 

 


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 plain copy
 
 
  1. package com.silenceburn;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.BroadcastReceiver;  
  5. import android.content.Context;  
  6. import android.content.Intent;  
  7. import android.content.IntentFilter;  
  8. import android.os.Bundle;  
  9. import android.util.Log;  
  10.   
  11. public class SdCardTester extends Activity {  
  12.       
  13.     BroadcastReceiver mReceiver;  
  14.       
  15.     /** Called when the activity is first created. */  
  16.     @Override  
  17.     public void onCreate(Bundle savedInstanceState) {  
  18.         super.onCreate(savedInstanceState);  
  19.         setContentView(R.layout.main);  
  20.         Log.i("myLoger"," onCreate ......");  
  21.           
  22.         mReceiver = new BroadcastReceiver() {  
  23.             @Override  
  24.             public void onReceive(Context context, Intent intent) {  
  25.                     Log.i("myLoger"," Receive SDCard Mount/UnMount!");  
  26.             }  
  27.         };  
  28.           
  29.         IntentFilter filter = new IntentFilter();  
  30.         filter.addAction(Intent.ACTION_MEDIA_MOUNTED);  
  31.         filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);  
  32.           
  33.         filter.addDataScheme("file");  
  34.           
  35.         registerReceiver(mReceiver, filter);  
  36.          
  37.     }  
  38.       
  39.     @Override  
  40.     protected void onDestroy() {  
  41.         // TODO Auto-generated method stub  
  42.         super.onDestroy();  
  43.         unregisterReceiver(mReceiver);  
  44.     }  
  45. }  

 

 

修改完成再次运行程序,不要用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 plain copy
 
 
  1. package com.silenceburn;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.BroadcastReceiver;  
  5. import android.content.Context;  
  6. import android.content.Intent;  
  7. import android.content.IntentFilter;  
  8. import android.os.Bundle;  
  9. import android.util.Log;  
  10.   
  11. public class SdCardTester extends Activity {  
  12.       
  13.     BroadcastReceiver mReceiver;  
  14.       
  15.     /** Called when the activity is first created. */  
  16.     @Override  
  17.     public void onCreate(Bundle savedInstanceState) {  
  18.         super.onCreate(savedInstanceState);  
  19.         setContentView(R.layout.main);  
  20.         Log.i("myLoger"," onCreate ......");  
  21.           
  22.         mReceiver = new BroadcastReceiver() {  
  23.             @Override  
  24.             public void onReceive(Context context, Intent intent) {  
  25.                   
  26.                 Log.i("myLoger", "Component: " + intent.getComponent());  
  27.                   
  28.                 Log.i("myLoger", "Aciton: " +  intent.getAction());  
  29.                 Log.i("myLoger", "Categories: " +  intent.getCategories());  
  30.   
  31.                 Log.i("myLoger", "Data: " + intent.getData());  
  32.                 Log.i("myLoger", "DataType: " + intent.getType());  
  33.                 Log.i("myLoger", "DataSchema: " + intent.getScheme());  
  34.                   
  35.                 Log.i("myLoger"," Receive SDCard Mount/UnMount!");  
  36.             }  
  37.         };  
  38.           
  39.         IntentFilter filter = new IntentFilter();  
  40.         filter.addAction(Intent.ACTION_MEDIA_MOUNTED);  
  41.         filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);  
  42.           
  43.         filter.addDataScheme("file");  
  44.           
  45.         registerReceiver(mReceiver, filter);  
  46.          
  47.     }  
  48.       
  49.     @Override  
  50.     protected void onDestroy() {  
  51.         // TODO Auto-generated method stub  
  52.         super.onDestroy();  
  53.         unregisterReceiver(mReceiver);  
  54.     }  
  55. }  

 

 

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 plain copy
 
 
  1. filter.addDataPath("mnt/sdcard", PatternMatcher.PATTERN_LITERAL);  

 

依然能够匹配成功,收到SD卡插拔事件。由于这样就组成了一个URI的彻底匹配。

 

咱们能够尝试把给 filter 增长 datatype 属性,

 

[java]  view plain copy
 
 
  1. try {  
  2.     filter.addDataType("text/*");  
  3. } catch (MalformedMimeTypeException e) {  
  4.     // TODO Auto-generated catch block  
  5.     e.printStackTrace();  
  6. }  

 

这样就没法匹配成功了。由于SD卡插拔事件的datatype是null,

而咱们定义的filter的datatype是MIME"text/*" 。

 


总结

至此文初的问题解析完毕。老生常谈,事实上android平台的intentFilter处理机制远远复杂于本文所述范围。

特别是本文对action和category两种要素的讨论很是少几乎没有,实际上这两种要素的处理也是比较复杂的。

 

本文只是冰山一角的讲述了了一些基本原理和基本准则。

IntentFilter机制做为android平台的重要基础知识之一,咱们你们要一块儿继续努力学习,和你们共勉 :)

相关文章
相关标签/搜索