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

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

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

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

依然可以匹配成功,收到SD卡插拔事件。因为这样就组成了一个URI的完全匹配。

我们可以尝试把给 filter 增加 datatype 属性,

[java] view plaincopy
  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平台的重要基础知识之一,我们大家要一起继续努力学习,和大家共勉 :)

转载于:https://www.cnblogs.com/senior-engineer/p/4708342.html

增加 addDataScheme(file) 才能收到SD卡插拔事件的原因分析 -- 浅析android事件过滤策略...相关推荐

  1. SD卡插上上网本时显示文件夹变成文件请问怎么才能修复

    电脑硬盘分区.移动硬盘.U盘等在使用过程中有时会出现错误,使分区打不开无法读取分区中的数据,并且提示"文件夹变成文件".SD卡插上上网本时显示文件夹变成文件请问怎么才能修复 文件夹 ...

  2. File存对象--android 的File存储到SD卡();

    方法1:android File存对象--File存储到SD卡(); 1.保存对象到本地或SD卡 需要注意的是,要保存的对象(OAuthV1)一定要实现了Serializable接口. 实现了Seri ...

  3. SD卡插上电脑提示 “使用驱动器中的光盘之前需要将其格式化” 是否要将其格式化的数据恢复

    SD卡插上电脑同时提示 "使用驱动器中的光盘之前需要将其格式化" 是否要将其格式化?故障提示如下图.本文件总结SD卡这个问题的数据恢复方法.这个问题有可能是热插拨.读卡闪掉.SD卡 ...

  4. 解决sd卡插到电脑无法识别问题

    昨天在用开发板的时候,突然系统崩掉了,打算重装一下,可是sd卡插在电脑上一点反应也没有,百度了一下,没找到解决办法,晚上问了下师兄,装了个软件,格式化sd卡一下子就解决了. 大家可以去我师兄的博客下看 ...

  5. sim插拔识别时间_求助:如何实现不关机SIM卡插拔(不用PUSH式卡座)

    求助:如何实现不关机SIM卡插拔(不用PUSH式卡座) kenny_nap Post at 2010/12/14 20:27:37 有一个平板项目,SIM卡座是侧边,使用频率很高,push式卡座寿命普 ...

  6. sim插拔识别时间_一种sim卡检测装置及其检测sim卡插拔的方法

    一种sim卡检测装置及其检测sim卡插拔的方法 [技术领域] [0001]本发明涉及移动通讯设备领域,尤其涉及一种S頂卡检测装置及其检测S頂卡插拔的方法. [背景技术] [0002]S頂卡是移动终端中 ...

  7. 【Android 性能优化】应用启动优化 ( 方法追踪代码模板 | 示例项目 | SD 卡访问权限 | 示例代码 | 获取 Trace 文件 | Android Studio 查看文件)

    文章目录 一. 方法追踪代码模板 二. 追踪 Launch 页面的 onCreate 方法执行情况 1. 示例项目 2. SD 卡访问权限问题 ( 动态权限申请 ) 3. MainActivity o ...

  8. android 读写cpu卡,RAM/CPU和SD卡读写性能提升_联想 K860i_手机Android频道-中关村在线...

    RAM\CPU和SD卡读写性能提升 AnTuTu(超级兔子) 超级兔子系统评测AnTuTu Benchmark是一个专门给Android系统的手机.平板电脑硬件跑分的软件,它能一键运行完整测试项目,通 ...

  9. H5页面卡顿加载慢原因分析

    H5页面卡顿原因分析: 1.动画太多:渲染重绘占用GPU 2.页面操作导致重绘频繁 3.页面元素复杂:资源类标签太多(图像/视频/dom树太长) 4.内置webview性能太差 5.和3类似,iOS出 ...

最新文章

  1. shujufenxi:一季度中国人每天存700亿元!“报复性存款”能带来消费吗?
  2. opencv cv::BorderTypes 像素外推方法(Pixel extrapolation method)
  3. idea部署web项目,能访问jsp,访问Servlet却出现404错误的解决方法汇总
  4. python的zip()函数
  5. int main(int argc,char* argv[])的作用
  6. JavaWeb(一)
  7. 化学专业有必要学python吗-cnBeta.COM - 中文业界资讯站
  8. limits学习之各数据类型的范围及相关属性
  9. 一岁半女娃海中“游泳秀” 观众含泪观看
  10. Learning Image Conditioned Label Space for Multilabel Classification
  11. 基于JSP的婚恋交友网
  12. 自从有了物联网,人类便能感知地球。
  13. 传奇人物郭盛华:汽车黑客,远远比我们想象中还要恐怖
  14. python添加第三方库
  15. 1896-2016年奥运会数据分析(SQL)
  16. 前端下载xlsx文件
  17. 网络管理员面试技术试题收集
  18. MySQL半同步复制(文章讲解很透彻!)
  19. 算法-(java)-从文件中读取、写入数据
  20. Deepin V20Beta 版本双硬盘双系统,CPU过热,温度高。NVIDIA驱动安装

热门文章

  1. C# 连接MySQL错误给定关键字不在字典中”,下载最新mysql.dll文件即可
  2. spring整合问题集合1
  3. 学习算法导论-红黑树之摘录
  4. 基础练习 数列排序 c语言
  5. 桌面快速启动软件AltRun----赶快使得你的桌面变整洁吧
  6. 多进程与多线程的区别
  7. 蚂蚁金服大规模分布式事务实践及四种分布式事务模式
  8. ThreadLocal父子线程传递实现方案
  9. 聊聊高并发(四)Java对象的表示模型和运行时内存表示
  10. 一文让你彻底理解 Java HashMap