Android四大组件之—BroadcastReceiver

本文章整理自Google官方文档,建议对广播机制有所了解后阅读

系统广播

系统广播是当一些系统事件发生时由系统发出的广播。

接收广播

程序可以通过两种方式来接收广播:在Manifest文件中声明(注册)的接收器和在Context中注册的接收器。

在Manifest文件中声明广播接收器(静态注册)

使用方法:

  1. 在Manifest文件中声明广播接收器

    <receiver android:name=".MyBroadcastReceiver"  android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
    </intent-filter>
    </receiver>

    其中intent-filter标签用于指定该广播接收器要接收什么样的广播。

  2. 继承BroadcastReceiver并重写onReceive(Context, Intent)方法

    public class MyBroadcastReceiver extends BroadcastReceiver {
       private static final String TAG = "MyBroadcastReceiver";
       @Override
       public void onReceive(Context context, Intent intent) {
           //广播信息封装在Intent参数中
           StringBuilder sb = new StringBuilder();
           sb.append("Action: " + intent.getAction() + "\n");
           sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
           String log = sb.toString();
           Log.d(TAG, log);
           Toast.makeText(context, log, Toast.LENGTH_LONG).show();
       }
    }

    通过此种方式注册的广播在程序被安装的时候即被system package manager所注册。这种情况下就为App提供了另一种『Entry Point』,因为即使程序不在运行状态,当接到该广播接收器所指定的广播时,系统也可以将其所在的程序唤醒,并将该广播发送至该接收器。

    系统会为处理每个广播的广播接收器都new一个实例出来,当实例的onReceive()方法返回后,系统则认为该实例不再需要了。

    对上面那一段我的理解是,当一个广播接收器连续收到两条广播的时候,并不存在一个类似于队列的东西,而是来多少广播,系统就创建多少个广播接收器,即一个广播对应一个广播接收器。我想也正是因此,系统需要在接收器的onReceive()方法返回之后将其列为可回收对象。

在Context中注册广播接收器(动态注册)

使用方法:

  1. 创建一个广播实例:

    BroadcastReceiver br = new MyBroadcastReceiver();
  2. 创建一个IntentFilter(作用类似于Manifest中的intent-filter标签),通过调用registerReceiver()方法注册之

    IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
    intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    this.registerReceiver(br, filter);

    注意:

    1. 如果要注册本地广播,请使用LocalBroadcastManager.registerReceiver()。
    2. 上面代码块中的registerReceiver()是Context的方法。

在Context中注册的Receiver只有在注册它们的Context存活的时候才会收到广播。比如说,在Activity中注册的广播,只有当该Activity存活的时候才能收到广播,而使用Application实例注册的广播则是在程序存活的时候才能收到广播。

  1. 取消注册

    对于动态注册的广播,一般要确保当不需要再接受广播的时候取消注册之。调用unregisterReceiver()方法即可。不要在onSaveInstanceState(Bundle)方法中取消注册,因为这个方法在正常的生命周期过程中不一定被调用。

广播对进程的影响

广播接收器的状态会影响其所在进程的状态,进而影响其被系统杀掉的可能性。比如说,当一个进程正在处理一个广播时,即正在运行其onReceive()方法,那么该进程会被认为是一个前台进程。这种情况下,除非是内存压力极大,否则系统不会杀掉它。

然而,一旦onReceive()方法返回,那么该Receiver就认为不再活跃,此时该Receiver所在的进程的活跃度则取决于该进程中的其他组件。如果一个进程只有一个静态注册的广播接收器呢?那当onReceive()方法返回之后,该进程则被划为低优先级进程,随时可能被系统杀掉来回收资源。

基于以上原因,不要再广播接收器中开启耗时的后台线程,因为当onReceive()方法返回后,该进程可能被杀掉,那么该进程中的线程也会被杀掉。为了避免这种情况,可以使用goAsync()或者JobService。这样的话相当于通知系统该进程还在进行活跃工作。

一个使用goAsync()的例子:

public class MyBroadcastReceiver extends BroadcastReceiver {
    private static final String TAG = "MyBroadcastReceiver";

    @Override
    public void onReceive(final Context context, final Intent intent) {
        final PendingResult pendingResult = goAsync();
        AsyncTask<String, Integer, String> asyncTask = new AsyncTask<String, Integer, String>() {
            @Override
            protected String doInBackground(String... params) {
                StringBuilder sb = new StringBuilder();
                sb.append("Action: " + intent.getAction() + "\n");
                sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
                Log.d(TAG, log);
                // Must call finish() so the BroadcastReceiver can be recycled.
                pendingResult.finish();
                return data;
            }
        };
        asyncTask.execute();
    }
}

由于广播接收器是运行在主线程中的,如果onReceive()中的业务逻辑大于16ms(这将导致UI有一帧的卡顿),使用goAsync()方法是个不错的选择。然而即使是使用这种方式,系统也希望该操作能在10s内完成。

goAsync()使得应用程序允许该广播在onReceive()方法返回后还存活一段时间。相当于告诉系统『再给我10秒钟~』

发送广播

Android提供了三种方式来发送广播。

  1. 使用sendOrderedBroadcast()发送有序广播

    这种方式发送的广播会按照广播接收器的优先级依次派发,收到广播的接受者可以生成一个结果传给后面的广播,并且可以选择是否abort广播的传递。对于优先级相同的多个广播,其接收顺序将随机指定。

  2. 使用sendBroadcast()发送普通广播

    这种方式更效率,因为所有的接受者将会几乎同时接收到(顺序不可指定)。但是广播接收器之间不能就该广播传递数据。

  3. 使用LocalBroadcastManager.sendBroadcast()发送本地广播。

    只有和发送者在同一个App中的接受器才能接收到这种广播,由于没有IPC过程,这种广播更加高效同时更加安全。

下面的代码段展示了如何使用Intent发送一个广播:

Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");//action名必须加上包名
intent.putExtra("data","Notice me senpai!");//将数据封装在Intent中
intent.setPackage("package_name");//可以使用该方法指定广播只发送到某些包名的App中,如自家全家桶
sendBroadcast(intent);

给广播加上权限

通过这种机制,可以指定只有获得了某些权限的App的Receiver才能接收到某条广播。

发送带权限的广播(发送方带限制,接收方去满足)

其实上面的sendBroadcast(Intent, String)方法有第二个参数可以指定,即权限字符串,使用方式如下:

sendBroadcast(new Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS);

而要接收这条广播的话,接收器所在的App必须申请了如下权限:

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

除了给广播加上系统的那些权限,甚至还可以使用标签自定义一个权限。

自定义的权限在app安装的时候即被注册。只有定义了自定义权限的app被安装了,使用该自定义权限的app才能使用之。

指定发送者的权限(接收方带限制,发送方去满足)

如果在注册的时候给Receiver指定了权限,不管是在registerReceiver()方法中还是在Manifest中的标签中,那么只有申请了该权限的发送方发送的广播才能被改Receiver接受。

例如:

<receiver android:name=".MyBroadcastReceiver"
          android:permission="android.permission.SEND_SMS">
    <intent-filter>
        <action android:name="android.intent.action.AIRPLANE_MODE"/>
    </intent-filter>
</receiver>

或者

IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null );

那么,为了发送广播给以上的两种广播接收器,发送方必须申请以下权限:

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

关于带权限的广播,总结一句话就是:谁指定了权限,谁就有发言权:你想收我这个广播(或你想发广播给我),你就得和我有一样的权限。霸道!

广播的安全问题

  1. 如果不需要将广播发送给自身app以外的其他组件,有限使用本地广播。本地广播还可以作为一个通用的『发布/订阅』模式的事件总线在app中使用,且不会增加系统开销。(类EventBus?)
  2. 如果大量的app都通过静态注册了相同的广播接收器,那么会导致性能问题和用户体验问题。基于此,推荐使用动态注册,并且Android还强制某些Action只能通过动态注册来接收,比如 CONNECTIVITY_ACTION
  3. 不要使用隐式Intent发送包含敏感信息的广播,因为任何注册了该广播的接收器都会收到。这里提供三种方式来稍微控制下:
    • 给该条广播加一个权限。
    • 在4.0或更高版本中,使用setPackage()方法指定包名。
    • 使用本地广播。
  4. 当注册了一个广播接收器之后,任何app都可能向其发送一个恶意广播。这里同样提供三种方式来稍微控制下:
    • 注册的时候给该接收器指定一个权限。
    • 对静态注册的广播,在标签中指定android:exported = false则可以让该接收器不接受app以外的广播。
    • 使用本地广播。
  5. 由于onReceive()方法运行在主线程中,那么它应该快速被执行完后返回。如果需要执行耗时操作,在开启线程或开启后台服务的时候要小心,因为系统可能在onReceive()方法结束之后干掉整个进程。具体的请查看上面的广播对进程的影响章节。
  6. 不要在广播接收器中启动Activity,因为用户体验不好。尤其是当存在多个接收器的时候。考虑下通知吧~
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值