Android四大组件详解之BroadcastReceiver广播接收者

Linux老司机
• 阅读 5247

Android四大组件详解---BroadcastReceicer广播接收者
广播有两个角色,一个是广播发送者,另一个是广播接收者。
广播按照类型分为两种,一种是全局广播,另一种是本地广播
全局广播:就是发出的广播被其他任意应用程序接收,或者可以接收来自其他任意应用程序的广播。
本地广播:则是只能在应用程序内部进行传递的广播,广播接收器也只能接收内部的广播,不能接收其他应用程序的广播
广播按照机制分两种,一种是标准广播,一种是有序广播
标准广播:是一种异步的方式来进行传播的,所有接收者都会接收事件,不可以被拦截,不可以被修改
有序广播:是一种同步执行的广播,按照优先级,一级一级的向下传递,接收者可以修改广播数据,也可以终止广播事件。
一:使用广播接收器接收广播
1.定义一个TestReceiver类继承广播接收者BroadcastReceiver,复写其中的onReceive()方法

public class TestReceiver extends BroadcastReceiver {
    private static final String TAG="TestReceiver";
 @Override
 public void onReceive(Context context, Intent intent) {
        Log.d(TAG,"onReceive()");
 }
}

2.对广播进行注册
注册方式有两种,一种是动态注册,一种是静态注册
动态注册

public class ReceActivity extends AppCompatActivity {
    private TestReceiver receiver;
 private IntentFilter intentFilter;
 @Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_rece);
 receiver=new TestReceiver();
 intentFilter=new IntentFilter();
 intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
 //当网络发生变化的时候,系统广播会发出值为android.net.conn.CONNECTIVITY_CHANGE这样的一条广播
 registerReceiver(receiver,intentFilter);
 }
}
结果:切换网络变化
D/TestReceiver: onReceive()

静态注册,像这种系统广播,android8.0以上对静态广播注册做了严格限制,就没法接受到网络变化的广播
Android四大组件详解之BroadcastReceiver广播接收者

<receiver android:name=".TestReceiver"
 android:enabled="true"
 android:exported="true">
 <!--//表示是否允许这个广播接收器接收本程序以外的广播-->
 <intent-filter>
 <action android:name="ndroid.net.conn.CONNECTIVITY_CHANGE"/>
 <!--网络变化完成后发出一条广播android.intent.action.BOOT_COMPLETED的广播-->
 </intent-filter>
</receiver>

静态注册一个自定义的广播。

<receiver android:name=".TestReceiver"
 android:enabled="true"
 android:exported="true">
 <!--//表示是否允许这个广播接收器接收本程序以外的广播-->
 <intent-filter>
 <action android:name="11"/>//定义action为“11”
 </intent-filter>
</receiver>

//方式1
Intent intent=new Intent("11");
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
    intent.setPackage(getPackageName());//包名发送广播com.soudao.test
}
sendBroadcast(intent);
//方式2
Intent intent=new Intent("11");
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
    //该方式适用:给其他应用的广播接收者发送消息(指定应用的包名、指定类的全类名)
 intent.setComponent(new ComponentName(getPackageName(), getPackageName()+".TestReceiver"));
 intent.setClassName(getPackageName(), getPackageName()+".TestReceiver");
}
sendBroadcast(intent);

结果:
都调用到onReceive()
D/TestReceiver: onReceive()

动态自定义广播

public class ReceActivity extends AppCompatActivity {
    private TestReceiver receiver;
 private IntentFilter intentFilter;
 private Button btn_b;
 @Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_rece);
 //动态注册广播
 receiver=new TestReceiver();
 intentFilter=new IntentFilter();
 intentFilter.addAction("11");
 registerReceiver(receiver,intentFilter);//注册广播
 Log.d("aa",getPackageName());
 
 //点击按钮发送广播
 btn_b=findViewById(R.id.btn_b);
 btn_b.setOnClickListener(new View.OnClickListener() {
            @Override
 public void onClick(View v) {
 
 Intent intent=new Intent("11");
 sendBroadcast(intent);//发送广播,这是一个无序的广播
 }
        });
 }
 
 @Override
protected void onDestroy() {
    super.onDestroy();
 unregisterReceiver(receiver);//解除广播注册
}
}

动态发送有序广播,设置了优先级Priority属性(-1000-1000)

//动态注册一个广播接收者TestReceiver
receiver=new TestReceiver();
intentFilter=new IntentFilter();
intentFilter.addAction("11");
intentFilter.setPriority(200);
registerReceiver(receiver,intentFilter);
//动态注册一个广播接收者TestReceiver2
receiver2=new TestReceiver2();
intentFilter=new IntentFilter();
intentFilter.addAction("11");
intentFilter.setPriority(100);
registerReceiver(receiver2,intentFilter);
public class TestReceiver2 extends BroadcastReceiver {
    private static final String TAG="TestReceiver2";
 @Override
 public void onReceive(Context context, Intent intent) {
        Log.d(TAG,"我是TestReceiver2的onReceive()");
 }

点击按钮发送广播

 Intent intent=new Intent("11");
sendOrderedBroadcast(intent,null);
结果:
D/TestReceiver: onReceive()
D/TestReceiver2: 我是TestReceiver2的onReceive()

如果我们想拦截广播
可以在onReceive()中调用abortBroadcast();即广播就不会再传递下去了

本地广播使用
业界常见的一些增加安全性的方案包括:
1.对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收
2.在广播发送和接收时,都增加相应的permission,用于权限验证
3.发送广播时,指定特定广播所在的包名,具体是通过intent.setPackage(packageName)指定,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
4.采用LocalBroadcastManager的方式
本地广播LocalBroadcastManager使用该机制发出的广播只能够在应用程序内部进行传递,并且广播接收器也只能接收来自本地应用程序发出的广播,这样所有的安全性问题都不存在了。

public class LocalReceiver  extends BroadcastReceiver {
    @Override
 public void onReceive(Context context, Intent intent) {
        Log.d("LocalReceiver","我是本地广播接收器");
 }
}
//注册本地广播
LocalBroadcastManager localBroadcastManager=LocalBroadcastManager.getInstance(this);//获取实例
LocalReceiver localReceiver=new LocalReceiver();
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction("cccc");
localBroadcastManager.registerReceiver(localReceiver,intentFilter);//注册本地广播监听

@Override
protected void onDestroy() {
    super.onDestroy();
 //unregisterReceiver(receiver);
 localBroadcastManager.unregisterReceiver(localReceiver);//注销本地广播
}

发送本地广播

 Intent intent=new Intent("cccc");
localBroadcastManager.sendBroadcast(intent);//发送本地广播
 //localBroadcastManager.sendBroadcastSync(intent);//发送本地有序广播
 
 结果:
 D/LocalReceiver: 我是本地广播接收器

广播权限有关
静态注册的方式:

自定义广播权限
<uses-permission android:name="com.ruan.rocky.permission"/>
<permission
 android:name="com.ruan.rocky.permission"
 android:label="BroadcastReceiverPermission"
 android:protectionLevel="signature">
</permission>
//permission的目录和<application>同级的位置配置使用到的权限
//protectionLevel="signature"的属性有哪些:
//normal:默认的,应用安装前,用户可以看到相应的权限,但无需用户主动授权。
//dangerous:normal安全级别控制以外的任何危险操作。需要dangerous级别权限时,Android会明确要求用户进行授权。常见的如:网络使用权限,相机使用权限及联系人信息使用权限等。
//signature:它要求权限声明应用和权限使用应用使用相同的keystore进行签名。如果使用同一keystore,则该权限由系统授予,否则系统会拒绝。并且权限授予时,不会通知用户。它常用于应用内部。**把protectionLevel声明为signature。如果别的应用使用的不是同一个签名文件,就没办法使用该权限,从而保护了自己的接收者**



<receiver android:name=".TestReceiver"
 android:enabled="true"
 android:exported="true"
 android:permission="com.ruan.rocky.permission">//添加了一个自定义的权限
 <!--//表示是否允许这个广播接收器接收本程序以外的广播-->
 <intent-filter>
 <action android:name="11"/>
 </intent-filter>
</receiver>

动态注册权限

receiver=new TestReceiver();
intentFilter=new IntentFilter();
intentFilter.addAction("11");
//intentFilter.setPriority(200);
registerReceiver(receiver,intentFilter,"com.ruan.rocky.permission",null);

在注册的时候,最关键的一点是用registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)进行注册,而不是平常用的是registerReceiver(BroadcastReceiver, IntentFilter)。相较于后者,前者在注册的时候要求了发送者必须具有的权限。如果发送者没有该权限,那么发送者发送的广播即使经过IntentFilter的过滤,也不会被receiver接收。此时如果再自定义一个权限,并且将权限的protectionLevel设置为signature,那么外部应用便无法使用该权限,也就无法触及到该receiver。
发送广播:

Intent intent=new Intent("11");
sendBroadcast(intent,"com.ruan.rocky.permission");//发送无序的权限 广播
//sendOrderedBroadcast(intent,"com.ruan.rocky.permission");发送有序的权限,广播

Android广播机制
1.同一app内部的同一组件内的消息通信(单个或多个线程之间);
2.同一app内部的不同组件之间的消息通信(单个进程);
3.同一app具有多个进程的不同组件之间的消息通信;
4.不同app之间的组件之间消息通信;
5.Android系统在特定情况下与App之间的消息通信
从实现原理上,Android中的广播采用了观察者模式,基于消息的发布/订阅事件模型,因此,从实现的角度来看,Android中的广播将广播的发送者和接收者极大程度的解耦,使得系统能方便集成,更易扩展
1.广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册;
2.广播发送者通过binder机制向AMS发送广播;
3.AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中;
4.消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法
由此看来,广播发送者和广播接收者分别属于观察者模式中的消息发布和订阅两端,AMS属于中间的处理中心。广播发送者和广播接收者的执行是异步的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到。显然,整体流程与EventBus非常类似。
在上文说列举的广播机制具体可以使用的场景中,现分析实际应用中的适用性
第一种情形:同一app内部的同一组件内的消息通信(单个或多个线程之间),实际应用中肯定是不会用到广播机制的(虽然可以用),无论是使用扩展变量作用域、基于接口的回调还是Handler-post/Handler-Message等方式,都可以直接处理此类问题,若适用广播机制,显然有些“杀鸡牛刀”的感觉,会显太“重”;
第二种情形:同一app内部的不同组件之间的消息通信(单个进程),对于此类需求,在有些教复杂的情况下单纯的依靠基于接口的回调等方式不好处理,此时可以直接使用EventBus等,相对而言,EventBus由于是针对统一进程,用于处理此类需求非常适合,且轻松解耦。
第三、四、五情形:由于涉及不同进程间的消息通信,此时根据实际业务使用广播机制会显得非常适宜
结尾:拨开云雾见天日 守得云开见月明

点赞
收藏
评论区
推荐文章
刘望舒 刘望舒
4年前
Android深入四大组件(四)广播的注册、发送和接收过程
Android框架层Android深入四大组件categories:Android框架层本文首发于微信公众号「刘望舒」前言我们接着来学习Android四大组件中的BroadcastReceiver,广播主要就是分为注册、接收和发送过程。1.广播的注册过程BroadcastReceiver的注册分为两种,分别是静态注册和动态注册,静态注册在应用安装时由
Stella981 Stella981
3年前
Android通过adb shell am broadcast发送广播 参数说明
通过命令行执行adbshellambroadcast发送广播通知。adbshellambroadcast后面的参数有:<INTENTspecificationsincludetheseflagsandarguments:\a<ACTION\\d<DATA\_URI\\t<MIME\_TY
Stella981 Stella981
3年前
MESI协议:Cache 一致性协议
总线嗅探机制把所有的读写请求都通过总线(Bus)广播给所有的CPU核心,然后让各个核心去“嗅探”这些请求,再根据本地的情况进行响应。MESI协议基于写失效(WriteInvalidate)如果某个CPU核心写入一个CacheLine,则会广播一个失效请求告诉所有其他的
Stella981 Stella981
3年前
Android拨号键盘增加魔力爱心数字
目的:在拨号键盘输入\\5201314\\启动自定义的某个(隐藏)应用,大胆说出你的爱!1\.修改Android源码dialer相关代码,自定义android\_secret\_code并在afterTextchanged函数中发出处理广播:vim packages/apps/Dialer/src/
Stella981 Stella981
3年前
Android 蓝牙相关的广播
Android蓝牙相关的广播监听蓝牙相关的广播并获得相关的信息,蓝牙相关的广播主要集中在BluetoothAdapter和BluetoothDevice类中,可以通过在AndroidManifest.xml中注册静态广播,也可以通过在代码中注册动态广播,两种形式的广播都可以监听到需要相关的权限<usespermissi
Stella981 Stella981
3年前
Android BLE 总结-源码篇(BluetoothLeAdvertiser)
在做AndroidBLE的应用程序时,我们发出广播数据是调用BluetoothLeAdvertiser的startAdvertising方法,如下所示:\java\viewplain(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fblog.csdn.net%2Fan
Stella981 Stella981
3年前
Kurento Tree 简介
KurentoTreeDescriptionKurentoTree是一个允许开发人员通过互联网构建视频广播网络应用程序的项目。它使用WebRtc技术和KurentoMediaServer开发。KurentoTree项目由服务器和两个客户端(Java客户端和JavaScript客户端)组成。还有两个演示应用程序可用,利用这个项目
Wesley13 Wesley13
3年前
DHCP报文交互流程
1.发现阶段,即DHCP客户机寻找DHCP服务器的阶段(DHCPdiscover)DHCP客户机以广播方式(因为DHCP服务器的IP地址对于客户机来说是未知的)发送DHCPdiscover发现信息来寻找DHCP服务器,即向地址255.255.255.255发送特定的广播信息。网络上每一台安装了TCP/IP协议的主机都会接收
Stella981 Stella981
3年前
Android 监听wifi广播的两种方式
packagecom.comit.broadcast;importcom.comit.util.AppHpler;importandroid.content.BroadcastReceiver;importandroid.content.Context;importandroid.
Stella981 Stella981
3年前
Netty实战十三之使用UDP广播事件
1、UDP的基础知识我们将会把重点放在一个无连接协议即用户数据报协议(UDP)上,它通常用在性能至关重要并且能够容忍一定的数据报丢失的情况下。面向连接的传输(如TCP)管理了两个网络端点之间的连接的建立,在连接的生命周期内的有序和可靠的消息传输,以及最后,连接的有序终止。相比之下,在类似于UDP这样的无连接协议中,并没有持久化连接这样的概念,并且每个
Stella981 Stella981
3年前
DHCP 广播 客户机自动获取ip
DHCP:DynamicHostConfigurationProtocol前身是bootplease:租约续租:租约到一半的时候就要申请续租广播:第一个响应的服务器广播申请,广播响应广播回复确认  IP:MASK:GATEWAY:DNS: ClientDHCPDI