Android广播机制详解

laba.jpeg

Android应用程序可以从Android系统和其他Android应用程序发送或接收广播消息,类似于发布-订阅设计模式。Android中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会接收到自己所关心的广播内容。Android广播机制,本质上它就是一种组件间的通信方式。广播的发送者和接收者事先是不需要知道对方的存在的,这样带来的好处便是,系统的各个组件可以松耦合地组织在一起,这样系统就具有高度的可扩展性,容易与其它系统进行集成。

一. 广播

广播分为标准广播和有序广播。

标准广播

这是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。接收器不能对收到的广播做任何处理,也不能截断广播继续传播。该种类的广播用sendBroadcast发送。

有序广播

这是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。
该种类的广播用sendOrderedBroadcast发送。

粘性广播

粘性广播的特点是Intent会一直保留到广播事件结束,而这种广播也没有所谓的10秒限制,10秒限制是指普通的广播如果onReceive方法执行时间太长,超过10秒的时候系统会将这个广播置为可以被干掉的‘候选人’,一旦系统资源不够的时候,就会干掉这个广播而让它不执行。该广播用sendStickyBroadcast发送。
在Android5.0 & API 21中已经失效,所以不建议使用。

系统广播

例如当系统切换到飞机模式时。系统广播被发送到所有订阅了该事件的应用程序。
广播消息本身被包装在一个Intent对象中,其action字符串标识发生的事件(例如android.intent.action.AIRPLANE_MODE)。意图还可能包括绑定到其额外字段中的附加信息。例如,飞机模式意图包含一个额外的布尔值,指示飞机模式是否打开。

应用内广播

本地广播和其他的广播有些不同,它是使用LocalBroadcastManager来发送广播以及注册广播接收器的。
优点:它发出的广播只会在应用程序的内部传播,不用担心广播被其他应用接收,造成数据泄漏,而广播接收器也只能接收到自己应用发出的广播,不会接收别的应用发来的广播,防止接收垃圾信息。

代码示例:

LocalBroadcastManager localManager   =LocalBroadcastManager.getInstance(this);
Intent localIntent=new Intent("com.example.Broad");
localManager.sendBroadcast(localIntent);

二. 广播接收者

要想接受广播,我们需要在项目中注册广播接收者,而注册的方式有两种。

静态注册

一般为常驻广播,在AndroidManifest.xml里通过<receive>标签声明

<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过滤器里指定的是接收器订阅的action。
Android 8.0版本以后,系统不再支持,以下摘自官网的更新日志

为提高设备性能,系统会限制未在前台运行的应用的某些行为。具体而言:
1. 现在,在后台运行的应用对后台服务的访问受到限制。
2. 应用无法使用其清单注册的大部分隐式广播(即,并非专门针对此应用的广播)。

动态注册

非常驻广播,在使用时注册,用完及时销毁。

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

记得及时注销,以免内存泄漏。

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(receiver);
    }

三. 带权限的标准广播

发广播

当调用sendBroadcast(Intent, String)sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)时,可以指定权限参数。只有申请了该权限的接收者才能接收广播。

//发广播
sendBroadcast(new Intent("com.example.NOTIFY"),
              Manifest.permission.SEND_SMS);

要想接收这个广播,那么这个接收的app必须申请该权限

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

当然我们也可以自定义权限(<permission>),这里就不展开说了。

接收广播

同理,如果在注册广播接收器时指定了权限参数(使用registerReceiver(BroadcastReceiver,IntentFilter,String,Handler)或清单文件中的<receiver>里),则只有在清单文件中使用<uses-permission>请求权限的广播发送者才可以将Intent发送给接收者。
比如,在清单文件中声明:

<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 );

那么,如果要给它发消息,那广播发送的app就必须得申请获得相应的权限才行:

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

最佳实践

参考官网及阿里的Android开发手册,以下是针对广播组件的一些推荐做法:

  • 避免在 BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时工作,应该创建 IntentService 完成,而不应该在 BroadcastReceiver 内创建子线程去做。
    说明:
    由于该方法是在主线程执行,如果执行耗时操作会导致 UI 不流畅。可以使用
    IntentService 、创建 HandlerThread 或者调用Context#registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)方法等方式,在其他Wroker线程执行 onReceive方法。BroadcastReceiver#onReceive()方法耗时超过 10 秒钟,可能会被系统杀死。
    示例
IntentFilter filter = new IntentFilter();
        filter.addAction(LOGIN_SUCCESS);
        this.registerReceiver(mBroadcastReceiver, filter);
        mBroadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                Intent userHomeIntent = new Intent();
                userHomeIntent.setClass(this, UseHomeActivity.class);
                this.startActivity(userHomeIntent);
            }
        };
  • 避免使用隐式 Intent 广播敏感信息,信息可能被其他注册了对应BroadcastReceiver 的 App 接收。
    说明:
    通过Context#sendBroadcast()发送的隐式广播会被所有感兴趣的receiver接收,恶意应用注册监听该广播的receiver可能会获取到Intent中传递的敏感信息,并进行其他危险操作。如果发送的广播为使用Context#sendOrderedBroadcast()方法发送的有序广播,优先级较高的恶意receiver可能直接丢弃该广播,造成服务不可用,或者向广播结果塞入恶意数据。
    如果广播仅限于应用内,则可以使用LocalBroadcastManager#sendBroadcast()实现,避免敏感信息外泄和Intent 拦截的风险

  • Activity或者Fragment中动态注册BroadCastReceiver时,registerReceiver()unregisterReceiver()要成对出现。
    说明
    如果registerReceiver()unregisterReceiver()不成对出现,则可能导致已经注册的receiver没有在合适的时机注销,导致内存泄漏,占用内存空间,加重 SystemService负担。
    部分华为的机型会对receiver进行资源管控,单个应用注册过多receiver会触发管控模块抛出异常,应用直接崩溃。

  • 广播的action是全局的,所以命名的时候尽量包含app的命名空间,以免无意间和其他app冲突

  • 尽量不要在广播接收者中启动activity,这样用户体验很不好,可以考虑用发送通知的方式来代替。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270