一. 问题描述:

最近在重构存储设备功能模块,想要将原本的动态广播转换为静态广播,这样不管在哪个地方,存储设备状态改变,我都能收到提示。于是我尝试了以下代码:

1
2
3
4
5
6
7
8
9
<receiver
android:name=".MyFileReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="100">
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<action android:name="android.intent.action.MEDIA_UNMOUNTED" />
</intent-filter>
</receiver>

但是如论如何,都接收不到该广播。

起初以为是自己的java 文件有毛病,但是手动发送一个广播,又能成功接收到。

所以具体是什么原因呢? 首先我按照 “静态广播无法接收” 的关键词 google 了一下,查到的全是静态广播如何写这样的文章,我….

二. 解决办法

过了几天,突发奇想,按照“INTENT.ACTION_MEDIA_MOUNTED” 这个关键词直接 google,找了一圈,发现 一篇文章 里有解决办法,如下:

1
2
3
4
5
6
7
8
9
10
11
<receiver
android:name=".MyFileReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="100">
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<action android:name="android.intent.action.MEDIA_UNMOUNTED" />
// 需要添加下面这一行
<data android:scheme="file"/>
</intent-filter>
</receiver>

所以为什么要添加这一行呢,参考链接里也贴了 详解的链接 ,接下来,我就再复习一遍吧。

三. 关于 Andoroid 事件过滤策略

  1. Intent 的分类:显式和隐式

    一个 Intent 包含的信息

图片取自 Intent详解 该文章有对 Intent 进行较为详细的解释,需要的战友可以进去看看。
由图可以看到,显式 Intent 与隐式 Intent 最大的区别,是 ComponentName 是否为空。

2. 事件过滤策略与 IntentFilter
原链接 的比喻挺适合理解。我就用自己的话再复述一遍吧:将系统广播的分发比喻为信件的派发。当信件上有收件人的详细地址时,邮递员可以直接通过地址找到你,并将信件放到你的手上。那如果,信件上没有详细地址,只有收件人的特征—— 男,30岁,码农。这样的信件邮递员该如何处理呢?邮递员惊喜地发现,负责信件派发区域内的所有房门上都有住户的特征介绍,比如:女,20岁,学生; 女,35岁,家庭主妇… 如此一来,只要对应上 所有 特征,就把信件交付给相应地住户就 OK 了。

当然这是不恰当地比喻,因为信件只有一封,只能给到一个人。但是系统可以将事件分发给所有符合条件的程序。

这个比喻里,有详细地址的信件,就相当于 Intent.getComponent != null 系统可以通过ComponentName 直接找到接收此事件的组件。而没有指定Intent.Component 的事件,就只能借助其它特征来决定该给哪些程序发广播了,这些特征就是ActionDataCategory 当程序给出的所有接收特征与系统广播的这些特征一一对应,那么就能接收到相应的广播啦。

那么我们就来研究下,在 SD 卡发生插拔事件时,系统发出的广播都携带了哪些特征叭!

  • 动态注册广播:
1
2
3
4
5
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
filter.addDataScheme("file"); // 必需,否则无法接收到事件
mContext.registerReceiver(myStorageReceiver, filter);
  • 打印信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private BroadcastReceiver myStorageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {

ComponentName componentName = intent.getComponent();
Set<String> categories = intent.getCategories();
Uri data = intent.getData();
String action = intent.getAction();

if (componentName != null) {
Log.i(TAG, "onReceive: componentName----" + componentName.toString());
} else {
Log.i(TAG, "onReceive: componentName----null");
}

if (categories != null) {
for (String item : categories) {
Log.i(TAG, "onReceive: category----" + item);
}
} else {
Log.i(TAG, "onReceive: categories----null");
}

if (data != null) {
Log.i(TAG, "onReceive: data----" + data);
} else {
Log.i(TAG, "onReceive: data----null");
}

if (action != null) {
Log.i(TAG, "onReceive: action----" + action);
} else {
Log.i(TAG, "onReceive: action----null");
}
}
};
  • 打印结果

可以看到,componentName 为 空,为隐式事件。

由图可以看到,IntentFilter 可以添加如上特征进行事件过滤,必须全部匹配才能收到事件广播。

3. Data 匹配规则

DataUri 来描述,而Uri由三部分组成:

==scheme://host:port/path== | 模式://端口:主机号/路径

从打印结果可以看到,SD 挂载事件,data: scheme == "file"

现在我们需要了解一个规则,即对于Uri,只比较filter 中声明的部分,如果匹配到声明的部分,即认为匹配成功。

我们代码中只声明了scheme 部分,并且与Uri 成功匹配,所以匹配成功。

Uri 的具体匹配规则如下:

  • 如果dataUridataType 均为空,那么filter中也需要全部为空才能匹配成功
  • 如果dataUri为空,dataType不为空,那么filter中的Uri必须为空,且dataType匹配成功,才能匹配成功
  • 如果dataUri 不为空,dataType为空,那么filter中的Uri必须匹配成功,且dataType为空,才能匹配成功
  • 如果dataUridataType 均不为空,那么filter中必须定义两者并且都匹配成功,才能匹配成功

一句话总结,即IntentFilter必须与Intent携带信息全部匹配成功,才能接收事件。

四. 最后

还是看看 原链接 吧,讲得真的很好了。小菜鸡的语言组织能力还是太差了,今后继续加油。

留下疑问:既然dataUri认为部分匹配成功,即认为匹配成功。那么我filter中不添加scheme,只添加path 匹配成功也收不到广播的问题在哪?