1. 什么是广播
Android 系统自己在很多时候都会发送广播,比如电量低或者充足,刚启动完,插入耳机,输入法改变等, 发生这些时间,系统都会发送广播,这个叫系统广播
每个 APP 都会收到,如果想让一个应用在接收到广播的时候做一些操作,比如:系统开机后,偷偷后台跑服务~哈哈,这个时候只需要为应用注册一个用于监视开机的 BroadcastReceiver
,当接收到开机广播就做写偷偷摸摸的勾当
应用场景:
- 同一应用具有多个进程的不同组件之间的消息通信
- 不同应用间的组件之间的消息通信
- 与Android系统在特定情况下的通信
- 如:系统开机,网络变化等
实现原理:
- Android中的广播使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型。因此,Android将广播的发送者和接收者解耦,使得系统方便集成,更易扩展。
模型中有3个角色
- 消息订阅者(广播接收者)
- 消息发布者(广播发布者)
- 消息中心(AMS,即Activity Manager Service)
原理描述:
- 广播接收者通过Binder机制在AMS注册
- 广播发送者通过Binder机制向AMS发送广播
- AMS根据广播发送者要求,在已注册列表中,寻找合适的广播接收者
寻找依据:IntentFilter/Permission - AMS将广播发送到合适的广播接收者响应的消息循环队列中;
- 广播接收者通过消息循环拿到此广播,并回调onReceiver()
特别注意:广播发送者和广播接收者的执行时异步的,发出去的广播不会关心有无接收者,也不确定接收者到底是何时才能接收到;
2. 四种广播类型
-
标准广播
完全异步执行的广播,发出广播后,所有的广播接收器几乎会在同一时刻收到这条广播通知
-
有序广播
同步执行的一种广播,发出广播后,同一时间只有一个广播接收者能收到,当这个广播接收者的逻辑执行完后,才会传递到下一个广播接收者
前一个广播接收者还可以截断广播,让广播不会继续传递
-
系统广播
Android系统中内置了多个系统广播,只要涉及到手机的基本操作,基本上都会发出相应的系统广播。如:开启启动,网络状态改变,拍照,屏幕关闭与开启,点亮不足等等。每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部当特定事件发生时,有系统自动发出。
-
应用内广播
Android中的广播可以跨进程甚至跨App直接通信,且注册是exported对于有intent-filter的情况下默认值是true,由此将可能出现安全隐患如下:
-
其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;
-
其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息。
无论哪种情形,这些安全隐患都确实是存在的。由此,最常见的增加安全性的方案是:
-
对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收;
-
在广播发送和接收时,都增加上相应的permission,用于权限验证;
-
发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定在,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
App应用内广播可以理解成一种局部广播的形式,广播的发送者和接收者都同属于一个App。实际的业务需求中,App应用内广播确实可能需要用到。同时,之所以使用应用内广播时,而不是使用全局广播的形式,更多的考虑到的是Android广播机制中的安全性问题。
相比于全局广播,App应用内广播优势体现在:
-
安全性更高;
-
更加高效。
为此,Android v4兼容包中给出了封装好的LocalBroadcastManager类,用于统一处理App应用内的广播问题,使用方式上与通常的全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将主调context变成了LocalBroadcastManager的单一实例。
3. 接受广播的方式
接收广播之前,先要为我们的 APP 注册广播接收器
Android 提供了两种注册广播的方式 动态 与 静态
动态注册:
就是在 Java
代码中指定 Intent-filter
,然后添加不同的 Action
想监听什么广播就写什么 Action
动态注册需要应用程序启动后才能接收广播信息
注意: 动态注册的广播一定要调用
unregisterReceive()
取消广播注册
例子: 网络状态广播 ,一开始时没有联网,打开网络时,Toast 通知
1.
MsBroadcastReceiver.java, onReceive()
方法中完成广播要处理的事务
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MsBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"网络状态发生改变~",Toast.LENGTH_SHORT).show();
}
}
2. MainActivity.java 动态注册广播
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
MsBroadcastReceiver mReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mReceiver = new MsBroadcastReceiver();
IntentFilter itFilter = new IntentFilter();
// 设置接受关闭的类型
itFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
registerReceiver(mReceiver, itFilter); // 动态注册
}
//别忘了将广播取消掉
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver); // 销毁广播
}
}
假如我们需要程序没有启动也能接收广播的话,那么就需要注册静态广播
2. 静态注册
在 AndroidManifest.xml
设置 <receiver>
就可以让 APP 在未启动的情况下接收到广播
1.MsBootCompleteReceiver
,重写 onReceive
完成事务处理:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MsBootCompleteReceiver extends BroadcastReceiver {
private final String ACTION_BOOT = "android.intent.action.BOOT_COMPLETED";
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_BOOT.equals(intent.getAction()))
Toast.makeText(context, "开机完毕~", Toast.LENGTH_LONG).show();
}
}
2. 添加开机广播的 intent-filter 和注册权限
<receiver android:name=".MsBootCompleteReceiver">
<intent-filter>
<action android:name = "android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
注册权限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-
然后重启下手机会发现过了一会儿,就会弹出开机完毕这个 Toast 的了
Android 4.3 以上的版本,是允许将程序安装到 SD 卡上的,假如你的程序是安装在 SD 上的,就会收不到开机广播
注意事项
广播更多的时候扮演的是一个打开其它组件的角色,比如启动 Service, Notification 提示 , Activity 等
不要在广播里添加过多逻辑或者进行任何耗时操作
因为在广播中是不允许开辟线程的,当 onReceiver()
方法运行较长时间 ( 超过 10 秒 ) 还没有结束的话,那么程序会报错
4. 发送广播
一个发送广播的流程:
自定义一个 BroadcastReceiver
,重写 onReceive()
方法,然后注册下广播:
- 发送标准广播
sendBroadcast(intent);
-
发送有序广播 `sendOrderedBroadcast(intent,null)
可以在
AndroidManifest.xml
中的<Intent-filter>
中通过android:priority="100"
设置优先级,值越大优先级越高,越先收到广播,优先级可选范围-1000 - 1000
-
可以调用
abortBroadcast()
截断广播,让其不再继续传递
一个自己向自己表白的例子:
a. 新建一个广播接收者 MsBroadcastReceiver.java
package cn.twle.android.sendbroadcast;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MsBroadcastReceiver extends BroadcastReceiver {
private final String ACTION_BOOT = "cn.twle.android.sendbroadcast.MS_BROADCAST";
@Override
public void onReceive(Context context, Intent intent) {
if( ACTION_BOOT.equals(intent.getAction()))
Toast.makeText(context, "收到告白啦~",Toast.LENGTH_LONG).show();
}
}
b.修改 AndroidManifest.xml
注册 MsBroadcastReceiver
,写上 Intent-filter
<receiver android:name=".MsBroadcastReceiver">
<intent-filter>
<action android:name="cn.twle.android.sendbroadcast.MS_BROADCAST"/>
</intent-filter>
</receiver>
c.添加一个按钮
<?xml version="1.0" encoding="utf-8" ?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical" >
<Button
android:text="发送告白"
android:id="@+id/btn_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
d. 使用 sendBroadcast 发送广播
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn_send = (Button) findViewById(R.id.btn_send);
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendBroadcast(new Intent("cn.twle.android.sendbroadcast.MS_BROADCAST"));
}
});
}
}
5. 本地广播
前面说的广播都是 全局广播,意味着我们 APP
发出的广播,其它 APP
都会接收到, 或者其它 APP 发送的广播,我们的 APP 也会接收到,这样容易引起一些安全性的问题
Android 提供了 本地广播 的机制,使用该机制发出的广播只会在
APP
内部传播,而且 广播接收者也只能收到本应用发出的广播
使用流程
使用
LocalBroadcastManager
来管理广播
- 调用
LocalBroadcastManager.getInstance()
获得实例mLBM
- 调用
mLBM.registerReceiver()
注册广播- 调用
mLBM.sendBroadcase()
发送广播- 调用
mLBM.unregisterReceiver()
取消注册广播
6. 系统广播
系统广播常量 | 说明 |
---|---|
Intent.ACTION_AIRPLANE_MODE_CHANGED | 关闭或打开飞行模式时的广播 |
Intent.ACTION_HEADSET_PLUG | 在耳机口上插入耳机时发出的广播 |
Intent.ACTION_POWER_CONNECTED | 插上外部电源时发出的广播 |
Intent.ACTION_POWER_DISCONNECTED | 已断开外部电源连接时发出的广播 |
Intent.ACTION_REBOOT | 重启设备时的广播 |
Intent.ACTION_SCREEN_OFF | 屏幕被关闭之后的广播 |
Intent.ACTION_SCREEN_ON | 屏幕被打开之后的广播 |
Intent.ACTION_UMS_DISCONNECTED | 设备已从USB大容量储存状态转为正常状态时发出的广播 |