BroadCastReceiver 简介
1.定义
- BroadCastReceiver(广播接收者) 是Android四大组件之一,与广播发送一起,利用intent机制,用于系统内的信息传递。
- 源码位于:framework/base/core/java/android.content.BroadcastReceiver.java
2.作用
- 用于监听/接收系统或应用内发出的广播信息,并做出响应
- 应用场景
a. 不同组件之间的通信(包括应用内/不同应用之间)
b.与Android系统在特定情况下的通信,如开机,电话呼入、网络可用等
c.多线程通信
3.实现原理
Android中的广播使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型。
因此,Android将广播的发送者 和 接收者 解耦,使得系统方便集成,更易扩展
模型中有3个角色:
1.消息订阅者(广播接收者)
2.消息发布者(广播发布者)
3.消息中心(AMS,即Activity Manager Service)
原理描述
1.广播接收者通过Binder机制在AMS注册
2.广播发送者通过Binder机制向AMS发送广播
3.AMS根据广播发送者的要求,在已注册列表中,找到合适的广播接收者寻找依据:IntentFilter / Permission
4.AMS将广播发送到合适的广告接收者相应的消息循环排队中
5.广播接收者通过消息循环拿到次广告,并回调onReceive()
特别注意:广播发送者 和 广播接收者的执行 是 异步 的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到;
BroadCastReceiver 使用
1 自定义广播接收者BroadcastReceiver
1.继承BroadcastReceiver类
2.重写抽象方法onReceive()方法
默认情况下,广播接收器运行在UI线程,因此,onReceive方法不能执行耗时操作(不能超过10秒钟),否则将导致ANR。
例子:
public class mBroadcastReceiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
//写入接收广播后的操作
}
}
2 注册BroadcastReceiver
注册有两种方式:静态注册、动态注册
2.1 静态注册
在AndroidManifest.xml里通过标签声明:
<receiver
android:enabled=["true" | "false"]
<!--此broadcastReceiver能否接收其他App的发出的广播
默认值是由receiver中有无intent-filter决定的:如果有intent-filter,默认值为true,否则为false -->
android:exported=["true" | "false"]
android:icon="drawable resource"
android:label="string resource"
//继承BroadcastReceiver子类的类名
android:name=".mBroadcastReceiver"
//具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;
android:permission="string"
//BroadcastReceiver运行所处的进程 //默认为app的进程,可以指定独立的进程 //注:Android四大基本组件都可以通过此属性指定自己的独立进程
android:process="string" >
//用于指定此广播接收器将接收的广播类型 //本示例中给出的是用于接收网络状态改变时发出的广播
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
当此App首次启动时,系统会自动实例化mBroadcastReceiver类,并注册到系统中。
2.2 动态注册####
在代码中通过调用Context的registerReceiver()方法进行动态注册BroadcastReceiver,具体代码如下:
@Override
protected void onResume()
{
super.onResume();
//实例化BroadcastReceiver子类 & IntentFilter mBroadcastReceiver
mBroadcastReceiver = new mBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
//设置接收广播的类型
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
//调用Context的registerReceiver()方法进行动态注册
registerReceiver(mBroadcastReceiver, intentFilter);
}
//注册广播后,要在相应位置记得销毁广播
//即在onPause() 中unregisterReceiver(mBroadcastReceiver)
// 当此Activity实例化时,会动态将MyBroadcastReceiver注册到系统中
// 当此Activity销毁时,动态注册的MyBroadcastReceiver将不再接收到相应的广播。
@Override
protected void onPause()
{
super.onPause();
//销毁在onResume()方法中的广播
unregisterReceiver(mBroadcastReceiver);
}
特别注意
动态广播最好在Activity的onResume()注册、onPause()注销。
原因:
1. 对于动态广播,有注册就必然得有注销,否则会导致内存泄露
重复注册、重复注销也不允许
2. Activity生命周期如下:
Activity生命周期的方法是成对出现的:
- onCreate() & onDestory()
- onStart() & onStop()
- onResume() & onPause()
在onResume()注册、onPause()注销是因为onPause()在App死亡前一定会被执行,从而保证广播在App死亡前一定会被注销,从而防止内存泄露。
不在onCreate() & onDestory() 或 onStart() & onStop()注册、注销是因为:
- 当系统因为内存不足(优先级更高的应用需要内存,请看上图红框)要回收Activity占用的资源时,Activity在执行完onPause()方法后就会被销毁,有些生命周期方法onStop(),onDestory()就不会执行。当再回到此Activity时,是从onCreate方法开始执行。
- 假设我们将广播的注销放在onStop(),onDestory()方法里的话,有可能在Activity被销毁后还未执行onStop(),onDestory()方法,即广播仍还未注销,从而导致内存泄露。
- 但是,onPause()一定会被执行,从而保证了广播在App死亡前一定会被注销,从而防止内存泄露。
2.3 两种注册方式的区别
3 广播发送者向AMS发送广播###
发送方法####
广播使用sendBroadcast(intent)或者sendOrderedBroadcast(intent)发送:
Intent intent= new Intent();
intent.setAction("android.intent.action.HIDE_STATUSBAR");
sendBroadcast(intent);
广播类型####
1.普通广播(Normal Broadcast)
2.系统广播(System Broadcast)
3.有序广播(Ordered Broadcast)
4.粘性广播(Sticky Broadcast)
5.App应用内广播(Local Broadcast)
具体说明如下:
1. 普通广播(Normal Broadcast)
即开发者自身定义intent的广播(最常用)。发送广播使用如下:
Intent intent = new Intent();
//对应BroadcastReceiver中intentFilter的action
intent.setAction(BROADCAST_ACTION);
//发送广播
sendBroadcast(intent);
若被注册了的广播接收者中注册时intentFilter的action与上述匹配,则会接收此广播(即进行回调onReceive())。如下mBroadcastReceiver则会接收上述广播:
<receiver
//此广播接收者类是mBroadcastReceiver
android:name=".mBroadcastReceiver" >
//用于接收网络状态改变时发出的广播
<intent-filter>
<action android:name="BROADCAST_ACTION" />
</intent-filter>
</receiver>
若发送广播有相应权限,那么广播接收者也需要相应权限
2. 系统广播(System Broadcast)
- Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播
- 每个广播都有特定的Intent - Filter(包括具体的action),Android系统广播action如下:
系统操作 | action |
---|---|
监听网络变化 | android.net.conn.CONNECTIVITY_CHANGE |
关闭或打开飞行模式 | Intent.ACTION_AIRPLANE_MODE_CHANGED |
电池电量低 | Intent.ACTION_BATTERY_LOW |
电池电量充足,即从电量低变化到饱满时会发出广播 | Intent.ACTION_BATTERY_OKAY |
系统启动完成后(仅广播一次) | Intent.ACTION_BOOT_COMPLETED |
按下照相时的拍照按键(硬件按键)时 | Intent.ACTION_CAMERA_BUTTON |
屏幕锁屏 | Intent.ACTION_CLOSE_SYSTEM_DIALOGS |
设备当前设置被改变时(界面语言、设备方向等) | Intent.ACTION_CONFIGURATION_CHANGED |
插入耳机时 | Intent.ACTION_HEADSET_PLUG |
未正确移除SD卡但已取出来时(正确移除方法:设置–SD卡和设备内存–卸载SD卡) | Intent.ACTION_MEDIA_BAD_REMOVAL |
插入外部储存装置(如SD卡) | Intent.ACTION_MEDIA_CHECKING |
成功安装APK | Intent.ACTION_PACKAGE_ADDED |
成功删除APK | Intent.ACTION_PACKAGE_REMOVED |
重启设备 | Intent.ACTION_REBOOT |
屏幕被关闭 | Intent.ACTION_SCREEN_OFF |
屏幕被打开 | Intent.ACTION_SCREEN_ON |
关闭系统时 | Intent.ACTION_SHUTDOWN |
重启设备 | Intent.ACTION_REBOOT |
注意:当使用系统广播时,只需要在注册广播接收者时定义相关的action即可,并不需要手动发送广播,当系统有相关操作时会自动进行系统广播
3. 有序广播(Ordered Broadcast)
定义:
- 发送出去的广播被广播接收者按照先后顺序接收
- 有序是针对广播接收者而言的
特点:
- 该广播的级别有级别之分,级别数值是在 -1000 到 1000 之间 , 值越大 , 优先级越高;
- 同级别接收是先后是随机的,再到级别低的收到广播;
- 实验现象,在这个方法发来的广播中,代码注册方式中,收到广播先后次序为:注明优先级的、代码注册的、没有优先级的;如果都没有优先级,代码注册收到为最先
- 先接收的广播接收者可以对广播进行截断(abortBroadcast()),即后接收的广播接收者不再接收到此广播;
- 先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播
例子:
public class FirstRecever extends BroadcastReceiver {
private static final String TAG = "MyReceiver";
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
//先获得传过来的MSG
String msg = intent.getStringExtra("msg");
Log.i(TAG, "FirstRecever:"+msg);
//更改广播数据
Bundle bundle = new Bundle();
bundle.putString("msg", msg + "@FirstReceiver");
setResultExtras(bundle);
}
}
具体使用:
AndroidManifest 注册:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.testbroadcast_order_perssion"
android:versionCode="1"
android:versionName="1.0" >
……
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
……
<receiver android:name=".FirstRecever" >
<intent-filter android:priority="10">
<action android:name="android.intent.action.MY_BROADCAST"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name=".SecondRecever" >
<intent-filter android:priority="9">
<action android:name="android.intent.action.MY_BROADCAST"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
……
</application>
</manifest>
发送广播:
Intent intent = new Intent("android.intent.action.MY_BROADCAST");
intent.putExtra("msg", "hello receiver.");
sendOrderedBroadcast(intent,null);
有序广播的发送主要是
使用:
添加访问权限:
sendOrderedBroadcast(intent, permission)中的第二个参数可以设定有序广播的访问权限:
Intent intent = new Intent("android.intent.action.MY_BROADCAST");
intent.putExtra("msg", "hello receiver.");
sendOrderedBroadcast(intent, "harvic.broadcast.permission");
这段代码中,我们利用 sendOrderedBroadcast(intent, “harvic.broadcast.perssion”); 发送一个必须拥有”harvic.broadcast.perssion”权限的接收器才能接收到我们的广播;
然后我们要在接收器中加入声明使用权限的代码:
<permission android:name="harvic.broadcast.permission" android:protectionLevel="normal"></permission>
然后是底部声明,我们要使用这个权限:
<uses-permission android:name="harvic.broadcast.permission"/>
所以总体的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.testbroadcast_order_perssion"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="14" />
<permission android:name="harvic.broadcast.perssion" android:protectionLevel="normal"></permission>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
……
<receiver android:name=".FirstRecever" >
<intent-filter android:priority="10">
<action android:name="android.intent.action.MY_BROADCAST"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
……
</application>
<!-- 如果不添加使用权限声明,那么接收器会拒绝接受消息的,所以在Log中不会有任何显示 -->
<uses-permission android:name="harvic.broadcast.perssion"/>
</manifest>
4. App应用内广播(Local Broadcast)
背景:
Android中的广播可以跨App直接通信(exported对于有intent-filter情况下默认值为true)
冲突:
可能出现的问题:
其他App针对性发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收广播并处理;
其他App注册与当前App一致的intent-filter用于接收广播,获取广播具体信息;
即会出现安全性 & 效率性的问题。
解决方案:
使用App应用内广播(Local Broadcast)
1.App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。
2.相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高
具体使用1 – 将全局广播设置成局部广播:
1.注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收;
2.在广播发送和接收时,增设相应权限permission,用于权限验证;
3.发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
通过intent.setPackage(packageName)指定报名
具体使用2 – 使用封装好的LocalBroadcastManager类:
使用方式上与全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将参数的context变成了LocalBroadcastManager的单一实例
注:对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册,不能静态注册
//注册应用内广播接收器
//步骤1:实例化BroadcastReceiver子类 & IntentFilter mBroadcastReceiver
mBroadcastReceiver = new mBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
//步骤2:实例化LocalBroadcastManager的实例
localBroadcastManager = LocalBroadcastManager.getInstance(this);
//步骤3:设置接收广播的类型
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
//步骤4:调用LocalBroadcastManager单一实例的registerReceiver()方法进行动态注册
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
//取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
//发送应用内广播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent);
5. 粘性广播(Sticky Broadcast)
由于在Android5.0 & API 21中已经失效,所以不建议使用,在这里也不作过多的总结。
特别注意
对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的
对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext
对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context
对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context
- 对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context;