前言
Android中有四种不同的应用组件类型,每种类型都服务于不同的目的,并且具有定义组件的创建和销毁方式的不同生命周期,这四种不同的应用组件分别为:Activity(活动)、Service(服务),ContentProvider(内容提供者)、BroadcastReceiver(广播接收器),简称<四大组件>
学习 广播接收器(BroadcastReceivcer) 之前我们先来了解以下 广播(Broadcast) 可以分为以下几点:
- 广播是什么?
- 广播有什么作用?
- 广播发送到接收的流程
1.广播是什么呢?
我们先来看看官方文档怎么说:
Android apps can send or receive broadcast messages from the Android system and other Android apps, similar to the publish-subscribe design pattern. These broadcasts are sent when an event of interest occurs. For example, the Android system sends broadcasts when various system events occur, such as when the system boots up or the device starts charging. Apps can also send custom broadcasts, for example, to notify other apps of something that they might be interested in (for example, some new data has been downloaded).
Apps can register to receive specific broadcasts. When a broadcast is sent, the system automatically routes broadcasts to apps that have subscribed to receive that particular type of broadcast.Android应用可以发送或接收来自Android系统和其他Android应用的广播消息,类似于发布 - 订阅设计模式。这些广播是在感兴趣的事件发生时发送的。例如,Android系统在发生各种系统事件时发送广播,例如系统启动或设备开始充电时。应用程序还可以发送自定义广播,例如,向其他应用程序通知他们可能感兴趣的内容(例如,某些新数据已被下载)。
应用可以注册以接收特定的广播。发送广播时,系统会自动将广播路由到已预订接收该特定广播类型的应用。
通俗来讲:广播就相当于(系统/应用)校园里的大喇叭,当它发出声音通知某班礼堂集合消息(广播),我们收到这个声音可以在教室可以在操场等地方,但当收到这个声音(广播)知道这个消息在讲我们班后我们就去礼堂集合了。(ps:在这里我们知道这个消息是在讲我们班的过程就是筛选信息的过程– intent-filter)
说了这么多那广播在Android有什么应用呢?
2.广播有什么作用/应用场景?
我们来看看官方文档怎么说:
Generally speaking, broadcasts can be used as a messaging system across apps and outside of the normal user flow. However, you must be careful not to abuse the opportunity to respond to broadcasts and run jobs in the background that can contribute to a slow system performance.
一般来说,广播可以用作应用程序之间和正常用户流程之外的消息传递系统。但是,您必须小心,不要滥用机会在后台响应广播和运行作业,这可能会导致系统性能降低
可以分为以下点:
- App内部的同一组件之间的通讯(ps:单或多线程之间)
- App内部的不同组件的通讯 (ps:单或多进程)
- 不同App之间的通讯
- 在特定情况下接受系统发出的消息(广播)
广播的原理:
它其实是采用了观察者模式,如文档所说 发布 - 订阅设计模式。
Android广播可以分为:广播接收器(BroadcastReceiver) 和 广播发布者
一条广播从广播发布者到被广播接收者接收的流程大致如下:
a.广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册.
b.广播发布者通过Binder机制向AMS发送广播.
c. AMS收到来自广播发布者的广播通过IntentFilter与Permission等筛选出符合条件的广播接收器,然后将广播发送到相应的BroadcastReceiver的消息循环队列中。
d.广播接收器从循环队列拿出广播(消息),并回调onReceive()方法.
了解了广播,那如何发送广播和接收广播呢?
使用广播分为以下两点:
1.广播注册
2.广播发送
1.广播注册
官方文档: Apps can receive broadcasts in two ways: through manifest-declared receivers and context-registered receivers.
应用程序可以通过两种方式接收广播:通过清单声明的接收器(静态注册)和上下文注册的接收器(动态注册)。
1.静态注册
注意点:如果您在清单中声明广播接收器,系统会在发送广播时启动您的应用程序(如果应用程序尚未运行)
在AndroidManifest清单文件中注册广播接收器(BroadcastReceiver):
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
意图过滤器(Intent-filter)来筛选收到的广播。
- 自定义子类继承BroadcastReceiver并重写OnReceive(Context context)方法:
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)期间有效。一旦您的代码从此方法返回,系统会认为该组件不再处于活动状态,所以不能再该方法执行时间过长的操作。
2.动态注册
- 用上下文注册接收者,步骤如下:
1.创建一个继承BroadcastReceiver并实现OnReceive()方法的自定义类:
BroadcastReceiver br = new MyBroadcastReceiver();
2.创建一个意图过滤器,用于筛选广播:
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
this.registerReceiver(br, filter);
注意点:
1.当广播不使用了之后一定要销毁广播,不让很容易造成内存泄漏!
2.注意你注册和注销接收者的位置,例如,如果你使用活动的上下文在onCreate(Bundle)中注册一个接收者,你应该在onDestroy()中取消注册,以防止接收者从活动上下文中泄漏出去。如果你在onResume()中注册一个接收器,你应该在onPause()中取消注册,以防止多次注册(如果你不想在暂停时接收广播,这可以减少不必要的系统开销)。
3.销毁广播
- 非本地广播调用: this.unregisterReceiver(android.content.BroadcastReceiver)方法.
- 本地广播:LocalBroadcastManager.unregisterReceiver()方法;
注意点:(这里引用了Carson_Ho这位博主的博文,非常感谢作者):
尽量在onResume()注册、onPause()注销是因为onPause()在App死亡前一定会被执行,从而保证广播在App死亡前一定会被注销,从而防止内存泄露。
原因:
1 .不在onCreate() & onDestory() 或 onStart() & onStop()注册、注销是因为:
当系统因为内存不足(优先级更高的应用需要内存,请看上图红框)要回收Activity占用的资源时,Activity在执行完onPause()方法后就会被销毁,有些生命周期方法onStop(),onDestory()就不会执行。当再回到此Activity时,是从onCreate方法开始执行。
2.假设我们将广播的注销放在onStop(),onDestory()方法里的话,有可能在Activity被销毁后还未执行onStop(),onDestory()方法,即广播仍还未注销,从而导致内存泄露。
3.但是,onPause()一定会被执行,从而保证了广播在App死亡前一定会被注销,从而防止内存泄露。
4.为什么OnReceive()不能执行长时间操作与让它能执行长时间的操作?
官方文档给出解释和方案:
1.您的BroadcastReceiver的状态(无论是否正在运行)会影响其包含进程的状态,从而可能影响其被系统杀死的可能性。例如,当一个进程执行一个广播接收者(也就是说,当前正在onReceive()方法中运行代码)时,它被认为是一个前台进程。除非存在极端的内存压力,否则系统会继续运行。
2.但是,一旦您的代码从onReceive()返回,BroadcastReceiver不再处于活动状态。广播接收者的主机进程变得与其中运行的其他应用程序组件一样重要。如果该进程仅托管AndroidManifest清单声明的接收方(用户从未或最近未与之交互过的应用的常见情况),则从onReceive()返回时,系统将其进程视为低优先级进程,并可能杀死它以使资源可用于其他更重要的进程。
3.出于这个原因,您不应该从广播接收器开始长时间运行后台线程。在onReceive()之后,系统可以随时终止进程以回收内存,并且这样做会终止在进程中运行的衍生线程。为了避免这种情况,您应该调用goAsync()(如果您需要更多时间在后台线程中处理广播)或使用JobScheduler从接收方调度JobService,那么系统知道该进程继续执行活动工作。
- 以下代码片段显示了一个BroadcastReceiver,它使用goAsync()来标记onReceive()完成后需要更多时间才能完成。如果你想在你的onReceive()中完成的工作足够长
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);
// 必须调用finish()以便BroadcastReceiver可以被回收.
pendingResult.finish();
return data;
}
};
//执行
asyncTask.execute();
}
}
2.广播的发送
广播的发送又可以分为:
- 普通广播
- 有序广播
- 粘性广播
- 系统广播
- 本地广播
1.普通广播(最简单常用的广播)
ps:第一个例子会将代码全部贴出,但后续的例子展示重要代码,因为都是一样的代码,带来不便请见谅。
a.自定义广播代码如下:
//自定义广播
public class MyBroadcast {
public static class MyBroadcast1 extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Log.d("Broadcast","1");
Toast.makeText(context, "MyBroadcast1", Toast.LENGTH_SHORT).show();
}
}
public static class MyBroadcast2 extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Log.d("Broadcast","2");
Toast.makeText(context, "MyBroadcast2", Toast.LENGTH_SHORT).show();
}
}
}
b. 这里使用静态注册,我们在AndroidManifest清单文件的 application 中注册:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.vveng.deletetest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".MyBroadcast$MyBroadcast1">
<intent-filter >
<action android:name="com.example.vveng.deletetest.MY_FLAG"/>
</intent-filter>
</receiver>
</application>
</manifest>
c.在Activity中发送广播:
public class MainActivity extends AppCompatActivity {
private Button btn_send;
Intent intent = new Intent();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_send = findViewById(R.id.main_btn);
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
intent.setAction("com.example.vveng.deletetest.MY_FLAG");
//发送广播
sendBroadcast(intent);
}
});
}
}
2.有序广播
首先什么是有序广播?
理解:有序有序就是具有一定先后顺序的接收广播,这是针对广播接收器而言的,有序广播和普通广播的发送方式不一样,有序广播的发送方式为: sendOrderedBroadcast(Intent intent, String receiverPermission);
对于有序广播的特点总结为两点:
1.当多个注册有效的广播接收器接收到sendOrderedBroadcast()发出的广播时,是按照从大到小的先后顺序,大小定义标准是通过清单文件中的Intent-Filter的android:priority或者IntentFilter中的setPriority()方法,优先级越大越先执行,对于android:priority该顺序仅适用于同步消息;对于异步消息它将被忽略,还有设置是有取值范围的按照官方文档所说是从 -1000 到 1000 .但是!但是!经过查阅最大的priority值是Integer最大值2147483647,这个优先级最高。
(ps:我使用oppoR9测试广播时出现了越小越先执行,但是在nexus5、小米、ZTE、一加手机上都是越大越先,这可能是厂商修改了源码或者是其他神秘力量,若遇到越小越先执行请先检查好自己代码再换不同机型测试,如果有小伙伴遇到跟我一样的情况知道原因请赐教!)
2.先接受到的广播可以对该广播进行截断(abortBroadcast()方法)或者修改消息,进行截断后广播将不会继续传递下去。
a.AndroidManifest清单文件:
<receiver android:name=".MyBroadcast$MyBroadcast1">
<intent-filter android:priority="100">
<action android:name="com.example.vveng.deletetest.MY_FLAG"/>
</intent-filter>
</receiver>
<receiver android:name=".MyBroadcast$MyBroadcast2">
<intent-filter android:priority="200">
<action android:name="com.example.vveng.deletetest.MY_FLAG"/>
</intent-filter>
</receiver>
b.Activity中的代码:
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
intent.setAction("com.example.vveng.deletetest.MY_FLAG");
//参1为意图,参2为权限(若没有则为null)
sendOrderedBroadcast(intent,null); //发送有序广播
}
});
3.粘性广播(StickyBroadcast)
粘性广播官方文档解释:执行“sticky”的sendBroadcast(Intent),意味着您发送的Intent在广播完成后仍处于停留状态,以便其他人可以通过registerReceiver(BroadcastReceiver,IntentFilter)的返回值快速检索该数据。在所有其他方面,这与sendBroadcast(Intent)的行为相同,还有粘性广播发送完成之后不会销毁而是保留在内存中。
发送的方法是: sendStickyBroadcast (Intent intent) 由于粘性广播在Android5.0已经弃用这里不再介绍使用方法。
官方给出的解释:此方法在API级别21中已弃用。不应使用粘滞广播。它们不提供安全性(任何人都可以访问它们),没有保护(任何人都可以修改它们)以及其他许多问题。推荐的模式是使用非粘性广播来报告某些事情已经发生变化,另一种机制是应用程序在需要时检索当前值。
4.系统广播
在Android系统中内置了许多系统广播,比如电量不足时系统会弹窗提示请及时充电、网络开启与关闭、音量大小弹出通知等等,我们也可以根据官方Api给出是action去注册相同的广播这样就能接收到系统广播了
比如监听手机开关飞行模式:
a.这次我们使用动态注册:
public class MainActivity extends AppCompatActivity {
private Button btn_send;
Intent intent = new Intent();
private MyBroadcast.MyBroadcast2 mMyBroadcast2;
private BroadcastReceiver mBroadcast2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_send = findViewById(R.id.main_btn);
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
intent.setAction("Intent.ACTION_AIRPLANE_MODE_CHANGED");
//参1为意图,参2为权限(若没有则为null)
sendBroadcast(intent);
}
});
}
@Override
protected void onResume() {
super.onResume();
mBroadcast2 = new MyBroadcast.MyBroadcast2();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
//注册广播
this.registerReceiver(mBroadcast2,filter);
}
@Override
protected void onPause() {
super.onPause();
//千万记得销毁广播
this.unregisterReceiver(mBroadcast2);
}
}
更多系统广播点击蓝色官方文档查询
下面我给出比较常用的系统广播:
android.net.conn.CONNECTIVITY_CHANGE —– 监听网络变化
Intent.ACTION_AIRPLANE_MODE_CHANGED —— 关闭或打开飞行模式
Intent.ACTION_REBOOT ——重启设备
Intent.ACTION_NEW_OUTGOING_CALL —–拨打电话
Intent.ACTION_SCREEN_OFF —–屏幕被关闭之后的广播
Intent.ACTION_SCREEN_ON—屏幕被打开之后的广播
5.本地广播
本地广播就是只在App应用程序内部的广播,以App的进程为界限。
官方文档给出的解释:LocalBroadcastManager.sendBroadcast()方法将广播发送到与广播发布者位于同一应用程序中的接收者。如果您不需要跨应用程序发送广播,请使用本地广播。实现效率更高(不需要进程间通信),您无需担心与其他应用程序能够接收或发送广播相关的任何安全问题。
有以下三点好处:
1.你知道你正在播放的数据不会离开你的应用程序,所以不必担心泄露私人数据。
2.其他应用程序无法将这些广播发送到您的应用程序,因此您不必担心会有可能利用的安全漏洞。
3.它比通过系统发送全球广播更有效率。
那如何使用本地广播呢?
代码:
public class MainActivity extends AppCompatActivity {
private Button btn_send;
Intent intent = new Intent();
private BroadcastReceiver mBroadcast2;
private LocalBroadcastManager mLocalBroadcastManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_send = findViewById(R.id.main_btn);
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
intent.setAction("com.example.vveng");
//参1为意图,参2为权限(若没有则为null)
//mLocalBroadcastManager发送广播
mLocalBroadcastManager.sendBroadcast(intent);
}
});
}
@Override
protected void onResume() {
super.onResume();
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
mBroadcast2 = new MyBroadcast.MyBroadcast2();
IntentFilter filter = new IntentFilter();
filter.addAction("com.example.vveng");
//mLocalBroadcastManager注册广播
mLocalBroadcastManager.registerReceiver(mBroadcast2,filter);
}
@Override
protected void onPause() {
super.onPause();
//千万记得mLocalBroadcastManager销毁广播
mLocalBroadcastManager.unregisterReceiver(mBroadcast2);
}
}
总结与注意点
1.BrocadcastReceiver的OnReceive()方法是在主线程中运行的,所以不能执行延时的工作,若要执行长时间的运行操作可以使用goAsync()进行异步操作。(App内部通讯的话可以使用优秀的第三方框架EventBus(推荐文章)或者Handler)
2.在有序广播执行过程中优先级越高越先执行,而在相同的优先级的广播接收器时 动态注册的优先级 比 静态注册的优先级 高
3.记得动态注册时要调用unregisterReceiver()方法注销广播,否则容易造成内存泄漏,建议在onResume()与onPause()方法中调用注册和注销!
4.如果你的广播不需要发送到本应用程序以外的地方,那就使用 本地广播 ,它的效率更高还可以保证不被其他的应用程序接受或者发送相关的广播而出现安全问题。
5.Android 7.0 移除了三项隐式广播,以帮助优化内存的使用和电量的消耗,移除的有CONNECTIVITY_ACTION(网络切换)与ACTION_NEW_PICTURE(相机)与ACTION_NEW_VIDEO(视频) 广播,由于这些广播经常被使用导致许多注册这些广播的应用程序被唤醒,现在在Android7.0以后移除了以AndroidManifest的注册方式,但是动态注册还是可以监听的或者用JobScheduler 。(推荐:Android7.0适配文章)
6.Android8.0开始 应用无法使用清单注册(静态注册)的方式来接收隐式广播,但是还是可以通过运行动态注册的方式注册或者使用JobScheduler,对于显示广播依然可以通过静态注册的方式。
备注:后面进一步学习会更新本文或者有遗漏有错误的地方请各位指出,非常感谢!
最后很感谢你看到这里,谢谢。