增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
2012-09-18 16:42
274 查看
http://blog.csdn.net/silenceburn/article/details/6083375
有一个问题,在网上被频繁的问到,就是为什么自定义的Receiver总是无法接收到SD卡插拔的事件。
而此问题大部分情况下可以通过增加一句代码解决: filter.addDataScheme("file"); // filter是IntentFilter对象
那么为什么增加这句代码就可以解决了呢?这个问题尽管有人问到,但是却没有太好的回答。
可能是因为对于精通IntentFilter策略的高手们来说,这根本算不上问题,是一个再明显不过的事实而已。
而对于不太了解IntentFilter策略的我们初学者来说,这个问题又暂时有点太难以理解吧。
因此,本文试着通过对android的事件过滤策略进行介绍和分析,结合示例程序进行验证,
来解答此问题,并浅显的介绍android事件过滤策略。
为了方便在后续步骤中模拟SD卡插拔,建议将目标平台设定为2.3版本,使用2.3版本的模拟器。
此外务必注意,运行此示例程序的AVD模拟器需要增加SD卡功能支持。
然后为SdCardTester类增加一个BroadcastReceiver类型的成员变量 mReceiver。
在onCreate中,使用匿名类的技巧,为 mReceiver 赋值一个BroadcastReceiver子类实例。
[java]
view plaincopyprint?
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("myLoger"," Receive SDCard Mount/UnMount!");
}
;
最后的最后不要忘了在onDestory中注销我们的自定义Receiver,
至此完成了示例程序的代码编写,SdCardTester的完整代码如下:
[java]
view plaincopyprint?
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;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("myLoger"," Receive SDCard Mount/UnMount!");
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
registerReceiver(mReceiver, filter);
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
unregisterReceiver(mReceiver);
}
}
修改完成再次运行程序,不要用BACK退出,按HOME按钮返回主屏幕,进入Setting模拟SD卡插拔,
然后观察logCat日志监控窗口,神奇的事情发生了,我们可以接收到事件了!如下图所示:
为了解决这个问题,我们要先学习Intent的分类。Intent分为两大类,显式和隐式。
显式事件,就是指通过 component Name 属性,明确指定了目标组件的事件。
比如我们新建一个Intent,指名道姓的说,此事件用于启动名为"com.silenceburn.XXXX”的Activity,那么这就是一个显式事件。
隐式事件,就是指没有 component Name 属性,没有明确指定目标组件的事件。
比如系统向所有监控通话情况的程序发送的“来电话了!”的事件,由于系统不确定谁会处理这个事件,
因此系统不会明确指定目标组件,也就是说没有目标组件,那么这就是个隐式的事件。
此处只是简介显式和隐式事件,更精确详细的描述请查阅SDK文档,
我们只需要记住一点,两种事件的最大区别是 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文档中都有详尽描述。
并和各个组件声明的IntentFilter进行匹配,找出匹配的组件进行送达。
因此SD卡插拔事件能否被我们自定义的Recevier收到就取决于如下子问题了:
1. SD卡插拔事件是显式事件,还是隐式事件
2. SD卡插拔事件的action, data , category 的内容是什么
3. 我们自定义的Receiver组件的IntentFilter是如何声明的
为了解决上述3个问题,我们修改一下代码,将SD卡插拔事件的内容打印到logcat中进行观察。
修改后的代码如下:(注意这里我们要添加好 filter.addDataScheme("file"); 以确保事件可以被接收到)
[java]
view plaincopyprint?
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;
/** Called when the activity is first created. */
@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);
}
}
依然可以匹配成功,收到SD卡插拔事件。因为这样就组成了一个URI的完全匹配。
我们可以尝试把给 filter 增加 datatype 属性,
[java]
view plaincopyprint?
try {
filter.addDataType("text/*");
} catch (MalformedMimeTypeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
filter.addDataType("text/*");
} catch (MalformedMimeTypeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
这样就无法匹配成功了。因为SD卡插拔事件的datatype是null,
而我们定义的filter的datatype是MIME"text/*" 。
特别是本文对action和category两种要素的讨论非常少几乎没有,实际上这两种要素的处理也是比较复杂的。
本文只是冰山一角的讲述了了一些基本原理和基本准则。
IntentFilter机制作为android平台的重要基础知识之一,我们大家要一起继续努力学习,和大家共勉 :)
有一个问题,在网上被频繁的问到,就是为什么自定义的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 plaincopyprint?
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("myLoger"," Receive SDCard Mount/UnMount!");
}
;
[java] view plaincopyprint? IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_MEDIA_MOUNTED); filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); registerReceiver(mReceiver, filter); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_MEDIA_MOUNTED); filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); registerReceiver(mReceiver, filter);
最后的最后不要忘了在onDestory中注销我们的自定义Receiver,
至此完成了示例程序的代码编写,SdCardTester的完整代码如下:
[java]
view plaincopyprint?
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;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("myLoger"," Receive SDCard Mount/UnMount!");
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
registerReceiver(mReceiver, filter);
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
unregisterReceiver(mReceiver);
}
}
[java] view plaincopyprint? 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; /** Called when the activity is first created. */ @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"," 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); } } 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; /** Called when the activity is first created. */ @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"," 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); } }
修改完成再次运行程序,不要用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 plaincopyprint?
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;
/** Called when the activity is first created. */
@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);
}
}
[c-sharp] view plaincopyprint? filter.addDataPath("mnt/sdcard", PatternMatcher.PATTERN_LITERAL); filter.addDataPath("mnt/sdcard", PatternMatcher.PATTERN_LITERAL);
依然可以匹配成功,收到SD卡插拔事件。因为这样就组成了一个URI的完全匹配。
我们可以尝试把给 filter 增加 datatype 属性,
[java]
view plaincopyprint?
try {
filter.addDataType("text/*");
} catch (MalformedMimeTypeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
filter.addDataType("text/*");
} catch (MalformedMimeTypeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
这样就无法匹配成功了。因为SD卡插拔事件的datatype是null,
而我们定义的filter的datatype是MIME"text/*" 。
总结
至此文初的问题解析完毕。老生常谈,事实上android平台的intentFilter处理机制远远复杂于本文所述范围。特别是本文对action和category两种要素的讨论非常少几乎没有,实际上这两种要素的处理也是比较复杂的。
本文只是冰山一角的讲述了了一些基本原理和基本准则。
IntentFilter机制作为android平台的重要基础知识之一,我们大家要一起继续努力学习,和大家共勉 :)
相关文章推荐
- android: 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 【BroadcastReceiver】增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- Android的BroadcastReciver,增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策
- 增加addDataScheme("file") 才能收到SD卡插拔事件的原因分析-- 浅析 ...
- 增加 addDataScheme("file") 才能收到SD卡插拔事件的原因分析
- 通过addDataScheme("file") 浅析android事件过滤策略
- 通过addDataScheme("file") 浅析android事件过滤策略