BroadcastReceiver
参考源码 Android 8.0
广播是一种可以跨进程的通信方式
系统广播列表,在如下路径中:
<Android SDK>/platforms/<任意android-api版本>/data/broadcsat_actions.txt
广播机制
Android中的广播主要可以分为两种类型:标准广播和有序广播。
- 标准广播:是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的;
- 有序广播:是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了;
- 系统广播:Android内置了很多系统级别的广播,可以在应用程序中通过监听这些广播来得到各种系统的状态信息。系统广播在系统内部当特定事件发生时,由系统自动发出广播。比如手机开机完成后会发出一条开机广播,网络状态变化以后也会发出一条网络状态的广播;
- 粘性广播:在android 5.0中不再推荐使用,相应的粘性有序广播,同样不再推荐使用;
- 本地广播:是一种普通广播,只不过只能在App应用内广播,其他应用接收不到本地广播发出来的广播;
标准广播的发送方法是sendBroadcast()方法,有序广播的发送方法是sendOrderedBroadcast()方法。
广播原理
Android中的广播使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型。因此,Android将广播的发送者和接收者解耦,使得系统方便集成,更易扩展。
模型中有三个角色:
1 . 消息订阅者(广播接收者);
2 . 消息发布者(广播发布者);
3 . 消息中心(AMS,即Activity Manager Service);
原理描述:
1 . 广播接收者通过Binder机制在AMS注册;
2 . 广播发送者通过Binder机制向AMS发送广播;
3 . AMS根据广播发送者要求,在已注册列表中,寻找合适的广播接收者。寻找依据是:IntentFilter、Permission;
4 . AMS将广播发送到合适的广播接收者相应的消息循环队列中;
5 . 广播接收者通过消息循环拿到此广播,并回调onReceiver()方法;
注意:
广播发送者和广播接收者的执行都是异步的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到的。
普通广播注册和接收
广播接收器可以自由地对自己感兴趣的广播进行注册,这样当有相应的广播发出时,广播接收器就能够收到该广播,并在内部处理相应的逻辑。
注册广播的方式一般有两种,在代码中注册和在AndroidManifest.xml中注册,前者被称为动态注册,后者被称为静态注册。动态注册的广播接收器一定要取消注册,一般是在Activity的onDestroy()方法中通过调用unregisterReceiver()方法来实现。动态注册的广播接收器可以自由地控制注册与注销,在灵活性方面有很大的优势,但是他也存在一个缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在onCreate()方法中的。要想在程序未启动的情况下就能接收到广播,需要使用静态注册的方式。静态注册的广播接收者会在程序运行的整个过程中一直存在,不会被注销掉,当程序进程被杀掉以后不会再接收广播。
创建一个广播接收器,只需要新建一个类,让它继承自BroadcastReceiver,并重写父类的onReceive()方法就行了。这样当有广播到来时,onReceive()方法就会得到执行,具体的逻辑就可以在这个方法中处理。
动态广播
//广播接收器中注册广播进行接收其他地方发送出来的广播;
public class Broadcast1Activity extends AppCompatActivity {
private BroadcastReceiver1 mBroadcastReceiver1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_broadcast1);
//注册广播;
mBroadcastReceiver1 = new BroadcastReceiver1();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.kang.broadcast1");
registerReceiver(mBroadcastReceiver1,intentFilter);
}
class BroadcastReceiver1 extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals("com.kang.broadcast1")){
String name = intent.getStringExtra("intent_name");
Toast.makeText(context, "接收到了com.kang.broadcast1广播:" + name, Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//注销广播;
unregisterReceiver(mBroadcastReceiver1);
}
}
//发送广播;
Intent broad1Intent = new Intent();
broad1Intent.setAction("com.kang.broadcast1");
broad1Intent.putExtra("intent_name","kang");
sendBroadcast(broad1Intent);
静态广播
静态广播需要在AndroidManifest清单文件中进行注册。
<receiver name=".CustomStaticReceiver">
<intent-filter>
<action android:name="com.kang.statiReceiver"/>
</intent-filter>
</receiver>
而且,静态广播接收者需要单独地写一个类,而不能像动态广播一样可以作为一个内部类写在Activity中。
注册方式 | 特点 | 应用场景 |
---|---|---|
静态注册 | 常驻,不受任何组件的生命周期影响;应用程序关闭后,如果有信息广播来,程序依旧会被系统调用,缺点是耗电、占内存 | 需要时刻监听的广播 |
动态注册 | 非常驻,灵活、跟随组件的生命周期变化;组件结束就是广播结束,在组件结束前,必须移除广播接收器 | 需要特定时刻监听的广播 |
有序广播
有序广播是可以设置广播接收器的优先级的,就是使用属性priority来设置的。优先级的数值范围是-1000到1000的int型数值。在有序广播中,可以使用abortBroadcast()方法来将广播截断,后面的广播接收器就无法再接收到这条广播了。这个priority可以在静态注册时在清单文件中赋值,使用android:priority=""属性,也可以在动态注册时使用IntentFilter的setPriority()方法进行赋值。需要注意的是,当优先级一致时,动态广播优先级比静态广播高。高优先级的广播接收者可以使用abortBroadcast()方法终止广播的继续传播,这样低优先级的广播接收者就收不到广播了。广播接收者可以通过getResultExtras()获取上一级传递过来的数据,可以通过setResultExtras()方法将数据存入广播中传递给下一级广播接收者。
有序广播的使用过程与普通广播非常类似,差异仅在于广播的发送方式是使用sendOrderedBroadcast(Intent intent)方法。
<intent-filter android:priority="100">
<action android:name="xxx"/>
</intent-filter>
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.test");
intentFilter.setPriority(100);
registerReceiver(mReceiver,intentFilter);
//终止广播,不让其往下传递;
mReceiver.abortBroadcast();
//发送数据给下一级广播接收者;
Bundle bundle = new Bundle();
bundle.putString("bundle_name","kang");
mReceiver.setResultExtras(bundle);
//获取数据;
Bundle bundle = mReceiver.getResultExtras(true);
本地广播
前面说的广播都是属于系统全局广播,即发出去的广播可以被其他任何应用程序接收到,并且我们也可以接受来自其他任何应用程序的广播。这样就很容易引起安全性的问题,比如说我们发送的一些携带关键性数据的广播有可能被其他的应用程序截获,或者其他的程序不停地向我们的广播接收器里发送各种垃圾广播。为了能够解决广播的安全性问题,Android引入了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播。
本地广播主要是使用了一个LocalBroadcastManager来对广播进行管理,并提供了发送和注册广播接收器的方法。本地广播无法通过静态注册的方式来接收。其实也可以理解为:因为静态注册就是为了让程序在未启动的情况下也能收到广播,而发送本地广播时,我们的程序肯定是已经启动了,因此也完全不需要使用静态注册的功能。
本地广播的优势:
1.可以明确地知道正在发送的广播不会离开我们的程序,因此不必担心机密数据泄漏;
2.其他的程序无法将广播发送到我们程序的内部,因此不需要担心会有安全漏洞的隐患;
3.发送本地广播比发送系统全局广播更加高效;
public class NetworkChange2Activity extends AppCompatActivity {
@BindView(R.id.networkChangeActivity_btn)
Button mNetworkChangeActivityBtn;
private LocalReceiver mLocalReceiver;
private LocalBroadcastManager mLocalBroadcastManager;
public static void toNetworkChange2Activity(Context context){
Intent intent = new Intent(context,NetworkChange2Activity.class);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_network_change2);
ButterKnife.bind(this);
init();
}
private void init() {
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.test.LOCAL_BROADCAST");
mLocalReceiver = new LocalReceiver();
mLocalBroadcastManager.registerReceiver(mLocalReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
mLocalBroadcastManager.unregisterReceiver(mLocalReceiver);
}
@OnClick(R.id.networkChangeActivity_btn)
public void onViewClicked() {
Intent intent = new Intent("com.example.test.LOCAL_BROADCAST");
mLocalBroadcastManager.sendBroadcast(intent);
}
class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "收到了本地广播消息...", Toast.LENGTH_SHORT).show();
}
}
}
广播的生命周期
一个广播接收者只有一个回调方法:onReceive()。当一个广播消息到达接收者时,Android调用它的onReceive()方法,并传递包含消息的Intent对象。广播接收者被认为仅当它执行这个方法时是活跃的。当onReceive()返回后,它是不活跃的。有一个活跃的广播接收者的进程是受保护的,不会被杀死。但是系统可以在任何时候杀死仅有不活跃组件的进程,当占用的内存是别的进程需要的时候。
如果一个广播消息的响应是比较耗费时间的,那么就应该在单独的线程中去做这些耗时操作,远离用户界面其他组件运行的主线程。如果在onReceive()中新建线程,那么整个进程包括线程会被判定为不活跃(除非进程中的其他应用程序组件是活跃的),将使这个进程处于容易被杀掉的危险中。解决这个问题的方法是onReceive()中启动一个service服务,让service去做耗时操作,这样系统就会知道进程中有活跃的工作在做了。
BroadcastReceiver为广播接收器,它和事件处理机制类似,只不过事件的处理机制是程序组件级别的。
BroadcastReceiver的生命周期比较短,从onReceive()方法开始执行到结束,为其有效期,之后系统会销毁BroadcastReceiver对象。所以在onReceive()方法中执行异步请求操作,很可能请求结果还没返回,BroadcastReceiver就被系统回收了。如果在onReceive()方法中不能在10s内执行完成,将会产生程序无响应错误,也就是ANR(Application Not Response)。如果在BroadcastReceiver中执行耗时操作,通过创建子线程的方式是不可靠的,因为BroadcastReceiver的生命周期很短,一旦结束,其所在的进程属于空进程(没有任何活动组件的进程),极易在系统内存不足时优先被杀死,这样子也会把正在工作的子线程杀死。如果需要做耗时操作,就可以在onReceive()中启动一个Service,这样可以提高宿主进程的优先级,保证耗时操作执行完成。
引申一下,Service中去做耗时操作,可能应用会被置于后台,如果担心Service被回收,可以通过startForeground()方法提升其优先级。
系统广播
Android中内置了多个系统广播,只要涉及到手机的基本操作(如开机、网络状态变化、拍照等),都会发出相应的广播。
每个广播都有特定的Intent-Filter(包括具体的action),Android系统广播action如下:
系统操作 | action |
---|---|
监听网络变化 | android.net.conn.CONNECTIVITY_CHANGE |
关闭或打开飞行模式 | |
充电时或电量发生变化 | |
电池电量低 | |
电池电量充足(即从电量低变化到饱满时会发出广播) | |
系统启动完成后(仅广播一次) | |
按下照相时的拍照按键(硬件按键)时 | |
屏幕锁屏 | |
设备当前设置被改变时(界面语言) | |
插入耳机时 | |
未正确移除SD卡但已取出来时(正确移除方式:设置--SD卡和设备内存--卸载SD卡) | |
插入外部存储装置(如SD卡) | |
成功安装APK | |
成功删除APK | |
重启设备 | |
屏幕被关闭 | |
屏幕被打开 | |
关闭系统时 |
当使用系统广播时,只需要在注册广播接收者时定义相关的action即可,并不需要手动发送广播,当系统有相关操作时会自动进行系统广播。
广播注意事项
广播传递的安全问题
Android中的广播可以跨进程甚至跨App直接通信,对于exported属性为true,或者没有exported属性但是有intent-filter的情况下,可能出现如下的安全隐患:
- 其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;
- 其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息;
常见的增加安全性的方案有如下几种: - 对于同一个App内部发送和接收广播,将exported属性设置为false,使得非本App内部发出的广播不被接收;
- 在广播发送和接收时,都增加上相应的permission,用于权限验证;
- 发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定,这样广播将只会发送到此包中的App内与之相匹配的有效广播接收器中;
- 尽量不要在广播接收器的onReceive()方法中开启线程,因为有可能线程中
源码跟踪
注册BroadcastReceiver
1 . ContextWrapper -- registerReceiver()
2 . ContextImpl -- registerReceiver()
3 . ContextImpl -- registerReceiverInternal()
4 . ActivityManagerService -- registerReceiver()
5 . BroadcastQueue
6 . LoadApk
当AMS接收到广播之后就可以从mReceiverResolver中找到对应的广播接收者。这样,广播的注册就完成了。
广播的发送
1 . ContextWrapper -- sendBroadcast()
2 . ContextImpl -- sendBroadcast()
3 . ActivityManagerService -- braodcastIntent()
4 . ActivityManagerService -- broadcastIntentLocked()
5 . BroadcastQueue -- scheduleBroadcastsLocked()
6 . BroadcastQueue -- BroadcastHandler -- handleMessage()
7 . BroadcastQueue -- processNextBroadcast()
8 . BroadcastQueue -- deliverToRegisteredReceiverLocked()
9 . BroadcastQueue -- performReceiveLocked()
void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
Intent intent, int resultCode, String data, Bundle extras,
boolean ordered, boolean sticky, int sendingUser) throws RemoteException {
//---;
app.thread.scheduleRegisterdReceiver(receiver, intent, resultCode,data, extras, ordered, sticky, sendingUser, app.repProcState);
//---;
}
10 . ActivityThread -- scheduleRegisterdReceiver()
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
int resultCode, String dataStr, Bundle extras, boolean ordered,
boolean sticky, int sendingUser, int processState) throws RemoteException {
//---;
receiver.performReceiver(intent, resultCode, dataStr, extras, ordered, sticky, sendingUser);
//---;
}
11 . LoadApk.ReceiverDispatcher.InnerReceiver -- performReceive()
IIntentReceiver是一个AIDL。调用是在LoadApk类中的内部类ReceiverDispatcher中的InnerReceiver类。
12 . LoadApk.ReceiverDispatcher -- performReceive()
13 . LoadApk.ReceiverDispatcher.Args -- getRunnable()
public final Runnable getRunnable() {
return () -> {
//---;
receiver.onReceive(mContext, intent);
//---;
}
}
最终调用BroadcastReceiver对象的onReceive()方法。这样,注册的广播接收者就能收到广播了。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
------------------last line for now------------------
-