BroadcastReceiver

零、资料

一、简介

Android 四大组件中的全局的监听器,BroadcastReceiver (广播接收器)。常用作监听/接收App发出的广播消息,并做出响应。常用如网络变化,强制下线等。

二、原理

1.设计模式

广播中使用观察者模式(消息的订阅、发布)。

2.组成

  • 订阅者:广播接收器
  • 发布者:广播发布者
  • 消息中心:AMS(Activity Manager Service) 系统自行管理。

3.流程

流程

4.说明

  1. 接收器通过Binder机制在AMS注册。
  2. 发送者通过Binder机制向AMS发送广播。
  3. AMS根据发送者要求,在已注册列表中,寻找符合IntentFilter/Permission的接收器。
  4. AMS将发送到符合的接收器相应的消息循环队列中。
  5. 接收器通过消息循环拿到此广播,并回调onReceive()

注意:广播发送者和广播接收器的执行是异步的,既广播发送者不会关心接收器是否收到。

三、生命周期

1.动态注册

  • 当前活动注册时开启
  • 当前活动解除时销毁

2.静态注册

  • app周期
  • onReceive 执行后系统任意时间段销毁

四、使用

1.流程

流程

2.接收器定义

  1. 继承 BroadcastReceiver 并重写 onReceive()
  2. 注意
    • 接收器接收到广播后会自动调用 onReceive()
    • onReceive()一般会涉及与其他组件交互,如发送Notification、启动Service等。
    • 接收器默认为UI线程,所以onReceive()方法不能执行耗时操作,易将导致ANR

3.接收器注册

1.静态注册

  • 注册方式:AndroidManifest.xml<receive>标签声明

  • 属性说明:

    // 是否能被系统实例化,能为true,否则为false。默认为true。
    android:enabled=["true" | "false"]
    
    // 能否接收其他App的发出的广播,默认值是由`receiver`中有无`intent-filter`决定的。
    // 如果有`intent-filter`,否则为false。默认为true。
    android:exported=["true" | "false"]
    
    // 继承BroadcastReceiver子类的类名。
    android:name=".mBroadcastReceiver"
    
    // 具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收。
    android:permission="string"
    
    // BroadcastReceiver运行所处的进程,默认为app的进程,可以指定独立的进程。
    // 注:Android四大基本组件都可以通过此属性指定自己的独立进程。
    android:process="string"
    
    // 用于指定此广播接收器将接收的广播类型,下面用于接收网络状态改变时发出的广播。
    <intent-filter>
      <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
    
  • 优缺点:当此App首次启动时,系统会自动实例化静态注册的广播接收器,并注册到系统中。耗电、占内存。

2.动态注册

  • 注册方式:代码中调用Context.registerReceiver()

  • 具体代码:

    // 在onResume()中注册
    // 当此Activity实例化时,会动态注册到系统中。
    @Override
    protected void onResume(){
        super.onResume();
    
        // 1.实例化BroadcastReceiver子类 & IntentFilter
        mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter();
    
        // 2.设置接收广播的类型
        intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
    
        // 3.动态注册:调用Context的registerReceiver()方法
        registerReceiver(mBroadcastReceiver, intentFilter);
     }
    
    
    // 在onPause()中销毁
    // unregisterReceiver(BroadcastReceiver)
    @Override
    protected void onPause() {
        super.onPause();
    
        // 4.销毁在onResume()方法中的广播
        unregisterReceiver(mBroadcastReceiver);
    }
    
  • 注意:

    • 动态广播最好在活动onResume()注册、onPause()注销。
    • 重复注册、重复注销也是不被允许的。
    • App死亡前一定会执行onPause(),所以在此注销防止内存泄露。
    • 不在onCreate()&onDestory()onStart()&onStop()注册、注销是因为
      当系统因为内存不足要回收活动占用的资源时,活动在执行完onPause()后就会被销毁,此时onStop()onDestory()就不会执行。当再回到此活动时,是从onCreate()开始执行。

4.发送广播

sendBroadcast(intent)

  • 显式广播(Explicit Broadcast):一般在知道目标组件名称的前提下去发送指定Intent调用。指定了要激活的组件,一般是在相同的应用内实现。

  • 隐式广播(Implicit Broadcast):一般在未明确指出组件名称的前提下通过Intent Filter来实现。系统会根据 Intent Filter中的动作(action)、类别(category)、数据(URI和数据类型)找到最合适的组件。一般是用于在不同应用程序之间。

五、类型

1.标准广播 Normal Broadcast

一种完全异步执行的广播,广播发出之后,所有的广播接收器几乎都会在同一时刻收到,无先后顺序。效率会比较高,但无法被截断。

  • 示意图


    示意图
  • 发送

    Intent intent = new Intent();
    // 对应BroadcastReceiver中intentFilter的action
    intent.setAction(BROADCAST_ACTION);
    // 发送广播
    sendBroadcast(intent);
    
  • 接收

    <receiver 
        // 此广播接收者类是xxxBroadcastReceiver
        android:name=".xxxBroadcastReceiver" >
        // 用于接收网络状态改变时发出的广播
        <intent-filter>
            <action android:name="BROADCAST_ACTION" />
        </intent-filter>
    </receiver>
    

2.系统广播 System Broadcast

Android 系统内置广播,无需发送广播,仅接收就可以。

  • 对应的action
系统操作 action
监听网络变化 android.net.conn.CONNECTIVITY_CHANGE
关闭或打开飞行模式 Intent.ACTION_AIRPLANE_MODE_CHANGED
充电时或电量发生变化 Intent.ACTION_BATTERY_CHANGED
电池电量低 Intent.ACTION_BATTERY_LOW
电池电量充足(即从电量低变化到饱满时会发出广播) Intent.ACTION_BATTERY_OKAY
系统启动完成后(仅广播一次) Intent.ACTION_BOOT_COMPLETED
按下照相时的拍照按键(硬件按键)时 Intent.ACTION_CAMERA_BUTTON
屏幕锁屏 Intent.ACTION_CLOSE_SYSTEM_DIALOGS
设备当前设置被改变时(界面语言、设备方向等) Intent.ACTION_CONFIGURATION_CHANGED
插入耳机时 Intent.ACTION_HEADSET_PLUG
未正确移除SD卡但已取出来时(正确移除方法:设置--SD卡和设备内存--卸载SD卡) Intent.ACTION_MEDIA_BAD_REMOVAL
插入外部储存装置(如SD卡) Intent.ACTION_MEDIA_CHECKING
成功安装APK Intent.ACTION_PACKAGE_ADDED
成功删除APK Intent.ACTION_PACKAGE_REMOVED
重启设备 Intent.ACTION_REBOOT
屏幕被关闭 Intent.ACTION_SCREEN_OFF
屏幕被打开 Intent.ACTION_SCREEN_ON
关闭系统时 Intent.ACTION_SHUTDOWN
重启设备 Intent.ACTION_REBOOT

3.有序广播 Ordered Broadcast

一种同步执行的广播,同一时刻只会有一个接收器收到广播,执行完毕后向下传递加工后的广播(或不传递,后面接收器收不到广播)。有先后顺序,可截断。

  • 示意图


    示意图
  • 接收顺序
    按照Priority属性值从大-小排序,值相同时动态注册的广播优先。

  • 特点

    1. 接收广播按顺序接收。
    2. 先接收的广播接收器可以对广播进行截断,即后接收的广播接收器不再接收到此广播。
    3. 先接收的广播接收器可以对广播进行修改,那么后接收的广播接收器将接收到被修改后的广播。

4.本地广播 Local Broadcast

方法一

  1. android:exported="false",仅接收当前应用的广播。
  2. 在广播发送和接收时,增设相应权限permission,用于权限验证。
  3. 发送广播时指定该广播接收器所在的包名intent.setPackage(packageName),此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。

方法二

使用LocalBroadcastManager,只支持动态注册。

// 注册应用内广播接收器
// 1.实例化 IntentFilter & BroadcastReceiver 
xxBroadcastReceiver = new xxBroadcastReceiver(); 
IntentFilter intentFilter = new IntentFilter(); 
  
// 2.实例化 LocalBroadcastManager 的实例
localBroadcastManager = LocalBroadcastManager.getInstance(this);
  
// 3.设置接收广播的类型 
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
  
// 4.调用 LocalBroadcastManager 单一实例的 registerReceiver() 进行动态注册 
localBroadcastManager.registerReceiver(xxBroadcastReceiver, intentFilter);
  

// 5.取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(xxBroadcastReceiver);
  

// 6.发送应用内广播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent);

注意

对于不同注册方式的广播接收器的OnReceive(Context context, Intent intent)中的context返回值是不一样的。

  • 静态注册 (全局广播 + 应用内广播) :contextReceiverRestrictedContext
  • 动态注册 (全局广播) :contextActivity Context
  • 动态注册 (应用内广播 & LocalBroadcastManager) :contextApplication Context
  • 动态注册 (应用内广播 & 非LocalBroadcastManager) :contextActivity Context

六、Android O(8.0)

Android8.0后优化了电量,当App targetSDK >= 26,几乎禁止了所有的隐式广播静态注册监听

1.赦免清单

// Android 8.0 上不限制的隐式广播
/**
开机广播
 Intent.ACTION_LOCKED_BOOT_COMPLETED
 Intent.ACTION_BOOT_COMPLETED
*/
"保留原因:这些广播只在首次启动时发送一次,并且许多应用都需要接收此广播以便进行作业、闹铃等事项的安排。"

/**
增删用户
Intent.ACTION_USER_INITIALIZE
"android.intent.action.USER_ADDED"
"android.intent.action.USER_REMOVED"
*/
"保留原因:这些广播只有拥有特定系统权限的app才能监听,因此大多数正常应用都无法接收它们。"
    
/**
时区、ALARM变化
"android.intent.action.TIME_SET"
Intent.ACTION_TIMEZONE_CHANGED
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED
*/
"保留原因:时钟应用可能需要接收这些广播,以便在时间或时区变化时更新闹铃"

/**
语言区域变化
Intent.ACTION_LOCALE_CHANGED
*/
"保留原因:只在语言区域发生变化时发送,并不频繁。 应用可能需要在语言区域发生变化时更新其数据。"

/**
Usb相关
UsbManager.ACTION_USB_ACCESSORY_ATTACHED
UsbManager.ACTION_USB_ACCESSORY_DETACHED
UsbManager.ACTION_USB_DEVICE_ATTACHED
UsbManager.ACTION_USB_DEVICE_DETACHED
*/
"保留原因:如果应用需要了解这些 USB 相关事件的信息,目前尚未找到能够替代注册广播的可行方案"

/**
蓝牙状态相关
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED
BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED
BluetoothDevice.ACTION_ACL_CONNECTED
BluetoothDevice.ACTION_ACL_DISCONNECTED
*/
"保留原因:应用接收这些蓝牙事件的广播时不太可能会影响用户体验"

/**
Telephony相关
CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
TelephonyIntents.ACTION_*_SUBSCRIPTION_CHANGED
TelephonyIntents.SECRET_CODE_ACTION
TelephonyManager.ACTION_PHONE_STATE_CHANGED
TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED
TelecomManager.ACTION_PHONE_ACCOUNT_UNREGISTERED
*/
"保留原因:设备制造商 (OEM) 电话应用可能需要接收这些广播"

/**
账号相关
AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION
*/
"保留原因:一些应用需要了解登录帐号的变化,以便为新帐号和变化的帐号设置计划操作"

/**
应用数据清除
Intent.ACTION_PACKAGE_DATA_CLEARED
*/
"保留原因:只在用户显式地从 Settings 清除其数据时发送,因此广播接收器不太可能严重影响用户体验"
    
/**
软件包被移除
Intent.ACTION_PACKAGE_FULLY_REMOVED
*/
"保留原因:一些应用可能需要在另一软件包被移除时更新其存储的数据;对于这些应用,尚未找到能够替代注册此广播的可行方案"

/**
外拨电话
Intent.ACTION_NEW_OUTGOING_CALL
*/
"保留原因:执行操作来响应用户打电话行为的应用需要接收此广播"
    
/**
当设备所有者被设置、改变或清除时发出
DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED
*/
"保留原因:此广播发送得不是很频繁;一些应用需要接收它,以便知晓设备的安全状态发生了变化"
    
/**
日历相关
CalendarContract.ACTION_EVENT_REMINDER
*/
"保留原因:由日历provider发送,用于向日历应用发布事件提醒。因为日历provider不清楚日历应用是什么,所以此广播必须是隐式广播。"
    
/**
安装或移除存储相关广播
Intent.ACTION_MEDIA_MOUNTED
Intent.ACTION_MEDIA_CHECKING
Intent.ACTION_MEDIA_EJECT
Intent.ACTION_MEDIA_UNMOUNTED
Intent.ACTION_MEDIA_UNMOUNTABLE
Intent.ACTION_MEDIA_REMOVED
Intent.ACTION_MEDIA_BAD_REMOVAL
*/
"保留原因:这些广播是作为用户与设备进行物理交互的结果:安装或移除存储卷或当启动初始化时(当可用卷被装载)的一部分发送的,因此它们不是很常见,并且通常是在用户的掌控下"

/**
短信、WAP PUSH相关
Telephony.Sms.Intents.SMS_RECEIVED_ACTION
Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION

注意:需要申请以下权限才可以接收
"android.permission.RECEIVE_SMS"
"android.permission.RECEIVE_WAP_PUSH"
*/
"保留原因:SMS短信应用需要接收这些广播"

2.官方推荐

官方对于隐式广播推荐2种方法

  • 动态通过调用 Context.registerReceiver()注册广播接收器而不是在清单中声明接收器。
  • 使用JobScheduler

3.Android O 隐式广播修改

对于隐式广播可以修改为显式广播

  • 平常隐式发送广播

    // 隐式广播:Android8.0 targetSDK >= 26 受到限制
    Intent intent = new Intent();
    // 对应 BroadcastReceiver 中 intentFilter 的 action
    intent.setAction("ANDROID_O");
    // 发送广播
    sendBroadcast(intent);
    
  • 修改为显示发送

    • 发送本地
      如果广播只是 App 自己发自己收,只需改为显式广播。

      Intent intent = new Intent();
      // 指定Action
      intent.setAction("ANDROID_O");
      // 指定包名
      intent.setPackage(getPackageName());
      sendBroadcast(intent);
      
    • 发送所有APP
      当想发给其他 App,而不知其包名时可通过pm把所有隐式注册了这个自定义广播的 App列出来,然后转成显式发送。

      Intent intent = new Intent();
      // 指定Action
      intent.setAction("ANDROID_O");
      
      PackageManager pm = getPackageManager();
      List<ResolveInfo> matches = pm.queryBroadcastReceivers(intent, 0);
      for (ResolveInfo resolveInfo : matches) {
          intent.setPackage(resolveInfo.activityInfo.applicationInfo.packageName);
          sendBroadcast(intent);
      }
      

项目Demo

Demo地址 中的 Broadcast[module]

2019-11-12

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