Broadcast Receiver 的使用

Broadcast-Receiver的使用

最近在复习Android的知识,从最基础的开始深入探究,初学Android的时候,四大组件学的很茫然,其实最多的用到的也就是Activity,其他的接触的都不多。
这里我们探究一下广播机制。

1.动态注册

我们用As新建一个接收器。
新建
这里我们可以使用它的快捷新建的方式,右键包名新建一个Other,选择Broadcast Receiver即可快速新建接收器。

我们输入类名,下面两个默认选中即可,其中exported选中为true,标识允许其他APP调用该组件,enabled标识是否可以被系统实例化,选中也为true,不必多探究。

确认之后,就可以看到系统为我们生成了一段代码

1
2
3
4
5
6
7
public class MyReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
//继承了BroadcastReceiver ,需要实现其中的接受方法。
}
}

同时我们的manifest文件中也出现了注册,注意一下四大组件都需要在manifest中定义哦。

1
2
3
4
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true" />

接收器中我们写一个Log,修改如下

1
2
3
4
5
6
7
8
public class MyReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
//这里我封装了一下log,你依然可以使用原生的log来打印
Logs.d("收到!");
}
}

这样我们的接收器就写好了。
我们在Activity中注册接收器,首先生成接收器和过滤器对象,接收器是用于注册的,那么过滤器是用来干嘛的?

机智如你,当然是过滤广播的,举个例子,学校的广播分年级宣布事情,你是高一的,你就不需要管高二的事情,而只需要过滤出高一的事情即可,一会你就会看到IntentFilter 设置所关心的事情的方法。

1
2
private MyReceiver myReceiver = new MyReceiver();
private IntentFilter mIntentFilter = new IntentFilter();

接下来注册广播,在onCreate()里设置你的关心并且注册。

1
2
3
4
5
6
7
8
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mIntentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
//注册广播接收器
registerReceiver(myReceiver,mIntentFilter);
}

可以看到,addAction()用于设置关心,而我设置的android.net.conn.CONNECTIVITY_CHANGE属性是代表了网络连接状态,所以你自会明白,我这个接收器,专门接受网络变化的广播。

最后要是离开这个页面,我们还需要解注册

1
2
3
4
5
6
@Override
protected void onDestroy() {
super.onDestroy();
//解注册
unregisterReceiver(myReceiver);
}

运行之后,界面没有变化,而当我们点击数据或者无线网关掉网络,我们的Log就会出现打印。

2. 静态注册

上面部分叫做动态注册,因为必须要在页面中使用才能看出来效果,那么还有一个静态注册,场景也很广泛,不用打开APP即可实现。
比如我们常见的开机自启动。
当系统开机的时候,发送一条广播。
注册开机广播接收的App在接收器中即可收到这条广播,然后在其中的逻辑中运行它的服务,即可实现开机自启动。
但目前很多国产手机都右禁用开机自启动,所以这个过程要更为复杂一些。

依然用刚才的接收器,里面的内容改成Toast(这里依然是我封装的,请自己选择合适的方式使用)

1
2
3
4
@Override
public void onReceive(Context context, Intent intent) {
Toasts("开机啦!");
}

然后给系统添加开机自启动权限。

1
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

接着又修改了一下我们刚才注册的receiver,里面添加了标签,这个看起来就异常的简单啦。
因为我们刚才学到的动态注册就是用了这个过滤器,这里,只不过是在xml中的写法,比如下面的action标签,添加的android.intent.action.BOOT_COMPLETED属性,都是我们刚才降到过的,其中BOOT_COMPLETED标识开机完成,类似的属性还有重启等等。

1
2
3
4
5
6
7
8
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>

这样我们只需要一个接收器和xml这一段内容即可,刚才写的动态注册都要删掉了。
这样我们关机后,再开机即可收到Toast,当然部分手机可能因为自动禁用开机自启动权限效果有所不同,请自行探究。

3.自定义广播

有了刚才的经验,我们就可以发送自定义广播了,无非是过滤器所关注的广播改成你所广播的内容,(接收器目前是开机Toast提示,该与不该都可),主要是看xml中,

1
2
3
4
5
6
7
8
9
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="susu.MY_BROADCAST"/>
</intent-filter>
</receiver>

我继续在原代码上添加了新的一行,并且让这个关注点叫做susu.MY_BROADCAST,名字随意,一会我们发送的时候对应起来即可。

MainActivity中添加了一个按钮,点击这个按钮的时候即可发送广播,可以看到这里的广播就是我们刚才注册的那个。

1
2
3
4
5
6
7
8
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("susu.MY_BROADCAST");
sendBroadcast(intent);
}
});

我们看一下我们调用的这个intent构造吧,(按住Ctrl,单击new Intent的Intent)

1
2
3
4
// @param action The Intent action, such as ACTION_VIEW.
public Intent(String action) {
setAction(action);
}

可以看到@param已做出解释,action是intent action,也就是我们前面一直提到的action,这样就全部对应起来啦。

运行起来,点击按钮,会弹出相应提示。

4. 小结

这里总结一下。

  1. 广播注册主要分为静态注册和动态注册,其中值得我们关注的就是过滤器,通过设置过滤的action(动作),接受自己所感兴趣的事情。
  2. 上述例子中,开机自启动的实现如果不能达成效果也很正常,由于API的变更或者厂商的定制总会出现一些各种各样的问题。
    3.前面提起过,广播是全局的,你写的App发送广播,别的App也可以收到(如果注册了的话),别的App发你的也可以收到,这里你可以写两个Demo尝试一下。
  3. 如果你用的Android8.0及以上的系统,可能上述自定义广播栗子无法实现预期效果,主要是因为Android API的修改,Android系统电量系统优化导致的。
    你可以给intent增加了一个内容,修改如下
    1
    2
    3
    Intent intent = new Intent("susu.MY_BROADCAST");
    intent.setComponent(new ComponentName("cn.surine.statusbardemo","cn.surine.statusbardemo.MyReceiver"));
    sendBroadcast(intent);

我们可以看到设置了一个Component,第一个参数是包名,第二个参数是MyReceiver即接收器的路径。

酱紫,就可以收到啦!
你以为结束了?
不可能!!!

5.有序广播

What?怎么又来,有序广播是什么。
有序广播是按照顺序来的,举个栗子,小红,小张,小明都要接受老师的同一条命令。
有两种形式,第一种,老师把他们三个找来,一下子传达,这个方式叫做标准广播,上面我们写过的都是标准广播
第二种,老师把小红找来,传达之后让小红传给小张,小张传给小明,但这里就有问题了,为什么是先传给小红而不是小明,小张会不会不给小明传达?等等等等问题。
这种方式就是有序广播,对应的问题就是广播优先级广播截断

那么现在我们实现有序广播。

1
sendOrderedBroadcast(intent,null);

好本次教程到此结束。
What???

因为太简单了,刚才我们写的sendBroadcast方法你还记得吧,别告诉我你忘了,你要是忘了就代表你一直在抄我的代码而没有自己写。

那个方法传了一个intent进去,这里sendOrderedBroadcast也传了intent,第二个参数为null是receiverPermission,暂且不用管。

这样就可以了,这就是有序广播。

但是有序广播刚才提到了两个问题,优先级和截断。
优先级在哪里设置?
答案就是注册这里,设置优先级100,保证先收到。

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

确保它第一个收到之后,我们可以在它的接收器方法中增加一个截断。

1
2
3
4
@Override
public void onReceive(Context context, Intent intent) {
abortBroadcast();
}

这样 下一个,比如说小明,就收不到广播了,是不是很霸道的样子。
这里我就一个Demo,我也木有演示效果,你可以自行演示。

6. 本地广播

刚才我们上面全部的广播,我都提到过,全局,全局意味着什么,别的App也可以收到我们的广播,万一我们的广播action恰好被别人知道了(虽然几率很小),那他是不是可以不停的发送垃圾广播来干扰我们的app正常运行,或者我们发送的关键数据,会不会泄露到其他App中?

这系列问题,都急需一个方法来处理,这就是本地广播。
下面我们来实现。

依然是MyReceive,我们看一下新代码,自行处理接受到信息的内容。

1
2
3
4
5
6
7
public class MyReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Toasts.show("接收到广播");
}
}

xml改成这样。

1
2
3
4
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true" />

所有的操作都将在Java里面匹配。

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
37
38
39
40
41
42
43
44
45
46
47
48
49
package cn.surine.statusbardemo;

import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

private Button button;
private LocalBroadcastManager localBroadcaseManager;
private MyReceiver myReceiver = new MyReceiver();
private IntentFilter intentFilter = new IntentFilter();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取LocalBroadcastManager实例
localBroadcaseManager = LocalBroadcastManager.getInstance(this);
//添加过滤器
intentFilter.addAction("susu.MY_BROADCAST");
//注册
localBroadcaseManager.registerReceiver(myReceiver,intentFilter);

button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//发送广播
Intent intent = new Intent("susu.MY_BROADCAST");
intent.setComponent(new ComponentName("cn.surine.statusbardemo","cn.surine.statusbardemo.MyReceiver"));
sendBroadcast(intent);
}
});

}


//解注册
@Override
protected void onDestroy() {
super.onDestroy();
localBroadcaseManager.unregisterReceiver(myReceiver);
}
}

这里是发送的全部的代码,可以看到似曾相识,因为这个和动态注册是一样的,只不过由LocalBroadcastManager一手接管了注册,解注册等等工作。

可能你有疑问,动态和本地都差不多,为啥要区分,
主要是是因为动态广播在其他App可以发送,本地广播就局限于App内。

7 .总结

又是总结。
这回真的是总结了。
在这里开始挖坑,有的童鞋可能用过EventBus,它的形式跟广播类似,不用在乎是否收到,只要订阅了接收器,就可以收到接受的信息,不过也有所不同。

挖好了坑,我们不知道何年何月才能探究出来。
总之,要学的东西有很多,继续加油!