3.事件(Intent)的分类:显式 和 隐式
那么,为什么加上
为了解决这个问题,我们要先学习Intent的分类。Intent分为两大类,显式和隐式。
显式事件,就是指通过 component Name 属性,明确指定了目标组件的事件。
比如我们新建一个Intent,指名道姓的说,此事件用于启动名为"com.silenceburn.XXXX”的Activity,那么这就是一个显式事件。
隐式事件,就是指没有 component Name 属性,没有明确指定目标组件的事件。
比如系统向所有监控通话情况的程序发送的“来电话了!”的事件,由于系统不确定谁会处理这个事件,
因此系统不会明确指定目标组件,也就是说没有目标组件,那么这就是个隐式的事件。
此处只是简介显式和隐式事件,更精确详细的描述请查阅SDK文档,
我们只需要记住一点,两种事件的最大区别是
4.事件过滤策略 和 IntentFilter
系统在传送显式事件时非常方便,因为如果把Intent比作一封信,那么component Name就是一个详细的收件人地址,
系统可以精确的把显式事件送达目标组件。
而传送隐式事件时,就比较麻烦了。因为这封信的信封上,没有写收信地址!
那怎么办呢?系统做了一个艰难的决定,就是把信拆开看看。通过信件内容里面的线索,去寻找合适的收件人。
比如信中的线索描述到:“收信人是男性,快30岁了,未婚,喜欢玩游戏”,那么系统就在小区里面去找这样的人。
非常值得庆幸的事情是,这个小区的人素质非常高,每户人家都写了点自我介绍在门口,
比如张三写道:“我是男性,90后,未婚,喜欢玩游戏”,李四写道:“我是女性,快30岁了,未婚,喜欢逛街”等等等等。
有了每户人家的自我介绍,系统就能很快的定位真正的收件人了!
上面是一个类比的例子,不过android系统处理隐式事件的策略,基本上就是上述这种模式了。
首先系统会通过观察Intent的内容(打开信件看内容),取得匹配线索,系统所需的线索是如下三种 :
其次,系统中每个组件,如果想收取隐式事件,则必须声明自己的IntentFilter(自我介绍,我对什么样的信件感兴趣)。
至于怎么写IntentFilter,已经相当明了了,那就是应该是这样写:
"我是组件XXXX,我想要接收这样的隐式事件:它的ACTION必须是 XXX,它的 category 必须是 YYYY,它包含的data必须是ZZZZ
如果组件不声明IntentFilter,那么所有的隐式事件都不会发送给该组件。(注意,这并不影响向该组件发送显式事件。)
对于系统中发生的每个隐式事件,系统都会尝试将
以找到合适的接收者。
上述是android系统的事件过滤策略的简单原理,实际情况远比这要复杂,考虑本文的目的,此处不再展开,SDK文档中都有详尽描述。
5. 分析SD卡插拔事件
由上节可知,对于显式事件,系统可以精确送达。对于隐式事件,系统分析事件的action,
并和各个组件声明的IntentFilter进行匹配,找出匹配的组件进行送达。
因此SD卡插拔事件能否被我们自定义的Recevier收到就取决于如下子问题了:
1. SD卡插拔事件是显式事件,还是隐式事件
2.SD卡插拔事件的action,
3. 我们自定义的Receiver组件的IntentFilter是如何声明的
为了解决上述3个问题,我们修改一下代码,将SD卡插拔事件的内容打印到logcat中进行观察。
修改后的代码如下:(注意这里我们要添加好 filter.addDataScheme("file"); 以确保事件可以被接收到)
- package
com.silenceburn; -
- import
android.app.Activity; - import
android.content.BroadcastReceiver; - import
android.content.Context; - import
android.content.Intent; - import
android.content.IntentFilter; - import
android.os.Bundle; - import
android.util.Log; -
- public
class SdCardTester extends Activity { -
-
BroadcastReceiver mReceiver; -
-
-
@Override -
public void onCreate(Bundle savedInstanceState) { -
super.onCreate(savedInstanceState); -
setContentView(R.layout.main); -
Log.i("myLoger"," onCreate ......"); -
-
mReceiver = new BroadcastReceiver() { -
@Override -
public void onReceive(Context context, Intent intent) { -
-
Log.i("myLoger", "Component: " + intent.getComponent()); -
-
Log.i("myLoger", "Aciton: " + intent.getAction()); -
Log.i("myLoger", "Categories: " + intent.getCategories()); -
-
Log.i("myLoger", "Data: " + intent.getData()); -
Log.i("myLoger", "DataType: " + intent.getType()); -
Log.i("myLoger", "DataSchema: " + intent.getScheme()); -
-
Log.i("myLoger"," Receive SDCard Mount/UnMount!"); -
} -
}; -
-
IntentFilter filter = new IntentFilter(); -
filter.addAction(Intent.ACTION_MEDIA_MOUNTED); -
filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); -
-
filter.addDataScheme("file"); -
-
registerReceiver(mReceiver, filter); -
-
} -
-
@Override -
protected void onDestroy() { -
// TODO Auto-generated method stub -
super.onDestroy(); -
unregisterReceiver(mReceiver); -
} - }
OK,让我们再次运行程序,通过Setting模拟插拔SD卡,观察logCat的输出情况。
下图是挂载SD卡时的日志输出情况:
通过日志输出我们可以得知挂载SD卡事件的
因此能否送达,需要看事件的action,
它的ACTION是 android.intent.action.MEDIA_MOUNTED,
和我们定义的IntentFilter的 filter.addAction(Intent.ACTION_MEDIA_MOUNTED);语句相匹配。
因此action部分是匹配的。
它的Categories是null,而我们定义的
null ==
action,
6. data匹配规则
首先务必认识到,data是一个相对复杂的要素。 data由URI来描述和定位,URI由三部分组成,
scheme://host:port/path
例如我们截获的挂载SD卡事件,它的data的URI是
其中模式部分是 file , 主机:端口部分是空的, path部分是 mnt/sdcard
此外在事件中,还可以设置data的MIME类型,作为事件的datatype属性。
综上所述,data是一个较复杂的要素,因此其匹配规则也格外复杂,
首先明确一个匹配原则,就是对于URI的匹配,只比较filter中声明的部分。
部分匹配原则:只要filter中声明的部分匹配成功,就认为整个URI匹配成功。
举例来说,
和filter定义为
注意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都必须定义并匹配成功,才能匹配成功。
有了规则,有了对SD卡插拔事件的内容的分析,我们就可以按图索骥照章办事了。
7.SD卡插拔事件的匹配
首先如第6节所述,SD卡插拔是一个隐式事件,而且 action 和 category 部分和我们的 filter均可以匹配成功。
其data部分的URI为
我们的filter中没有使用 addtype 方法
data的URI为file:///mnt/sdcard
根据部分匹配规则,data匹配成功。
至此,整个事件匹配成功,至此,我们就明白了文初的问题,为什么必须添加
我们可以尝试把 filter.addDataScheme("file");
- filter.addDataPath("mnt/sdcard",
PatternMatcher.PATTERN_LITERAL);
依然可以匹配成功,收到SD卡插拔事件。因为这样就组成了一个URI的完全匹配。
我们可以尝试把给 filter 增加 datatype 属性,
- try
{ -
filter.addDataType("text/*"); - }
catch (MalformedMimeTypeExcepti on e) { -
// TODO Auto-generated catch block -
e.printStackTrace(); - }
这样就无法匹配成功了。因为SD卡插拔事件的datatype是null,
而我们定义的filter的datatype是MIME"text/*" 。