Broadcast Receiver 基础

Broadcast Receiver 基础

Broadcast Receiver 是四大组件之一,可以用来接受系统或者app(可以app 内部组件,也可以是跨 app)的各种事件,当然这些事件必须通过 sendBroadcast()方法发送出来,Broadcast Receiver 才可以接受到。

广播可以作为组件之间,跨进程,跨应用之间的通信,更多的时候,是配合系统的内置 Broadcast 对我们的 APP 需要的手机转态进行管理,例如 wifi 状态。

概述

注册方式

你可以通过两种方式注册一个 Broadcast Receiver,一种是在AndroidMainFest 中注册,称为静态注册,注册方式如下:

<receiver android:name="br.NetWorkChangeToastReceiver"></receiver>

另一种是动态注册,通过代码注册,调用 Context.registerReceiver() 方法去注册

private void registerBroadcastReceiver() {
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
    registerReceiver(new NetWorkChangeToastReceiver(), intentFilter);
}

两种方式存在一定的区别,通过代码动态注册的,具有组件级别效果,也就是说,在 Broadcast 未调用 onReceive()之前,如果通过unRegisterReceive()方法被调用,则该 Receiver 被销毁;

注意:

  1. 在 onReceive() 是执行在主线程中的,所以不能在 onReceive()里面做耗时操作,否则会引起 anr。

  2. 在同等情况下,动态注册的广播接收者会比静态注册的广播接收者优先级高,会先收到 broadcast

生命周期解释

通过 AndroidMainFest 文件注册的 Receiver,在 APP 安装的时候,会通过 package manage 全部注册;从这个角度来说,这些 Receiver 和 APP 是相对独立的,即便你的 APP 没有启动,这些 Receiver 也可以在 onReeiver() 接受特定的事件。

通过 Context 注册的 Broadcast Receiver 的生命周期和注册它的 Context 是息息相关的,在Context 没有被销毁的情况下,这个 Receiver 的onReceive()方法才会被调用 。从另外一点来说,如果你是通过 Activity 去 register(),你需要在退出这个 Activity 之前 unRegister() 这个 Activity(),同时注意调用方法的时刻,如果在 onCreate() register,则需要在 onDestory() 的时候 unRegister():如果是 onResume() 的时候 register(),则需要在 onPasue() 的实现 unRegister();避免多次调用 register() 和 unRegister() 方法。

下面是一个例子,进行跨 APP 发送和接受广播

首先在接收端 APP 实现自定义的 Broadcast Receiver,自定义的 BroadcastReceiver 类文件如下,这里接受对应 Action 的Broadcast Receiver 然后Toast 接收到的内容:

public class AppCrossBroadcast extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (TextUtils.equals(action, "com.fx.app.cross.br1")) {
            String data = intent.getStringExtra("data");
            if (data != null) {
                Toast.makeText(context, "收到" + data, Toast.LENGTH_SHORT).show();
            }
        }
    }
}

然后在接收端静态注册这个 Broadcast Receiver,在 androdmainfest 文件中:

<receiver
    android:name="common.AppCrossBroadcast"
    android:exported="true">
    <intent-filter>
        <action android:name="com.fx.app.cross.br1"></action>
    </intent-filter>
</receiver>

这里 android:exported=”true” 这个属性设置为 true 表示,这个Application 组件是否可以被其它应用访问(调用);

基础用法及语义

有序广播和无序(标准)广播

通过 sendBroadcast(Intent ) 方法发送的广播,称为标准无序广播,相对下面介绍的一种广播,效率要高之。但是意味着所有匹配 Action 的 Reveiver 都可以接收。

我们可以通过 sendOrderBroadcast(Intent,String),发送有序广播,这种广播根据 Receiver 的优先级,被 Receiver 顺序接收和处理,这里意味着,你可以拦截 Broadcast(通过调用),你可以在 Receiver 之间传递数据。

下面是一个例子,首先定义 Broadcast Receiver:

public class OrderBr1 extends BroadcastReceiver {
    public final String TAG = this.getClass().getSimpleName();

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (TextUtils.equals(action, "com.fx.order")) {
            Toast.makeText(context, TAG + "收到广播", Toast.LENGTH_SHORT).show();
        }
    }
}

然后依次复制,生成 OrderBr2,OrderBr3。代码都是一样的,不过在 OdderBr2 里面,进行广播的拦截,代码如下:

public class OrderBr2 extends BroadcastReceiver {
    public final String TAG = this.getClass().getSimpleName();

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (TextUtils.equals(action, "com.fx.order")) {
            Toast.makeText(context, TAG + "收到广播", Toast.LENGTH_SHORT).show();
        }
        abortBroadcast();// 拦截广播
    }
}

然后在 AndroidMainFest 中 receiver 标签的子标签 intent-filter 标签里面,修改 android:priority 属性。

<receiver android:name="br.OrderBr1">
    <intent-filter android:priority="100">
        <action android:name="com.fx.order"></action>
    </intent-filter>
</receiver>
<receiver android:name="br.OrderBr2">
    <intent-filter android:priority="99">
        <action android:name="com.fx.order"></action>
    </intent-filter>
</receiver>
<receiver android:name="br.OrderBr3">
    <intent-filter android:priority="98">
        <action android:name="com.fx.order"></action>
    </intent-filter>
</receiver>

最后,我们在我们的 Activity 里面发送 Broadcast,代码如下:

private void sendOrderBr() {
    Intent intent = new Intent();
    intent.setAction("com.fx.order");
    sendOrderedBroadcast(intent, null);
}

我们可以看到,OrderBr3 是没有接收到广播的。

使用 permission

sendOrderedBroadcast(intent, null)方法里面,第二个参数是一个 permission,这很好理解,我们的应用在使用某些功能或者访问硬件的时候,需要一些 uses-permission 例如下面这个是访问网络状态的 uses-permission。

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>

同样 BroadcastReceiver 也可以对 Receiver 所以的应用 进行 permission 过滤,这里的过滤包括发送方和接收方,对发送方过滤,意味着,如果你的应用没有在 androidmanifest 中声明对应的 例如我们发送一条这样的广播,

sendOrderedBroadcast(intent, Manifest.permission.SEND_SMS);

那么只有在 AndroidManiFest 文件中,声明了发送信息的权限,你的 app 才能准确的发送这条广播,同样的道理,想要接收这个 Broadcast 的 receiver 也需要

这里可能比较难理解,我先讲解一下,我们调用这个方法,最后会调用 ContextImpl 类里面的 sendOrderedBroadcast()方法:

@Override
public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
    warnIfCallingFromSystemProcess();
    String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
    String[] receiverPermissions = receiverPermission == null ? null
            : new String[] {receiverPermission};
    try {
        intent.prepareToLeaveProcess(this);
        ActivityManagerNative.getDefault().broadcastIntent(
                mMainThread.getApplicationThread(), intent, resolvedType, null,
                Activity.RESULT_OK, null, null, receiverPermissions, AppOpsManager.OP_NONE,
                null, true, false, getUserId());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

之后会把需要的 permission 写进 Parcel 里面,然后发送出去;那么接收 Broadcast 的代码呢?接收和发送 Broadcast 都是由一个叫做 ActivityManagerService 的类进行代理的,你通过 Activity 或者 Service 发出出去的 Broadcast 都有 ActivityManagerService 进行接收和分发,然后 ActivityManagerService 根据注册的 permission 和优先级,分发 Broadcast。

tip:你也可在 receiver 标签里面,设置 android:permission 属性,对

Local Broadcast

本地广播,在 app 内通信; google 建议在不需要 IPC(跨进程通信)的前提下,使用 Local Broadcast 效率比普通Broadcast 要高。

android 可以通过 support 包轻松简单的发送 local broadcast,先来看下 LocalBroadcastManager 类的介绍和用法:

LocalBroadcastManager 在v4 包里面,根据 api 的介绍“这是一个帮助类,帮助你在你的进程内注册和发送 broadcast”,然后使用 Local Broadcast 有以下几个好处:

  1. 可以避免广播被其它app 接受到,避免信息泄漏
  2. 可以避免自己的 Receiver 接收到破坏性的 Broadcast
  3. 不用进行 ipc,效率远远高于普通的 global Broadcast。

这个类,注册和处理相关的 Broadcast,它和 ActivityManagerService 是独立的,也就是你通过 LocalBroadcastManager 发送 Broadcast 和注册 receiver 是不会通过 ActivityManagerService 的,下面是这个类处理 broadcast 的流程图:

注册的时候, 其实就是将 Receiver 加入到两个HashMap 中,其中一个 mReceivers,记录了某个Broadcast 对应的所有 IntentFilter,这个 HashMap 主要作为锁对象,确保所有的 Receiver 在线程中同步。另一个 HashMap 才是起数据存储的作用,把 action 作为 key,IntentFilter 和 Receiver 值生成的复合对象作为 value 值。

然后发送广播的时候,大概流程如下:

发送广播的时候,会根据Intent 的action 去查找是否有对应的 Receiver,然后匹配 action,scheme,category 等,如果匹配成功,则将对应的 Receiver 加入 一个 ArrayList,同时 Handler 发送消息,通知处理发送的广播,通过遍历 ArrayList,执行符合条件的 Receiver 的onReceive()方法。

具体详细可以参考源码。

goAsync()方法

我们知道,一个 Receiver 如果执行完 onReceive() 方法,则这个组件会被系统回收。如果你在 onReceive()方法里面执行异步操作,很可能因为这个 Receiver 已经被收回了,导致业务出错甚至app直接崩溃。android 在 Broadcast 类里面 提供了 goAsync()方法,提供我们在其它子线程进行耗时操作,避免引起 anr 问题。

public class AsyncBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final PendingResult pendingResult = goAsync();
        Thread thread = new Thread(new SleepThread(1000 * 20) {
            @Override
            public void run() {
                super.run();
                Log.i("Async", " ->finish");
                pendingResult.finish();
            }
        });
        thread.start();
        Log.i("Async", " ->onReceive");
    }
}

这样需要进行耗时操作就不会引起 anr 问题了。

Sticky Broadcast- 粘性广播

普通发送的广播都是非粘性的,根据 google 官方 api 的解释,所谓粘性广播,定义如下:

Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the Intent you are sending stays around after the broadcast is complete,so that others can quickly retrieve that data through the return value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}.  In all other ways, this behaves the same as{@link #sendBroadcast(Intent)}.

简单来说,这条广播会一直停留在系统里面,所以只要有新的 receiver 注册,马上便可以收到这条广播。不过,你可以通过 removeStickyBroadcast() 移除这条广播。发送粘性广播,需要特定的 user-permission,如下:

<uses-permission android:name="android.permission.BROADCAST_STICKY"></uses-permission>

但是在 api 21 以后,也就是 android 5.0 以后, 出于 安全原因,sticky broadcast 被废弃,不再使用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值