【Android】Broadcasts详解

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

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

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。可以通过显示一个通知来代替。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • JavaScript高级程序设计(读书笔记)(七)[通俗易懂]

    JavaScript高级程序设计(读书笔记)(七)[通俗易懂]本笔记汇总了作者认为“JavaScript高级程序设计”这本书的前七章知识重点,仅供参考。第七章函数表达式小结:在JavaScript编程中,函数表达式是一种非常有用的技术。使用函数表达式可以无须对函数命名,从而实现动态编程。匿名函数,也称为拉姆达函数,是一种使用JavaScript函数的强大方式。以下总结了函数表达式的特点。函数表达式不同于函数声明。

  • win10显卡驱动怎么装_win10系统显卡驱动安装失败怎么办

    win10显卡驱动怎么装_win10系统显卡驱动安装失败怎么办大家好,今天分享一篇来自小白系统官网(xiaobaixitong.com)的图文教程。我们日常在对电脑的使用过程中,经常都会遇到这样或那样的问题。比如说win10系统显卡驱动安装失败该怎么办呢?别着急,还有小编在呢?接下来小编就来告诉大家win10电脑系统显卡驱动安装失败怎么解决。详细教你win10系统显卡驱动安装失败怎么办:方法一,删除之前的显卡驱动文件重新安装1,首先,右键点击“此电脑”,菜单…

  • jquery.tmpl 基础用法

    jquery.tmpl 基础用法jQuer.tmpl通过动态请求返回数据时通过HTML显示到页面快速便捷实用的方法。只需要在预先定义好一个模板在动态数据返回后调用jQuery对应实现的方法即可对HTML进行拼接同时显示出来。并且定义模板时可以使用一些逻辑判断的标签。个人认为jQuer.tmpl有个不好的地方就是没有错误提示;例如在使用标签进行判断时可能有个地方字段写错的但是没有提示需要花一点时间去找问题,那就会有一些苦恼。…

  • Linux中修改文件权限方法「建议收藏」

    Linux中修改文件权限方法「建议收藏」​一、文件类型在Linux操作系统中,一切皆文件,Linux不以扩展名来区分文件类型,而是在文件属性中有一列专门记录文件类型。普通文件:.c.cpp.h.txt.pdf用’-‘表示目录文件(文件夹):用’d‘表示管道文件(用于进程间通信的一种文件):用’p’表示链接文件(相当于Windows上的快捷方式):用’l’表示设备文件:字符设备文件(c)块设备文件(b)套接字(s)用ls-l查看文件属性信息

  • 面试中如何回答JVM垃圾回收机制[通俗易懂]

    面试中如何回答JVM垃圾回收机制[通俗易懂]JVM中的垃圾回收了解吗首先是如何标记存活对象,主要有两个算法,分别是引用计数法和可达性分析算法。引用计数法:给一个对象添加一个引用计数器,当一个地方引用它时,计算器+1,不引用的时候-1,当引用计数器为0时说明该对象可回收。但是一旦出现互相引用的情况,就会出现无法回收的现象。所以JVM采用的是可达性分析算法。可达性分析算法:首先会标记所有GCroot能够直接关联的对象。GCro…

  • 25个经典Selenium自动化面试题,赶紧收藏

    25个经典Selenium自动化面试题,赶紧收藏(1)selenium的工作原理?①脚本启动driver②driver去驱动浏览器作为远程服务器③执行脚本发送请求④服务器解析请求作出相应操作,并返回给客户端(脚本)(2)selenium自动化页面元素找不到存在异常的原因?①元素定位错误②页面加载时间过慢,需要查找的元素程序已经完成,单页面还未加载,此时可以加载页面等待时间③有可能元素包含在iframe或…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号