BroadcastReceiver
概述
BroadcastReceiver翻译过来是"广播接收者”的意思,与字面意思相同,它的功能就是用来接收来自系统和应用中的广播。
下面是API中对其的一些描述:
- 广播接收器是一种用于响应系统范围广播通知的组件。
- 许多广播都是由系统发起的 — 例如,通知屏幕已关闭、电池电量不足或已拍摄照片的广播。
- 应用也可以发起广播 — 例如,通知其他应用某些数据已下载至设备,并且可供其使用。
- 尽管广播接收器不会显示用户界面,但它们可以创建状态栏通知,在发生广播事件时提醒用户。
- 但广播接收器更常见的用途只是作为通向其他组件的“通道”,设计用于执行极少量的工作。 例如,它可能会基于事件发起一项服务来执行某项工作。
- 广播接收器作为 BroadcastReceiver 的子类实现,并且每条广播都作为 Intent 对象进行传递。
在Android系统中,广播体现在方方面面,例如:
- 当开机完成后系统会产生一条广播,接收到这条广播就能实现开机启动服务的功能
- 当网络状态改变时系统会产生一条广播,接收到这条广播就能及时地做出提示和保存数据等操作
- 当电池电量改变时,系统会产生一条广播,接收到这条广播就能在电量低时告知用户及时保存进度
Android中的广播机制设计的非常出色。
很多事情原本需要开发者亲自操作的,现在只需等待广播告知自己就可以了,大大减少了开发的工作量和开发周期。
而作为应用开发者,只需要数练掌握Android系统提供的这个开发利器——BroadcastReceiver。
类型
广播接收器通常分如下三个类型:
- 默认广播Normal broadcasts:发送一个默认广播使用Content.sendBroadcast()方法,普通广播对于接收者来说是完全异步的,通常每个接收者都无需等待即可以接收到广播,接收者相互之间不会有影响。对于这种广播,接收者无法终止广播,即无法阻止其他接收者的接收动作。
- 有序广播Ordered broadcasts:发送一个有序广播使用Content.sendOrderedBroadcast()方法,有序广播比较特殊,它每次只发送到优先级较高的接收者那里,然后由优先级高的接收者再传播到优先级低的接收者那里,优先级高的接收者有能力终止这个广播
- 粘性广播Sticky Broadcast:广播处理完之后,依然存在,直到你把它去掉。主要是为了服务某些动态注册的接收者。
创建一个广播接收器
准备工作:准备一个Button
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/normal"
android:onClick="normal"
android:text="发送一个普通的广播"
app:layout_constraintTop_toTopOf="parent"/>
除此之外,还需要准备一个Broadcast receiver
创建完毕后 配置清单文件会我们注册这个receiver
package com.example.a4_5broadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
//自定义的广播接收器
public class MyReceiver extends BroadcastReceiver {
//接收
@Override
public void onReceive(Context context, Intent intent) {
String info=intent.getStringExtra("info");
Toast.makeText(context,info,Toast.LENGTH_SHORT).show();
}
}
接着书写onClick事件:在学习intent的时候,我们已经知道,activity、service、broadcast receiver组件的交互都是依靠intent的,所以我们在这里需要构建一个Intent(参数为action,需要对应的在配置清单文件加上这个action,并嵌套一个intent-filter)
由于发送的是普通(默认)的广播,所以最后需要使用sendBroadcast()方法。
package com.example.a4_5broadcastreceiver;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//发送一个普通的广播
public void normal(View view){
Intent intent=new Intent("com.example.action.MY_BROADCAST");
intent.putExtra("info","发送了一个普通的广播");
this.sendBroadcast(intent);
}
}
在8.0及以上版本,上面的方法(静态注册)是不能正常实现的,请换用更低版本的模拟器进行测试,或者跳过这个案例继续看文章。
注册广播接收器的两种方式
- 静态注册:在AndroidManifest.xml文件中配置。(即上面演示的案例)
- 动态注册:需要在代码中动态的指定广播地址并注册,通常我们是在Activity或Service注册一个广播
注意:
动态注册的优先级高于静态注册
下面给出一个动态注册的案例
依旧需要需要一个Broadcast receiver,之前提到配置清单文件会自动注册,由于选择了动态注册,所以把配置清单文件里的注册的部分去掉(或注释掉)
我们简单的写一下:
package com.example.a4_5broadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MyReceiver2 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "动态注册的广播接收器", Toast.LENGTH_LONG).show();
}
}
重点是MainActivity如何注册Broadcast receiver:
先new一个Broadcast receiver对象
在onResume中广播注册(通过IntentFilter过滤动作,通过registerReceiver注册)
在onPause中解除广播注册
package com.example.a4_5broadcastreceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
private MyReceiver2 myReceiver2 = new MyReceiver2();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//发送一个普通的广播
public void normal(View view){
Intent intent=new Intent("com.example.action.MY_BROADCAST");
intent.putExtra("info","发送了一个普通的广播");
this.sendBroadcast(intent);
}
//该方法中进行广播注册(动态)
@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction("com.example.action.MY_BROADCAST");
registerReceiver(myReceiver2, filter);
}
//在该方法中解除广播注册
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(myReceiver2);
}
}
有序广播
之前两个案例都是关于默认广播的,现在给出有序广播的案例,下面是一些注意事项和补充说明。
- 需要用到android:priority属性:这个属性可以控制优先级,范围在-1000到1000,数值越大, 优先级越高。
- 如果是同级别接收先后将是随机的。
- 使用sendOrderedBroadcast方法发送有序广播时,需要一个权限参数。这样做是从安全角度考虑的,例如系统的短信就是有序广播的形式,一个应用可能是具有栏截垃圾短信的功能,当短信到来时它可以先接受到短信广播,必要时终止广播传递,这样的软件就必须声明接收短信的权限。
参数如下:
null——不要求接收者声明指定的权限。
不为null——接收者若要接收此广播,需声明指定权限。
为了更好的体现有序广播的特性,我们建立两个接收器,并重写onReceive方法....
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "低优先级的Receive", Toast.LENGTH_LONG).show();
}
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"高优先级的Receive",Toast.LENGTH_LONG).show();
}
同样准备一个按钮,并完成对应的点击事件
//发送一个有序的广播
public void order(View view){
Intent intent=new Intent("com.example.action.MY_BROADCAST2");
//参数:intent ,接收权限具体细节见上面的补充说明
this.sendOrderedBroadcast(intent,null);
}
再对它们配置清单文件的android:priority属性进行设置。
效果会是,先出现高优先级的广播,再出现低优先级的广播。
当然这样子还不能完全体现出有序广播的全部特殊之处。我们进一步研究下去。
说明:
- 终止广播传递:abortBroadcast(),高优先级的接收者可以终止广播传递。这个就不测试了,效果就是如果被终止,后面的广播就不显示了。
- 在广播接收器中使用setResultExtras方法将一个Bundle对象设置为结果集对象。传递到下一个接收者那里,这样优先级低的接收者可以用getResultExtras获取到最新的经过处理的信息集合。
修改两个接收器
package com.example.a4_5broadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
public class MyReceiver4 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"高优先级的Receive",Toast.LENGTH_LONG).show();
Bundle data=new Bundle();
data.putString("info","放入了一段内容");
this.setResultExtras(data);
}
}
package com.example.a4_5broadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
public class MyReceiver3 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Bundle data= getResultExtras(false);
String info= data.getString("info");
Toast.makeText(context, "低优先级的Receive/"+info, Toast.LENGTH_LONG).show();
}
}
可以看到,低优先级的Receive收到了经过处理之后的最新广播。
粘性广播
下面是关于粘性广播的案例。
注意事项:
-
使用sendStickyBroadcast()来发送粘性广播
-
使用sendStickyOrderedBroadcast()来发送兼具有序广播和粘性广播的特性的广播(优先级情况和之前类似)
-
粘性广播需要在配置清单提供<uses-permission android:name="android.permission.BROADCAST_STICKY" />的权限
-
使用removeStickyBroadcast()来解除粘性广播
在配置清单加入权限
一个按钮👇及其点击事件方法
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/sticky"
android:onClick="sticky"
android:text="发送一个粘性广播"
app:layout_constraintTop_toBottomOf="@id/order"/>
//发送一个粘性广播
public void sticky(View view) {
Intent intent = new Intent("com.example.action.MY_BROADCAST3");
this.sendStickyBroadcast(intent);
}
一个广播接收器 (并进行动态注册)
package com.example.a4_5broadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MyReceiver5 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"一个粘性的广播",Toast.LENGTH_SHORT).show();
}
}
为了测试粘性广播特性,额外加一个Activity并额外加一个按钮启动这个Activity
package com.example.a4_5broadcastreceiver;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class Main2Activity extends AppCompatActivity {
private MyReceiver5 myReceiver5 = new MyReceiver5();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter("com.example.action.MY_BROADCAST3");
registerReceiver(myReceiver5,filter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(myReceiver5);
}
}
效果如下:
显然接收器注册在发送广播之后,但是依然收到了广播。
系统广播
安卓内置了不少系统级的广播,使用步骤与前面的案例无异,下面简单的提供一些案例。
开机启动服务
一个接收器
package com.example.a4_5broadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
//开机启动服务
public class MyReceiver6 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "开机启动服务", Toast.LENGTH_SHORT).show();
}
}
还需要设置权限
配置action
如此一来就可以在开机启动的时候推送广播了。
然后模拟器上可能看不出来效果...orz
网络状态变化
准备一个接收器
package com.example.a4_5broadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.widget.Toast;
public class MyReceiver7 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//系统级服务,网络管理服务
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//网络活动信息,可以从info中拿到网络类型名
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null) {
String name = info.getTypeName() + "";
Toast.makeText(context, name, Toast.LENGTH_LONG).show();
}
}
}
设置权限
对应的action
然后模拟器上可能同样看不出来....
电量变化
同样一个接收器
package com.example.a4_5broadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
import android.widget.Toast;
//电池电量变化
public class MyReceiver8 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//当前电量
int curr = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
//总电量
int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
//计算百分比
int percent = curr * 100 / total;
Toast.makeText(context, "当前电量为" + percent, Toast.LENGTH_SHORT).show();
}
}
设action(这个不用额外设置权限)
然后模拟器上还是可能看不出来
不过这个可以抢救一下
改成动态注册(不监测电量变化,而是直接输出当前电量)
无视第三第四行...(是之前的遗留产物)
@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction("com.example.action.MY_BROADCAST");
registerReceiver(myReceiver2, filter);
//立即获取电量信息的方法
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction("android.intent.action.BATTERY_CHANGED");
Intent intent=getApplicationContext().registerReceiver(null,intentFilter);
//当前电量
int curr = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
//总电量
int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
//计算百分比
int percent = curr * 100 / total;
Toast.makeText(this, "当前电量为" + percent, Toast.LENGTH_SHORT).show();
}
效果如👈
收发短信
安卓的收发短信其实也是通过广播(有序广播)来实现的,下面带来一组收发短信的案例。
发送短信
安卓中已经自带发送短信的应用,所以我们只需要进行集成调用即可。
- 获取默认的消息管理器:SmsManager manager=SmsManager.getDefault()
- 拆分长短信:ArrayList list=manager.divideMessage(String txt)
- 发送短信:manager.sendTextMessage(String phone,null,String content,null,null)
- 发送短信还需要设置权限 <uses-permission android:name="android.permission.SEND_SMS"/>
准备一个按钮
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/send"
android:onClick="send"
app:layout_constraintTop_toTopOf="parent"
android:text="发送短信"/>
书写点击事件
package com.example.a4_5message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.view.View;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void send(View view) {
//获取短信管理器
SmsManager smsManager = SmsManager.getDefault();
String message = "你好,这是一条测试短信";
//拆分长短信
ArrayList<String> list = smsManager.divideMessage(message);
int size = list.size();
for (int i = 0; i < size; i++) {
//第一个参数为电话号码
smsManager.sendTextMessage("10086", null, list.get(i), null, null);
}
}
}
接收短信
之前提到了,安卓的短信也是靠广播(有序广播)来实现的,主要指的就是接收广播。
- 接收该广播的Action:android.provider.Telephony.SMS_RECEIVED
- 接收短信也需要设置权限 <uses-permission android:name="android.permission.RECEIVE_SMS"/>
创建接收器并在配置清单设置对应的权限及Action,见👆(过程不表)
package com.example.a4_5message;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.widget.Toast;
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Bundle bundle=intent.getExtras();
if (bundle!=null){
//通过pdus获得接收到的所有短信消息,获取短信内容
Object[] objs= (Object[]) bundle.get("pdus");
//构建短信对象数组
SmsMessage[] smsMessages=new SmsMessage[objs.length];
for (int i=0;i<objs.length;i++){
//获取单挑短信内容,以pdu格式存储,并生成短信对象
smsMessages[i]=SmsMessage.createFromPdu((byte[])objs[i]);
//发送方的号码
String number=smsMessages[i].getDisplayOriginatingAddress();
System.out.println(number);
//获取短信的内容
String content=smsMessages[i].getDisplayMessageBody();
System.out.println(content);
Toast.makeText(context, number+"---"+content, Toast.LENGTH_LONG).show();
}
//黑名单...
abortBroadcast();
}
}
}
注意:亲测上述方案只支持API23及以下
效果如下: