广播机制简介
这是因为Android中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会收到自己所关心的广播内容,这些广播可能是来自于系统的,也可能是来自于其他应用程序的。Android提供了一套完整的API,允许应用程序自由地发送和接收广播。发送广播的方法其实之前稍微提到过,如果你记性好的话,可能还会有印象,就是借助我们第3章学过的Intent。
而接收广播的方法则需要引入一个新的概念——BroadcastReceiver。
roadcastReceiver的具体用法将会在下一节介绍,这里我们先来了解一下广播的类型。Android中的广播主要可以分为两种类型:标准广播和有序广播:
标准广播(normal broadcasts)是一种完全异步执行的广播,在广播发出之后,所有的BroadcastReceiver几乎会在同一时刻收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的
有序广播(ordered broadcasts)则是一种同步执行的广播,在广播发出之后,同一时刻只会有一个BroadcastReceiver能够收到这条广播消息,当这个BroadcastReceiver中的逻辑执行完毕后,广播才会继续传递。所以此时的BroadcastReceiver是有先后顺序的,优先级高的BroadcastReceiver就可以先收到广播消息,并且前面的BroadcastReceiver还可以截断正在传递的广播,这样后面的BroadcastReceiver就无法收到广播消息了。
接收系统广播
Android内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种系统的状态信息。比如手机开机完成后会发出一条广播,电池的电量发生变化会发出一条广播,系统时间发生改变也会发出一条广播,等等。如果想要接收这些广播,就需要使用BroadcastReceiver,下面我们就来看一下它的具体用法。
动态注册监听时间变化
我们可以根据自己感兴趣的广播,自由地注册BroadcastReceiver,这样当有相应的广播发出时,相应的BroadcastReceiver就能够收到该广播,并可以在内部进行逻辑处理。
注册BroadcastReceiver的方式一般有两种:
在代码中注册和在AndroidManifest.xml中注册。其中前者也被称为动态注册,后者也被称为静态注册。那么如何创建一个BroadcastReceiver呢?
其实只需新建一个类,让它继承自BroadcastReceiver,并重写父类的onReceive()方法就行了。
这样当有广播到来时,onReceive()方法就会得到执行,具体的逻辑就可以在这个方法中处理。
下面我们就先通过动态注册的方式编写一个能够监听时间变化的程序,借此学习一下BroadcastReceiver的基本用法。新建一个BroadcastTest项目,然后修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
private TimeChangeReceiver timeChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_TIME_TICK);
timeChangeReceiver = new TimeChangeReceiver();
registerReceiver(timeChangeReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(timeChangeReceiver);
}
private class TimeChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Time has changed", Toast.LENGTH_SHORT).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一定要取消注册才行,这里我们是在onDestroy()方法中通过调用unregisterReceiver()方法来实现的。
整体来说,代码还是非常简单的。现在运行一下程序,然后静静等待时间发生变化。系统每隔一分钟就会发出一条android.intent.action.TIME_TICK的广播,因此我们最多只需要等待一分钟就可以收到这条广播了
这就是动态注册BroadcastReceiver的基本用法,虽然这里我们只使用了一种系统广播来举例,但是接收其他系统广播的用法是一模一样的。Android系统还会在亮屏熄屏、电量变化、网络变化等场景下发出广播。如果你想查看完整的系统广播列表,可以到如下的路径中去查看:
<Android SDK>/platforms/<任意android api版本>/data/broadcast_actions.txt
静态注册实现开机启动
动态注册的BroadcastReceiver可以自由地控制注册与注销,在灵活性方面有很大的优势。但是它存在着一个缺点,即必须在程序启动之后才能接收广播,因为注册的逻辑是写在onCreate()方法中的。那么有没有什么办法可以让程序在未启动的情况下也能接收广播呢?这就需要使用静态注册的方式了。
其实从理论上来说,动态注册能监听到的系统广播,静态注册也应该能监听到,在过去的Android系统中确实是这样的。但是由于大量恶意的应用程序利用这个机制在程序未启动的情况下监听系统广播,从而使任何应用都可以频繁地从后台被唤醒,严重影响了用户手机的电量和性能,因此Android系统几乎每个版本都在削减静态注册BroadcastReceiver的功能。
在Android 8.0系统之后,所有隐式广播都不允许使用静态注册的方式来接收了。隐式广播指的是那些没有具体指定发送给哪个应用程序的广播,大多数系统广播属于隐式广播,但是少数特殊的系统广播目前仍然允许使用静态注册的方式来接收。
https://developer.android.google.cn/develop/background-work/background-tasks/broadcasts/broadcast-exceptions?hl=zh-cn
在这些特殊的系统广播当中,有一条值为android.intent.action.BOOT_COMPLETED的广播,这是一条开机广播,那么就使用它来举例学习吧。
这里我们准备实现一个开机启动的功能。在开机的时候,我们的应用程序肯定是没有启动的,因此这个功能显然不能使用动态注册的方式来实现,而应该使用静态注册的方式来接收开机广播,然后在onReceive()方法里执行相应的逻辑,这样就可以实现开机启动的功能了。
那么就开始动手吧。上一小节中我们是使用内部类的方式创建的BroadcastReceiver,其实还可以通过Android Studio提供的快捷方式来创建。右击com.example.broadcasttest包→New→Other→Broadcast Receiver
可以看到,这里我们将创建的类命名为BootCompleteReceiver,Exported属性表示是否允许这个BroadcastReceiver接收本程序以外的广播,Enabled属性表示是否启用这个BroadcastReceiver。勾选这两个属性,点击“Finish”完成创建。
然后修改BootCompleteReceiver中的代码
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();
}
}
代码非常简单,我们只是在onReceive()方法中使用Toast弹出一段提示信息。另外,静态的BroadcastReceiver一定要在AndroidManifest.xml文件中注册才可以使用。不过,由于我们是使用Android Studio的快捷方式创建的BroadcastReceiver,因此注册这一步已经自动完成了。打开AndroidManifest.xml文件瞧一瞧
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<application android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!-- 其他配置 -->
<receiver android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
</receiver>
</application>
</manifest>
可以看到,标签内出现了一个新的标签,所有静态的BroadcastReceiver都是在这里进行注册的。它的用法其实和标签非常相似,也是通过android:name指定具体注册哪一个BroadcastReceiver,而enabled和exported属性则是根据我们刚才勾选的状态自动生成的。
不过目前的BootCompleteReceiver是无法收到开机广播的,因为我们还需要对AndroidManifest.xml文件进行修改才行,
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcasttest">
<!-- 声明所需的权限 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!-- 定义 BroadcastReceiver -->
<receiver android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- 其他组件和配置 -->
</application>
</manifest>
由于Android系统启动完成后会发出一条值为android.intent.action.BOOT_COMPLETED的广播,因此我们在标签中又添加了一个标签,并在里面声明了相应的action。
另外,这里有非常重要的一点需要说明。Android 系统为了保护用户设备的安全和隐私,做了严格的规定:如果程序需要进行一些对用户来说比较敏感的操作,必须在AndroidManifest.xml文件中进行权限声明,否则程序将会直接崩溃。比如这里接收系统的开机广播就是需要进行权限声明的,所以我们在上述代码中使用标签声明了android.permission.RECEIVE_BOOT_COMPLETED权限。
这是你第一次遇到权限的问题,其实 Android中的许多操作是需要声明权限才可以进行的,后面我们还会不断使用新的权限。不过目前这个接收系统开机广播的权限还是比较简单的,只需要在 AndroidManifest.xml 文件中声明一下就可以了。Android 6.0 系统中引入了更加严格的运行时权限,从而能够更好地保证用户设备的安全和隐私。
这是你第一次遇到权限的问题,其实 Android中的许多操作是需要声明权限才可以进行的,后面我们还会不断使用新的权限。不过目前这个接收系统开机广播的权限还是比较简单的,只需要在 AndroidManifest.xml 文件中声明一下就可以了。Android 6.0 系统中引入了更加严格的运行时权限,从而能够更好地保证用户设备的安全和隐私。
到目前为止,我们在BroadcastReceiver的onReceive()方法中只是简单地使用Toast提示了一段文本信息,当你真正在项目中使用它的时候,可以在里面编写自己的逻辑。需要注意的是,不要在onReceive()方法中添加过多的逻辑或者进行任何的耗时操作,因为BroadcastReceiver中是不允许开启线程的,当onReceive()方法运行了较长时间而没有结束时,程序就会出现错误。