【Android】Broadcasts详解

Android应用程序可以发送广播,也可以接收Android系统或者其它应用发出的广播,这跟发布-订阅设计模式很相似。当一些受到关心的事件发生后,广播会被自动发送。举例来说,当一些系统事件(如开机,设备开始充电等)发生,Android系统会发送广播。应用程序也可以发送自定义的广播,比如当某个应用关注的事件(如数据更新等)发生后可以发送广播提醒它。

系统广播

当一系列系统事件发生的时候,系统会自动发送广播,比如飞行模式的切换。系统广播会发送给所有注册监听广播的应用。

广播消息封装在一个Intent对象中,其中的action属性标识的事件的类型(比如android.intent.action.AIRPLANE_MODE),可能在intent的附件字段还包含了附加的信息。比如,用于表示飞行模式的intent包含一个附加的布尔字段来表示飞行模式的状态是开启还是关闭。

如果想要具体了解如何如何读取一个intent并且获取附加字段,参阅Intents and Intent Filters

参阅Android SDK中的BROADCAST_ACTIONS.TXT来了解所有系统广播的action。每一个系统广播都有一个常量与其绑定。比如,常量ACTION_AIRPLANE_MODE_CHANGED表示android.intent.action.AIRPLANE_MODE。每一个广播的action的文档都在与其关联的常量域中。

系统广播的变化

Android 7.0或更高版本不再发送下列系统广播,这项优化会影响所有的应用程序,而不只是那些针对Android 7.0开发的程序。

  • ACTION_NEW_PICTURE
  • ACTION_NEW_VIDEO

针对Android 7.0(API level 24)或更新版本开发的应用必须在程序中使用 ACTION_NEW_PICTURE
ACTION_NEW_VIDEO
注册监听下列的广播,在程序清单中声明不再有效。

  • CONNECTIVITY_ACTION

接收广播

应用程序可以使用两种方式接收广播:在应用清单中定义一个广播接收器;在程序中注册一个广播接收器。

静态广播接收器

要定义一个静态广播接收器,执行下面的步骤:

  1. 在应用清单中指定一个元素

    <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 filters指定了你需要接收哪个广播的action

  2. 继承BroadcastReceiver并且实现onReceive(Context, Intent)方法。下面的广播接收器的例子把接收的广播内容显示出来:

    public class MyBroadcastReceiver extends BroadcastReceiver {
        private static final String TAG = "MyBroadcastReceiver";
        @Override
        public void onReceive(Context context, 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();
        }
    }

当应用程序安装的时候,软件包管理器会在系统中注册广播接收器。之后这个广播接收器就变成了你的应用程序中一个独立的入口,这就意味着如果你的应用程序不在运行,系统可以启动你的程序并传递广播。

系统会创建一个新的BroadcastReceiver组件对象来处理接收到的广播。这个对象只在调用onReceive(Context, Intent)方法期间有效。一旦从该方法返回,系统就认为这个组件对象已经失效。

动态广播接收器

要注册一个上下文相关的动态广播接收器,执行以下步骤:

  1. 创建一个BroadcastReceiver的实例。

    BroadcastReceiver br = new MyBroadcastReceiver();
  2. 创建一个IntentFilter,然后调用registerReceiver(BroadcastReceiver, IntentFilter)注册到receiver:

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

    注意:要注册本地广播,调用需要替换为LocalBroadcastManager.registerReceiver(BroadcastReceiver, IntentFilter)

    动态广播接收器与它的上下文有相同的生命周期。比如你在一个Activity的上下文中注册,只要你的Activity没有被销毁,那么这个接收器一直有效。如果你在应用程序的上下文中注册,那么只要你的程序在运行,接收器就一直有效。

  3. 要停止接受广播,只需要调用unregisterReceiver(android.content.BroadcastReceiver)。当你不需要接受广播或者上下文环境不再有效的时候,请务必注销广播接收器。
    请记清楚你是在哪里注册又是在哪里注销广播接收器的。比方说,如果你在onCreate中注册,那你就应该在onDestroy中注销,以免发生广播接收器的泄露;如果你在onResume中注册,那就应该在onPause中注销,以免重复注册(如果你不想在暂停后接收广播,这样做可以降低系统的资源消耗)。不要在onSaveInstanceState(Bundle)中注册,因为当用户从当前Activity中返回的时候,这个函数不会被调用。

对进程状态的影响

你的广播接收器的状态会影响它所在的进程的状态,转而会影响进程被系统杀死的可能性。比如,当一个进程执行一个广播接收器(执行onReceive()方法中的代码),它会被当作一个前台进程。除非内存极度匮乏,否则系统会一直让该进程运行。

然而,一旦从onReceive()返回,广播接收器就不再处于激活状态,它的宿主进程也就跟其它的普通进程具有相同的优先级。如果那个进程只拥有一个在应用清单中定义的接收器,那么当从onReceive()返回后,系统会把它当作一个低优先级的进程,当其它优先级更高的进程需要更多内存的时候,它就可能被杀掉。

鉴于这个原因,你不应该在一个广播接收器中启动一个长时间在后台运行的线程。当从onReceive()返回后,系统可能会杀掉进程来回收内存,这会结束所有运行在这个进程中的线程。为了避免这种情况,你要么调用goAsync()(如果你希望能够长时间在后台线程中运行广播接收器),要么在接收器中使用JobScheduler调度一个JobService。这样系统就直到你的进程还在继续执行任务。参阅
Processes and Application Life Cycle来获取更多信息。

下面的代码片段展示了使用goAsync()来标识进程需要更多时间来完成任务。如果你要执行的任务会造成UI阻塞(>16ms),这种方式非常有效。

```
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();
    }
}
```

发送广播

Android提供了三种发送广播的方式:

  • sendOrderedBroadcast(Intent, String)方法一次向一个receiver发送广播。因为每个receiver轮流执行,所以receiver可以将结果向下个receiver转发。receiver接收的顺序可以通过intent-filter中的android:priority属性控制,具有相同接收优先级的receiver的接收顺序是随机的。

  • LocalBroadcastManager.sendBroadcast方法只会向本应用中的receiver发送广播。如果你不想在应用之间发送广播,可以使用本地广播。这种实现方式更加高效(无需进程间通信),并且你无须考虑由于其它应用接收你的广播而带来的安全问题。

下面的代码片段示范了如何通过创建Intent并且调用sendBroadcast(Intent)来发送广播:

Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data","Notice me senpai!");
sendBroadcast(intent);

广播消息被封装在一个Intent对象中。intent的action属性必须提供应用的包名并且能够唯一地标识一个广播事件。你可以通过调用putExtra(String, Bundle)来附加额外的xinxi。你也可以通过调用intent的setPackage(String)方法来将广播范围限定在某个组织的一系列应用的范围之内。

注意:虽然intents同时被用来发送广播和启动Activity,但是这些行为之间并没有任何关联。广播接收器无法捕捉到用来启动Activity的intent;同样地,当你广播一个intent,你也无法启动一个Activity。

通过权限限制广播的收发

权限机制可以让你将广播的范围限制在一系列拥有特定权限的应用之间。你既可以限制发送发,也可以限制接收方。

带权限发送

当你调用sendBroadcast(Intent, String)或者 sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)时,你可以指定一个权限参数。只有应用在应用清单中申请了那个权限,其中的receiver才能接收到广播。比如下面的代码发送了一个带权限的广播:

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

要接收这个广播,应用必须申请下面的权限:

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

你既可以指定一个系统中已经存在的权限,比如SEND_SMS,也可以用自定义一个权限。关于权限的详情请参考System Permissions。

带权限接收

如果你在注册receiver的时候指定了一个权限参数,那么只有申请了相应权限的应用才能够向你的receiver发送广播。

比如,假设你的receiver在应用清单中这样定义:

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

或者在代码中这样定义一个上下文相关的receiver:

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

那么,如果你要向这些receiver发送广播,发送方必须申请如下的权限:

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

安全性和最佳使用方案

下面是关于发送和接收广播的安全性考虑和最佳使用方案:

  • 如果你不需要向其它应用发送广播,那么可以使用LocalBroadcastManager发送和接收本地广播。本地广播更加高效(无需进程间通信),并且你无须考虑由于其它应用接收你的广播而带来的安全问题。本地广播可以在不增加系统范围内广播数量的前提下实现一个应用内部的发布/订阅事件通道。

  • 如果许多应用都在应用清单中注册接收同一个广播,会造成系统启动大量应用,对硬件性能和用户体验造成影响。为了避免这种情况,优先考虑上下文相关的广播接收器,而不是在应用清单中定义。有时,Android系统会强制要求使用上下文相关的广播接收器。比如CONNECTIVITY_ACTION这个广播只会发送给上下文相关的广播接收器。

  • 不要使用隐式intent发送敏感信息。这个信息可能会被其它任何注册该广播的应用监听。有三种方法来限定广播的接收方:

    • 发送广播的时候你可以指定一个权限
    • 在Android 4.0或更高版本,你可以通过setPackage(String)来指定一个包名。系统会将广播发送到匹配该包名的应用中。
    • 你可以通过LocalBroadcastManager发送本地广播。
  • 当你注册一个receiver后,任何应用都可以向你发送具有潜在恶意信息的广播。有三种方式来限制广播的发送发:

    • 注册receiver的时候可以指定一个权限。
    • 对于在应用清单中定义的receiver,可以将android:exported属性设为false,这样receiver就不会接收其它应用发来的广播。
    • 你可以通过LocalBroadcastManager仅接收本地广播。
  • 广播的action标志是全局的,确保action的值和其它字符串的值是在你自己的命名空间中,否则你可能会不小心与其它应用发生冲突。

  • 因为receiver的onReceive(Context, Intent)方法运行在主线程中,所以它必须能够很快地执行并返回。如果你需要执行一个耗时的操作,要小心使用子线程或者后台服务,因为当onReceive(Context, Intent)函数返回之后,系统随时会杀死你的进程。要了解更多信息,参考对进程的影响小节,要执行耗时的操作,我们建议:

    • 在receiver的onReceive()中调用goAsync(),然后将BroadcastReceiver.PendingResult传递给后台线程。这样可以让receiver在onReceive()返回之后保持活跃。但即使这样,系统也期望你快速结束这个任务(10s以内)。它确实可以让你把任务放到后台线程从而不影响主线程。
    • 通过JobScheduler调度一个任务。详情参考Intelligent Job Scheduling。
  • 不要在receiver中启动Activity,因为这严重影响用户体验,尤其是当存在多个receiver。可以通过显示一个通知来代替。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值