
目录
广播机制简介
接收系统广播
动态注册
静态注册
发送自定义广播
发送标准广播
发送有序广播
Android允许应用程序自由的发送和接受广播,这些广播可能是来自系统的,也可能是来自应用程序的。
广播主要可以分为两种类型:
Android 内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到 各种系统的状态信息。比如手机开机完成后会发出一条广播,电池的电量发生变化会发出一 条广播,时间或时区发生改变也会发出一条广播等等。如果想要接收到这些广播,就需要使 用广播接收器。
广播接收器可以自由地对自己感兴趣的广播进行注册,这样当有相应的广播发出时,广 播接收器就能够收到该广播,并在内部处理相应的逻辑。注册广播的方式一般有两种,在代 码中注册和在 AndroidManifest.xml 中注册,其中前者也被称为动态注册,后者也被称为静态 注册。
如何创建一个广播接收器?新建一个类,让它继承自 BroadcastReceiver, 并重写父类的 onReceive()方法就行了。这样当有广播到来时,onReceive()方法就会得到执行, 具体的逻辑就可以在这个方法中处理。
一、动态注册监听网络变化
通过动态注册的方式编写一个能够监听网络变化的程序,借此学习广播接收器的基本用法。新建一个 BroadcastTest 项目,然后修改 MainActivity 中的代码,如下所示:
public class MainActivity extends Activity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(networkChangeReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(networkChangeReceiver);
}
class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "network changes",
Toast.LENGTH_SHORT).show();
}
}
}
1. 在MainActivity 中定义了一个内部类NetworkChangeReceiver,这个类是继承自BroadcastReceiver 的,并重写了父类的onReceive()方法。这样每当网络状态发生变化时,onReceive()方法就会得到执行,这里只是简单地使用Toast 提示了一段文本信息。
2. 创建IntentFilter 的实例,并给它添加了一个值android.net.conn.CONNECTIVITY_CHANGE 的action,当网络状态发生变化, 系统发出的是一条android.net.conn.CONNECTIVITY_CHANGE 的广播。
3. 接下来创建了一个NetworkChangeReceiver 的实例,
4. 然后调用registerReceiver()方法进行注册,将NetworkChangeReceiver 的实例和IntentFilter 的实例都传了进去
这样NetworkChangeReceiver 就会收到所有值为android.net.conn.CONNECTIVITY_CHANGE 的广播,也就实现了监听网络变化的功能。
5. 动态注册的广播接收器一定都要取消注册才行,这里我们是在onDestroy()方法中通过调用unregisterReceiver()方法来实现的。
二、动态注册监听时间变化
通过动态注册的方式编写一个能够监听时间变化的程序, 然后修改MainActivity中的代码,如下所示:
import androidx.appcompat.app.AppCompatActivity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private TimeChangeReceiver timeChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
// 时间变化
intentFilter.addAction("android.intent.action.TIME_TICK");
timeChangeReceiver = new TimeChangeReceiver();
registerReceiver(timeChangeReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(timeChangeReceiver);
}
class TimeChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Time has changed", Toast.LENGTH_LONG).show();
}
}
}
在MainActivity中定义了一个内部类TimeChangeReceiver,这个类是继承自BroadcastReceiver的,并重写了父类的onReceive()方法。这样每当系统时间发生变化时,onReceive()方法就会得到执行,这里只是简单地使用Toast提示了一段文本信息 。
观察onCreate()方法,创建了一个IntentFilter的实例,并给它添加了一个值为android.intent.action.TIME_TICK的action,当系统时间发生变化时,系统发出的正是一条值为android.intent.action.TIME_TICK的广播,也就是说BroadcastReceiver想要监听什么广播,就在这里添加相应的action。接下来创建了一个TimeChangeReceiver的实例,然后调registerReceiver()方法进行注册,将TimeChangeReceiver的实例和IntentFilter的实例都传了进去,这样TimeChangeReceiver就会收到所有值为android.intent.action.TIME_TICK的广播,也就实现了监听系统时间变化的功能。
动态注册的BroadcastReceiver可以自由地控制注册与注销,在灵活性方面有很大的优势。但是它存在着一个缺点,即必须在程序启动之后才能接收广播,因为注册的逻辑是写在onCreate()方法中的。那么有没有什么办法可以让程序在未启动的情况下也能接收广播呢?这就需要使用静态注册的方式。
其实从理论上来说,动态注册能监听到的系统广播,静态注册也应该能监听到,在过去的Android系统中确实是这样的。但是由于大量恶意的应用程序利用这个机制在程序未启动的情况下监听系统广播,从而使任何应用都可以频繁地从后台被唤醒,严重影响了用户手机的电量和性能,因此Android系统几乎每个版本都在削减静态注册BroadcastReceiver的功能。
在Android 8.0系统之后,所有隐式广播都不允许使用静态注册的方式来接收了。隐式广播指的
是那些没有具体指定发送给哪个应用程序的广播,大多数系统广播属于隐式广播,但是少数特
殊的系统广播目前仍然允许使用静态注册的方式来接收。这些特殊的系统广播列表详见https://developer.android.google.cn/guide/components/broadcastexceptions.html。
在这些特殊的系统广播当中,有一条值为android.intent.action.BOOT_COMPLETED的广播,这是一条开机广播。使用该广播实现一个开机启动的功能。在开机的时候,我们的应用程序肯定是没有启动的,因此这个功能显然不能使用动态注册的方式来实现,而应该使用静态注册的方式来接收开机广播,然后在onReceive()方法里执行相应的逻辑,这样就可以实现开机启动的功能了。除了内部类方式创建的BroadcastReceiver,其实还可以通过Android Studio提供的快捷方式来创建。右击com.example.broadcasttest包→New→Other→Broadcast Receiver,会弹出如图6.4所示的窗口。
修改MainActivity内容如下,在onReceive()方法中使用Toast弹出一段提示信息:
package com.example.broadcasttest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// TODO: This method is called when the BroadcastReceiver is receiving
// an Intent broadcast.
Toast.makeText(context, "Boot Complete", Toast.LENGTH_SHORT).show();
}
}
另外,静态的BroadcastReceiver一定要在AndroidManifest.xml文件中注册才可以使用。不
过,由于我们是使用Android Studio的快捷方式创建的BroadcastReceiver,因此注册这一步
已经自动完成了,此外还需要进一步修改Androidmanifest内容。修改后的Androidmanifest.xml内容如下:
标签内出现了一个新的标签
BroadcastReceiver都是在这里进行注册的。它的用法其实和标签非常相似,也
是通过android:name指定具体注册哪一个BroadcastReceiver,而enabled和exported属
性则是根据我们刚才勾选的状态自动生成的。
由于Android系统启动完成后会发出一条值为android.intent.action.BOOT_COMPLETED
的广播,因此我们在
明了相应的action。
Android 系统为了保护用户设备的安全和隐私,做了严格的规定:如果程序需要进行一些对用户来说比较敏感的操作,必须在AndroidManifest.xml文件中进行权限声明,否则程序会崩溃。比如这里接收系统的开机广播就是需要进行权限声明的,所以我们在上述代码中使用
现在程序已经可以接收开机广播了,长按模拟机器的power按钮,然后重启模拟器,在启动完成之后就会收到开机广播
要注意的是,不要在onReceive()方法中添加过多的逻辑或者进行任何的耗时操作,因为
BroadcastReceiver中是不允许开启线程的,当onReceive()方法运行了较长时间而没有结
束时,程序就会出现错误。
1. 先定义一个广播接收器来准备接收此广播,新建一个MyBroadcastReceiver 继承自BroadcastReceiver
package com.example.broadcasttest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Received MyBroadcast", Toast.LENGTH_LONG).show();
}
}
2. 在AndroidManifest.xml 中对这个广播接收器进行注册;让MyBroadcastReceiver 接收一条值为com.example.broadcasttest.MY_BROADCAST 的广播,因此待会儿在发送广播的时候,我们就需要发出这样的一条广播。
3. 接下来修改activity_main.xml 中的代码,如下所示:
这里在布局文件中定义了一个按钮,用于作为发送广播的触发点。
4. 。然后修改MainActivity中的代码,在MainActivity中加入如下代码:
Button button = (Button) findViewById(R.id.send_broadcast_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
//intent.setPackage("com.example.broadcasttest");
intent.setPackage(getPackageName());
sendBroadcast(intent);
}
});
在按钮的点击事件里面加入了发送自定义广播的逻辑。首先构建出了一个Intent 对象,并把要发送的广播的值传入,然后调用了Context 的sendBroadcast()方法将广播发送出去,这样所有监听com.example.broadcasttest.MY_BROADCAST 这条广播的广播接收器就会收到消息。此时发出去的广播就是一条标准广播。
第2步调用的setPackage()方法进行更详细的说明。在Android8.0系统之后,静态注册的BroadcastReceiver是无法接收隐式广播的,而默认情况下我们发出的自定义广播恰恰都是隐式广播。因此这里一定要调用setPackage()方法,指定这条广播是发送给哪个应用程序的,从而让它变成一条显式广播,否则静态注册的BroadcastReceiver将无法接收到这条广播。
现在重新运行程序,并点击“Send MyBroadcast”按钮,效果如下图:
另外,由于广播是使用Intent来发送的,因此还可以在Intent中携带一些数据传递给相应的
BroadcastReceiver,这一点和Activity的用法比较相似的。
和标准广播不同,有序广播是一种同步执行的广播,并且是可以被截断的。为了验证这一点, 我们需要再创建一个新的BroadcastReceiver。新建AnotherBroadcastReceiver,代码如下 所示:
AnotherBroadcastReceiver.java
package com.example.broadcasttest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class AnotherBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Receive AnotherBroadcast", Toast.LENGTH_LONG).show();
}
}
在AndroidManifest.xml中对这个BroadcastReceiver的配置进行修改:
AnotherBroadcastReceiver同样接收的是 com.example.broadcasttest.MY_BROADCAST这条广播。重新运行程序,点击“Send Broadcast”按钮,就会分别弹出两次提示信息。
目前为止,程序发出的都是标准广播。发送有序广播只需要改动一行代码,即将sendBroadcast()方法改成 sendOrderedBroadcast()方法。sendOrderedBroadcast()方法接收两个参数:第一个 参数仍然是Intent;第二个参数是一个与权限相关的字符串,这里传入null就行了。现在重 新运行程序,并点击“Send Broadcast”按钮,你会发现,两个BroadcastReceiver仍然都可 以收到这条广播。
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private TimeChangeReceiver timeChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
Button button = (Button) findViewById(R.id.send_broadcast_button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
// intent.setPackage(getPackageName());
intent.setPackage("com.example.broadcasttest");
sendOrderedBroadcast(intent, null);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
这个时候的BroadcastReceiver是有先后顺序的,而且前面的BroadcastReceiver还可以将广播截断,以阻止其继续传播。在注册的时候可以设定广播接收的顺序,修改 AndroidManifest.xml中的代码。通过android:priority属性给AnotherBroadcastReceiver设置了优先级,优先级比较高的先收到广播。将AnotherMyBroadcastReceiver优先级设成100, 以保证它一定会在BroadcastReceiver之前收到广播。
android:priority
- priority 必须是整数,默认是0 , 范围是[-1000, 1000],数字越大,优先级越高
- 优先级的概念用于描述控件的 intent的filter的类型。
- 这个属性只对activity 和 receivers 是有意义的。
- 隐式调用activity: 多个activity满足响应条件,只会触发priority高的activity。
- 有序广播:多个receiver满足响应的条件,会优先触发priority高的receiver。
既然已经获得了接收广播的优先权,那么AnotherMyBroadcastReceiver就可以选择是否允许广播继续传递了。修改MyBroadcastReceiver中的代码,如下所示:
package com.example.broadcasttest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class AnotherBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Receive AnotherBroadcast", Toast.LENGTH_LONG).show();
abortBroadcast();
}
}
添加了abortBroadcast()表示将这条广播截断,后面的BroadcastReceiver将无法再接收到这条广播。重新运行程序,并点击“Send Broadcast”按钮,只有MyBroadcastReceiver中的Toast信息能够弹出,说明这条广播经过AnotherMyBroadcastReceiver之后确实终止传递了。