前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Android】Broadcasts详解

【Android】Broadcasts详解

作者头像
全栈程序员站长
发布2022-07-02 16:46:31
7900
发布2022-07-02 16:46:31
举报
文章被收录于专栏:全栈程序员必看

大家好,又见面了,我是你们的朋友全栈君。

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

接收广播

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

静态广播接收器

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

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

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

动态广播接收器

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

对进程状态的影响

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

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

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

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

代码语言:javascript
复制
```
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)来发送广播:

代码语言:javascript
复制
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才能接收到广播。比如下面的代码发送了一个带权限的广播:

代码语言:javascript
复制
sendBroadcast(new Intent("com.example.NOTIFY"),
              Manifest.permission.SEND_SMS);

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

代码语言:javascript
复制
<uses-permission android:name="android.permission.SEND_SMS"/>

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

带权限接收

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

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

代码语言:javascript
复制
<receiver android:name=".MyBroadcastReceiver" android:permission="android.permission.SEND_SMS">
    <intent-filter>
        <action android:name="android.intent.action.AIRPLANE_MODE"/>
    </intent-filter>
</receiver>

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

代码语言:javascript
复制
IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null );

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

代码语言:javascript
复制
<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。可以通过显示一个通知来代替。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/148542.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 系统广播
    • 系统广播的变化
    • 接收广播
      • 静态广播接收器
        • 动态广播接收器
          • 对进程状态的影响
          • 发送广播
          • 通过权限限制广播的收发
            • 带权限发送
              • 带权限接收
              • 安全性和最佳使用方案
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档