Tim

一枚野生程序员~

  • 主页
  • 分类
  • 标签
  • 归档
  • 关于
所有文章 工具

Tim

一枚野生程序员~

  • 主页
  • 分类
  • 标签
  • 归档
  • 关于

全局大喇叭——BroadcastReceiver

阅读数:次 2020-12-04
字数统计: 2.8k字   |   阅读时长≈ 11分

本篇文章记述了Android的四大组件之一 —— BroadcastReceiver(广播接收者)。广播的作用、广播注册的方式、自定义广播、广播的类型以及在较新的Android系统中使用BroadcastReceiver需要注意的问题。Android 应用与 Android 系统和其他 Android 应用之间可以相互收发广播消息,这与发布-订阅设计模式相似,这些广播会在所关注的事件发生时发送出去。

关于系统广播

举例来说,Android 系统会在发生各种系统事件时发送广播,例如系统启动或设备开始充电时。应用可以发送自定义广播来通知其他应用它们可能感兴趣的事件(例如,一些新数据已下载)。应用可以注册接收特定的广播。广播发出后,系统会自动将广播传送给同意接收这种广播的应用。

系统会在发生各种系统事件时自动发送广播,例如当系统进入和退出飞行模式时,系统广播会被发送给所有同意接收相关事件的应用,或者在手机低电量的时候,系统也会发出一个手机电量低的广播:

常见的系统广播:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 开机广播 -->
<action android:name="android.intent.action.BOOT_COMPLETED"/>

<!-- 开始充电广播 -->
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>

<!-- 低电量广播 -->
<action android:name="android.intent.action.BATTERY_LOW"/>

<!-- 应用卸载广播 -->
<action android:name="android.intent.action.PACKAGE_REMOVED"/>

<!-- 应用安装广播 -->
<action android:name="android.intent.action.PACKAGE_ADDED"/>

<!-- 声明这个数据类型才可以收到应用安装/卸载的广播 -->
<data android:scheme="package"/>

<... />

系统广播的注册

应用程序可以通过两种方式接收广播:通过清单文件声明的广播接收者和上下文注册的广播接收者。

静态注册

首先作为四大组件之一肯定是要在清单文件声明的,创建一个AppReceiver用来接收App安装和卸载的广播,此类要继承BroadcastReceiver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class AppReceiver extends BroadcastReceiver {
private static final String TAG = "AppReceiver";

@Override
public void onReceive(Context context, Intent intent) {
// 接收广播
if(intent != null){
// 判断收到的是什么广播
String action = intent.getAction();
assert action != null;
switch (action){
case Intent.ACTION_PACKAGE_REMOVED:
Log.i(TAG, "onReceive: ACTION_PACKAGE_REMOVED " + "应用被卸载");
break;
case Intent.ACTION_PACKAGE_ADDED:
Log.i(TAG, "onReceive: ACTION_PACKAGE_ADDED " + "应用被安装");
break;
}
}
}
}

AndroidManifest.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 静态注册广播接收者 -->
<receiver android:name=".AppReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<!-- 应用卸载广播 -->
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<!-- 应用安装广播 -->
<action android:name="android.intent.action.PACKAGE_ADDED"/>
<!-- 声明这个数据类型才可以收到应用安装/卸载的广播 -->
<data android:scheme="package"/>
</intent-filter>
</receiver>

在进行应用卸载和安装的时候,就可以收到对应的广播了:

动态注册

AppReceiver.java还是和上面的一样,但是现在不在AndroidManifest.xml中声明,而是使用动态注册的方式,下面的例子演示了如何在Activity创建的动态的注册广播接收者:

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MainActivity extends AppCompatActivity {

private BroadcastReceiver receiver;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 新建一个广播接收器
receiver = new AppReceiver();

// 接收哪些广播
IntentFilter intentFilter = new IntentFilter();
intentFilter.addDataScheme("package");
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);

// 注册广播接收者
registerReceiver(receiver, intentFilter);
}

@Override
protected void onDestroy() {
super.onDestroy();
// 取消注册广播接收器(不取消会导致内存泄漏)
if(receiver != null){
unregisterReceiver(receiver);
}
}
}

不过动态注册广播接收者千万别忘记在Activity销毁的时候解除之前注册的广播接收者。

静态注册需要在AndroidManifest.xml中声明,只要APP启动过一次,所静态注册的广播就会生效,无论当前的APP处于停止使用还是正在使用状态。只要相应的广播事件发生,系统就会遍历所有的清单文件,通知相应的广播接收者接收广播,然后调用广播接收者的onReceiver方法。

动态注册动态注册方式依赖于所注册的组件,当APP关闭后,组件对象都不在了动态注册的代码都不存在了,所动态注册监听的Action自然就不再生效了。静态注册的广播传播速度要远远慢于动态注册的广播。

如果即使用了动态注册,又使用了静态注册,那么动态动态注册的广播的优先级大于静态注册的广播。

广播的生命周期

1、BroadCastReceiver 的生命周期很短暂,当接收到广播的时候创建,当onReceive()方法结束后销毁

2、正因为BroadCastReceiver的声明周期很短暂,所以不要在广播接收器中去创建子线程做耗时的操作,因为广播接受者被销毁后,这个子进程就会成为空进程,很容易被杀死

3、因为BroadCastReceiver是运行在主线程的,所以不能直接在BroadCastReceiver中去做耗时的操作,否则就会出现ANR异常 ,耗时的较长的工作最好放到Service中去完成。这里不能使用子线程来解决 , 因为BroadcastReceiver的生命周期很短,子线程可能还没有结束BroadcastReceiver就先结束了,BroadcastReceiver一旦结束,此时BroadcastReceiver的所在进程很容易在系统需要内存时被优先杀死,因为它属于空进程 ( 没有任何活动组件的进程 ),如果它的宿主进程被杀死,那么正在工作的子线程也会被杀死,所以采用子线程来解决是不可靠的。

自定义Broadcast

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class MainActivity extends AppCompatActivity {

public static final String MY_ACTION = "cn.tim.action.MY_ACTION";
public static final String MY_ACTION_EXTRA_KEY = "input_content";
private EditText etContent;
private CustomReceiver receiver;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etContent = findViewById(R.id.et_content);
TextView tvShow = findViewById(R.id.tv_show);

receiver = new CustomReceiver();
receiver.tvShow = tvShow;

IntentFilter filter = new IntentFilter();
filter.addAction(MY_ACTION);
registerReceiver(receiver, filter);
}

public void send(View view) {
// 新建广播
Intent intent = new Intent(MY_ACTION);
// 放入广播要携带的数据
intent.putExtra(MY_ACTION_EXTRA_KEY, etContent.getText().toString());
sendBroadcast(intent);
}

@Override
protected void onDestroy() {
super.onDestroy();
// 解除注册
if(receiver != null){
unregisterReceiver(receiver);
}
}
}

CustomReceiver.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CustomReceiver extends BroadcastReceiver {
TextView tvShow;

@Override
public void onReceive(Context context, Intent intent) {
if(intent != null){
String action = intent.getAction();
if(MainActivity.MY_ACTION.equals(action)){
String inputContent = intent.getStringExtra(MainActivity.MY_ACTION_EXTRA_KEY);
tvShow.setText(inputContent);
}
}
}
}

不同的应用间通信,也是上面同样的代码,只要写相同的ACTION,那么就OK了:

Broadcast分类

广播的发送,可以分为有序广播、无序广播、本地广播以及粘性广播。

有序广播

有序广播是一种分先后广播接收器的广播,广播接收者的优先级越高,越先接收广播。优先级高的广播先收到广播,收到广播后可以修改广播的内容,也可以拦截广播不让广播向下传递。

如果在CReceiver中终止广播,那么优先级比较低的A与B都收不到广播了。

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends AppCompatActivity {
public static final String MY_ACTION = "cn.tim.action.MY_ACTION";
public static final String KEY = "cn.tim.action.MY_ACTION";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 新建广播
Intent intent = new Intent(MY_ACTION);
// 放入广播要携带的数据
Bundle bundle = new Bundle();
bundle.putInt(KEY, 100);
intent.putExtras(bundle);
//sendBroadcast(intent);

// 发送顺序广播,参数二:权限
sendOrderedBroadcast(intent, null);
}
}

注意:如果要修改数据的话前提得是Intent发送数据得格式必须是Bundle。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 优先级 2
public class CReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(intent != null){
String action = intent.getAction();
if(MainActivity.MY_ACTION.equals(action)){
Bundle bundle = intent.getExtras();
Toast.makeText(context, "C:" + bundle.getInt(MainActivity.KEY), Toast.LENGTH_SHORT).show();
Bundle newBundle = new Bundle();
newBundle.putInt(MainActivity.KEY, 90);
setResultExtras(newBundle);
}
}
}
}

// 优先级 1
public class AReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(intent != null){
String action = intent.getAction();
if(MainActivity.MY_ACTION.equals(action)){
Bundle bundle = getResultExtras(true);
Toast.makeText(context, "A:" + bundle.getInt(MainActivity.KEY), Toast.LENGTH_SHORT).show();
Bundle newBundle = new Bundle();
newBundle.putInt(MainActivity.KEY, 80);
setResultExtras(newBundle);
}
}
}
}

// 优先级 0
public class BReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(intent != null){
String action = intent.getAction();
if(MainActivity.MY_ACTION.equals(action)){
Bundle bundle = getResultExtras(true);
Toast.makeText(context, "B:" + bundle.getInt(MainActivity.KEY), Toast.LENGTH_SHORT).show();
}
}
}
}

无序广播

无序广播指所有与之匹配的广播接收者都能收到广播,没有先后顺序,直到没有广播接收者接收广播为止才会停止广播的传递。

默认情况下有广播发送时,系统会遍历全部APP的Receiver。如果想使得本APP的Receiver不被外界的广播所干扰,可以在Receiver节点添加android:exported=”false”属性,这样系统遍历全部APP清单文件的广播接收者时不会对本App的Receiver进行判断及处理。

本地广播

本地广播仅仅在APP内传播,其他的程序无法收到这个广播。这种广播保证安全性,不会传播到外界。同时由于LocalBroadcastManager不需要用到跨进程机制,因此相对 BroadcastReceiver 而言要更为高效。LocalBroadcastManager只在动态广播时使用,静态广播不能使用LocalBroadcastManager。

1
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class MainActivity extends AppCompatActivity {
public static final String MY_NEW_ACTION = "cn.tim.action.MY_NEW_ACTION";

private LocalReceiver localReceiver;
private LocalBroadcastManager localBroadcastManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 获得LocalBroadcastManager对象
localBroadcastManager = LocalBroadcastManager.getInstance(this);
// 动态注册广播
localReceiver = new LocalReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(MY_NEW_ACTION);
localBroadcastManager.registerReceiver(localReceiver, filter);
}

public void sendLocalBroadcast(View view) {
// 发送本地广播
Intent intent = new Intent(MY_NEW_ACTION);
localBroadcastManager.sendBroadcast(intent);
}


static class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent){
Toast.makeText(context,"Received LocalBroadcast!",Toast.LENGTH_SHORT).show();
}
}

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

new SDK中使用广播

随着 Android 平台的发展,它会不定期地更改系统广播的行为方式。如果要在Android 7.0(API 级别 24)或更高版本的SDK使用广播,必须注意以下更改:

Android 7.0 API 24

Android 7.0(API 级别 24)及更高版本不发送以下系统广播:

1
2
ACTION_NEW_PICTURE
ACTION_NEW_VIDEO

此外,以 Android 7.0 及更高版本为目标平台的应用必须使用动态注册的方式,无法在清单中声明广播接收器,所以以后所有广播直接全部使用动态注册的方式吧,如果非要用静态注册的方式,可以发送显式广播,(即指定指定包名再发送,但是我们有时不知道有谁要接收广播,所以显式广播用的比较少),这个时候可以在发送广播的时候携带intent.addFlags(0x01000000); 就能让广播突破隐式广播限制,但是依旧不建议静态注册,还是动态注册比较好。

Android 9.0 API 28

从 Android 9(API 级别 28)开始,NETWORK_STATE_CHANGED_ACTION广播不再接收有关用户位置或个人身份数据的信息。通过 WLAN 接收的系统广播不包含 SSID、BSSID连接信息或扫描结果,如果要获取这些信息,可以调用 WifiManager.getConnectionInfo()。

关于广播的文档可以看这里:《Receiving broadcasts》

赏

谢谢你请我喝咖啡

支付宝
微信
  • 本文作者: Tim
  • 本文链接: https://zouchanglin.cn/17555.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明出处!
  • Android
  • 消息通信
  • 移动开发

扫一扫,分享到微信

Application全局应用
SQLite原理与运用
  1. 1. 关于系统广播
  2. 2. 系统广播的注册
    1. 2.1. 静态注册
    2. 2.2. 动态注册
  3. 3. 广播的生命周期
  4. 4. 自定义Broadcast
  5. 5. Broadcast分类
    1. 5.1. 有序广播
    2. 5.2. 无序广播
    3. 5.3. 本地广播
  6. 6. new SDK中使用广播
    1. 6.1. Android 7.0 API 24
    2. 6.2. Android 9.0 API 28
© 2017-2021 Tim
本站总访问量次 | 本站访客数人
  • 所有文章
  • 工具

tag:

  • 生活
  • Android
  • 索引
  • MySQL
  • 组件通信
  • Nginx
  • JavaSE
  • JUC
  • JavaWeb
  • 模板引擎
  • 前端
  • Linux
  • 计算机网络
  • Docker
  • C/C++
  • JVM
  • 上传下载
  • JavaEE
  • SpringCloud
  • Golang
  • Gradle
  • 网络安全
  • 非对称加密
  • IDEA
  • SpringBoot
  • Jenkins
  • 字符串
  • vim
  • 存储
  • 文件下载
  • Mac
  • Windows
  • NIO
  • RPC
  • 集群
  • 微服务
  • SSH
  • 配置中心
  • XML
  • Chrome
  • 压力测试
  • Git
  • 博客
  • 概率论
  • 排序算法
  • 分布式
  • 异常处理
  • 文件系统
  • 哈希
  • openCV
  • 栈
  • 回溯
  • SpringCore
  • 流媒体
  • rtmp
  • 面向对象
  • Vue
  • ElementUI
  • 软件工程
  • 异步
  • 自定义UI
  • ORM框架
  • 模块化
  • 交互式
  • Jsoup
  • Http Client
  • LRUCache
  • RabbitMQ
  • 消息通信
  • 服务解耦
  • 负载均衡
  • 权限
  • 多线程
  • 单例模式
  • Protobuf
  • 序列化
  • Python
  • m3u8
  • 堆
  • 二叉树
  • 自定义View
  • 观察者模式
  • 设计模式
  • 线程池
  • 动态扩容
  • 高可用
  • GC
  • ffmpeg
  • SpringMVC
  • REST
  • Redis
  • 缓存中间件
  • UML
  • Maven
  • Netty
  • 高性能网络
  • IPC通信
  • IO
  • Stream
  • 发布订阅
  • SQLite
  • Hash
  • 集合框架
  • 链表
  • Lambda
  • 汇编语言
  • 组件化
  • Router
  • 开发工具

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia-plus根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • 思维导图
  • PDF工具
  • 无损放大
  • 代码转图
  • HTTPS证书