编辑
2020-12-04
客户端技术
00
请注意,本文编写于 1024 天前,最后修改于 105 天前,其中某些信息可能已经过时。

目录

关于系统广播
系统广播的注册
静态注册
动态注册
广播的生命周期
自定义Broadcast
Broadcast分类
有序广播
无序广播
本地广播
new SDK中使用广播
Android 7.0 API 24
Android 9.0 API 28

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

关于系统广播

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

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

常见的系统广播:

xml
<!-- 开机广播 --> <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:

java
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

xml
<!-- 静态注册广播接收者 --> <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

java
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

java
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

java
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

java
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。

java
// 优先级 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

="false"属性,这样系统遍历全部APP清单文件的广播接收者时不会对本App的Receiver进行判断及处理。

本地广播

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

groovy
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'

MainActivity.java

java
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)及更高版本不发送以下系统广播:

java
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

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!