Android Broadcast 用法简单讨论

前言

在 Android 中,广播(Broadcast) 是用来在组件之间传递数据的一种机制。这些组件可以在同一进程中,也可以在不同进程中。
Android中广播分为两个角色:广播发送者/广播接收者

作用

监听APP/系统发出的广播消息,并做出响应。

原理

这里简单说下广播的原理。
1、采用的原型
Android 中的广播采用的是设计模式中的观察者模式:基于消息的发布/订阅时间模型。Android 将广播的发送者和接收者进行解耦,使得系统更方便集成,更容易扩展。
2、原理(图片去自网络,如果侵权请联系本人删除)
示意图

BroadcastReceiver

BroadcastReceiver是对发送出来的符合过滤条件的Broadcast进行接收和响应的组件,是 Android 四大组件之一。
首先要将需要发送的消息和用于过滤接收者的信息(Action、Category)装入到一个 Intent 对象中,然后通过调用 Context.sendBroadcast或者 Context.sendOrderBroadcast 把 Intent 通过广播形式发送出去。广播发送出去后 AMS 会检查已注册的 BroadcastReceiver 的 IntentFilter 是否满足当前的Intent 的过滤条件,如果满足,则调用 BroadcastReceiver 的 onReceive 方法。默认情况下 BroadcastReceiver是运行在 UI 线程中,所以onReceive 方法中不能执行耗时的操作,否则会 ANR。一旦 onReceive 方法执行完毕,该 Receiver 的生命周期也就结束了。
Android 中的广播分为两种:
标准广播
标准广播是一种完全异步执行的广播,在广播发出去之后,所有广播接收者会同时接收到广播,没有顺序而言,效率比较高,且不会被截断。
有序广播
有序广播是一种同步执行的广播,在广播发出的同一时刻只有一个广播接收者可以收到广播,优先级高的接收者先接收到广播,等该接收者的 onReceive方法执行完毕之后,广播才会继续往优先级低的广播接收者传递。如果当前的广播接收者截断了此广播,那么优先级低的广播接收者就无法接收到广播了。另外,优先级高的广播接收者可以修改当前广播的内容,后面接收者接收到的广播就是修改过后的内容了。

注册广播接收者

1、静态注册

<receiver 
	//声明当前系统能否实例化这个广播接收器,true 可以实例化,false 不可以实例化,默认为 true
    android:enabled=["true" | "false"]
    //这个属性声明该接收器能不能接收应用外的广播,如果设置为 true 则可以,如果false,则该接收者只能接收那些相同应用程序组      件或带有相同用户 ID的应用程序所发出的广播。
    //这个属性的默认值依赖它所包含的的 Intent过滤器。如果没有设置过滤器,那么就只能通过指定了类名的 Intent来调用了,这就意味着该接收器只能在应用程序内部使用,因为外部应用基本上不会知道该类名,所以该默认值就是 false。如果设置了至少一个过滤器,就意味着它可以接收系统或者外部程序的Intent 对象,因此,默认值是 true。
    //当然这个属性不是唯一限制是否接收外部应用调用的方法,还可以通过 permisson 控制。
    android:exported=["true" | "false"]
    //定义了一个代表广播接收器的图标
    android:icon="drawable resource"
    //给接收器设定一个用户可读懂的文本标签
    android:label="string resource"
    //这个属性是设置BroadcastReceiver的实现类的类名,通常是类的全名(包括包名),也可以是简写(.xxxx)。如果是简写,那么系统会自动把 manifest 中设置的 package 属性所设定的包名加到这个简写上去。这个类必须是 BroadcastReceiver 的子类。
    android:name=".MyBroadcastReceiver"
    //这个属性设定把消息发送给该接收器的广播所需要的权限。
    android:permission="string"
    //设定接收器运行所在的进程。通常组件都是运行在该应用程序创建的默认的进程中运行,这个进程的名称就是应用程序的包名。
    //<application>标签的 process 可以设置成和所有组件都不同的默认名,但是这些组件可以通过自己设置process 来覆盖这个默认值,这样就可以将一个应用跨多进程运行。
    //如果这个属性以冒号开头,那么这个进程是应用程序私有的,当他被需要时系统会创建一个新的进程,并且该 接收器就运行在该进程中。如果不是以小写字符开头的,那么该接收器就运行在这个名称命名的全局的进程中,并且他提供使其工作的权限,这样就可以在不同应用中共享一个工作进程,减少内存消耗。
    android:process="string" >
    //用于指定接收器的过滤器,也就是接收哪种类型的广播
   <intent-filter>
      <action android:name="action name" />
   </intent-filter>
</receiver>
1)、标准广播静态注册
首先定义一个 BroadcastReceiver
public class NormalReceiver extends BroadcastReceiver {

    private static String TAG = "NormalReceiver";

    public NormalReceiver() {
        Log.e(TAG, "NormalReceiver Constructor");
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String msg = intent.getStringExtra("MSG");
        Log.e(TAG, msg);
    }
}

再在 mainfest 中进行静态注册

        <receiver android:name=".broadcast.NormalReceiver">
            <intent-filter>
                <action android:name="com.normal.action" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </receiver>

接着进行调用了

 Intent intent = new Intent();
 intent.setAction(NORMAL_ACTION);
 intent.putExtra("MSG", "value1");
 sendBroadcast(intent);

接下来就是满怀期待的等待触发了。
在这里插入图片描述
但是我点了无数次发现并没有打印出 log,也就是说接收器没有收到广播,那这是怎么回事呢。这个问题曾经也困扰过我一段时间,最后发现文档中有这样一段
https://developer.android.com/guide/components/broadcasts.html#receiving_broadcasts

Beginning with Android 8.0 (API level 26), the system imposes additional restrictions on manifest-declared receivers.
If your app targets API level 26 or higher, you cannot use the manifest to declare a receiver for most implicit broadcasts (broadcasts that do not target your app specifically).
也就是说
在Android 8.0 及以上 在xml中注册的广播,在接收的时候收到了额外的限制,如果你的app目标等级是26及以上,将无法接收到xml注册的广播
在这里插入图片描述
到 gradle 里面一看,果然 targetSdkVersion 是27,我现在改成25看下能不能触发。
在这里插入图片描述
果然触发了 onReceive 方法。
那么该怎么解决呢,主要有三种解决方式:
1、显示调用BroadcastReceiver,但是这样的话 intent-filter 属性就没有用了。

Intent intent = new Intent(this, NormalReceiver.class);
intent.putExtra("MSG", "value1");
sendBroadcast(intent);

2、动态注册广播(后面会介绍)
3、将 gradle 中的 targetSdkVersion 设置小于26
我们接下来就先将targetSdkVersion设置成25方便介绍静态注册。

2、有序广播
首先我们定义三个有序广播 OrderReceiver1、OrderReceiver2和OrderReceiver3,优先级依次降低,在 OrderReceiver1中我们对数据进行修改,在 OrderReceiver2中对广播进行拦截,来看下打印的结果。

OrderReceiver1关键代码

public class OrderReceiver1 extends BroadcastReceiver {

    private static final String TAG = "OrderReceiver1";

    @Override
    public void onReceive(Context context, Intent intent) {
        //取出intent 中的数据
        String msg = intent.getStringExtra("MSG");
        Log.e(TAG, msg);
        //对数据进行修改传递给下面优先级低的receiver
        Bundle bundle = new Bundle();
        bundle.putString("MSG", msg + " OrderReceiver1 changed value");
        setResultExtras(bundle);
    }
}

OrderReceiver2关键代码

public class OrderReceiver2 extends BroadcastReceiver {

    private static final String TAG = "OrderReceiver2";

    @Override
    public void onReceive(Context context, Intent intent) {
       //原始数据
        String msg = intent.getStringExtra("MSG");
        Log.e(TAG, msg);

        //上个 receiver 修改的数据
        String msg2 = getResultExtras(true).getString("MSG");
        Log.e(TAG, msg2);
        //拦截广播
        abortBroadcast();

    }
}

OrderReceiver3关键代码

public class OrderReceiver3 extends BroadcastReceiver {

    private static final String TAG = "OrderReceiver3";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e(TAG, "test value");
    }
}

manifest中进行注册

        <receiver android:name=".broadcast.OrderReceiver1" >
        //设置优先级,数字越大优先级越高
            <intent-filter android:priority="100">
                <action android:name="broadcast.OrderReceiver"/>
            </intent-filter>
        </receiver>

        <receiver android:name=".broadcast.OrderReceiver2" >
        //设置优先级
            <intent-filter android:priority="99">
                <action android:name="broadcast.OrderReceiver"/>
            </intent-filter>
        </receiver>

        <receiver android:name=".broadcast.OrderReceiver3" >
        //设置优先级
            <intent-filter android:priority="98">
                <action android:name="broadcast.OrderReceiver"/>
            </intent-filter>
        </receiver>

发送广播

Intent intent = new Intent();
intent.setAction("broadcast.OrderReceiver");
intent.putExtra("MSG", "value1");
//调用sendOrderedBroadcast,后面一个参数值在自定义权限时使用
sendOrderedBroadcast(intent, null);

我们看到最终打印的结果为
在这里插入图片描述
说明有序广播可以对广播内容进行修改(或者用补充更为恰当)和拦截。

动态注册

动态注册是在代码中定义并设置好IntentFilter 对象,然后在需要注册的地方调用Context.registerReceiver(),在需要解除注册的时候调用Context.unregisterReceiver()f 方法进行解除。而不需要在manifest 中去注册。

public class DynamicBroadcastActivity extends AppCompatActivity {

    private BroadcastReceiver normalReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dynamic_broadcast);
        registerReceiver();
        findViewById(R.id.btn_normal_dynamic).setOnClickListener(view -> {
            Intent intent = new Intent("Dynamic_Normal_Receiver");
            intent.putExtra("MSG", "value");
            sendBroadcast(intent);
        });
    }

    private void registerReceiver() {
        normalReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String msg = intent.getStringExtra("MSG");
                Log.e("Dynamic Receiver", msg);
            }
        };
        IntentFilter intentFilter = new IntentFilter("Dynamic_Normal_Receiver");
        registerReceiver(normalReceiver, intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(normalReceiver);
    }
}

打印结果
在这里插入图片描述
也可以通过动态注册有序广播,在注册的时候intentFilter.setPriority()方法设置优先级就行了,这里就不单独介绍了。

本地广播(Local Broadcast)

之前我们说到 exported 属性在至少有一个intent-filter的情况下默认是 ture。也就是说这些广播都是全局的系统广播,那么就可能导致以下两个问题。
1、如果其它应用发出的广播和当前的应用的 intent-filter相匹配,那么当前应用就可以接收此广播,有可能会导致当前应用不断的收到其它应用发出的广播导致应用出现异常。
2、如果其它应用注册的Receiver的 intent-filter 与当前应用发出的广播相匹配,就可能会获取到广播的信息,从而造成安全性和效率问题。
那么就可以使用本地广播(或者叫应用内广播),使用这个机制发出的广播只能在应用内进行传递。本地广播可以理解成局部广播,广播的发送者和接收者都是同一个应用内的。
具体做法几种:
1、注册广播时将 exported属性设置成 false,这样就只能接收本应用内的广播。
2、在发送和接收广播时增加相应的permission,用于权限验证。后面会介绍。
3、发送广播时时指定Receiver 所在的包名intent.setPackage(packageName)。例如上面的发送广播的代码改成这样

Intent intent = new Intent("Dynamic_Normal_Receiver");
intent.setPackage("aaa.bbb");//设置一个与广播所在包名不一致的包名
intent.putExtra("MSG", "value");
sendBroadcast(intent);

会发现之前的 Receiver 无法接收到广播,即使 intent-filter 匹配。改成manifest 中 package 一致的包名就可以正常触发了。

4、使用封装好的LocalBroadcastManager来对广播进行管理
使用LocalBroadcastManager 发送的广播只能通过 LocalBroadcastManager 进行动态注册,不能静态注册。
下面我们简单来通过一个例子来简单看下用法。

首先我们定义一个 Receiver:

public class LocalBroadcastReceiver extends BroadcastReceiver {

    private static final String TAG = "LocalBroadcastReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        String msg = intent.getStringExtra("MSG");
        Log.e(TAG, msg);
    }
}

再建个 Activity

public class LocalBroadcastActivity extends AppCompatActivity {

    private static  final String ACTION = "com.test.local.broadcast";

    LocalBroadcastReceiver localBroadcastReceiver;
    LocalBroadcastManager localBroadcastManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_local_broadcast);

        //实例化LocalBroadcastManager的实例
        localBroadcastManager = LocalBroadcastManager.getInstance(this);

        registerLocalBroadcastReceiver();

        findViewById(R.id.btn_local).setOnClickListener(view -> {
            triggerBroadcast();
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unRegisterLocalBroadcastReceiver();
    }

    private void triggerBroadcast() {
        Intent intent = new Intent(ACTION);
        intent.putExtra("MSG", "value");
        localBroadcastManager.sendBroadcast(intent);
    }

    private void registerLocalBroadcastReceiver() {
        localBroadcastReceiver = new LocalBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter(ACTION);
        localBroadcastManager.registerReceiver(localBroadcastReceiver, intentFilter);
    }

    private void unRegisterLocalBroadcastReceiver() {
        if (localBroadcastReceiver != null && localBroadcastManager != null) {
            localBroadcastManager.unregisterReceiver(localBroadcastReceiver);
        }
    }
}

代码比较简单,废话就不多说了。

使用permission

前面我们说过可以通过 permission 来控制广播接收器是否响应外部广播调用。这节我们就来具体说下这个用法。
首先我们定义一个 BroadcastReceiver:

public class PermissionReceiver extends BroadcastReceiver {

    private static final String TAG = "PermissionReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        String msg = intent.getStringExtra("MSG");
        Log.e(TAG, msg);
    }
}

然后自定义一个权限:

	//自定义权限
    <permission android:name="com.permission.example.receiver"
        android:protectionLevel="normal"/>
    //申请使用权限
    <uses-permission android:name="com.permission.example.receiver" />

动态注册(静态注册也可以)刚才的 Receiver:

        <receiver android:name=".broadcast.PermissionReceiver"
            android:exported="true"
            //加上权限控制
            android:permission="com.permission.example.receiver">
            <intent-filter>
                <action android:name="com.permission.example.receiver"/>
            </intent-filter>
        </receiver>

接下来我们新建一个 APP 工程:
然后申请刚才的权限

    <uses-permission android:name="com.permission.example.receiver"/>

接下来发送广播:

Intent intent = new Intent("com.permission.example.receiver");
intent.putExtra("MSG", "New APP Value");
//加上权限
sendBroadcast(intent, "com.permission.example.receiver");

最后我们会在日志中看到打印的结果
在这里插入图片描述
permission 控制广播就简单介绍到者,关于 permission 的具体用法,以后有时间会单开一篇进行探讨。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值