1. 概述
1.1 定义
跨进程的通信方式,运用在应用程序之间传递消息的机制,允许应用接收来自各处的广播消息,比如电话,短信等。同样,可以向外发出广播消息,例如电池电量低时会发送一条提示广播。要过滤并接收广播中的消息就需要使用BroadcastReceiver(广播接收者,Android四大组件之一)。
监听系统中的广播消息,并实现在不同组件之间的通信。当Android系统产生一个广播事件时,可以有多个对应的BroadcastReceiver接收并处理,这些广播接收者只需要在清单文件或者代码中注册并指定要接收的广播事件,然后创建一个类继承自BroadcastReceiver类,重写onReceive()方法,在方法中处理广播事件即可。
1.2 创建
创建一个类继承自BroadcastReceiver类,重写onReceive()方法,在方法中处理广播事件即可。广播要监听什么样的广播,需要添加相应的Action,这样才能接收到。
1.3 注意事项
过多的逻辑或耗时操作,因为在广播接收器中是不允许开启线程的,一旦onReceive()方法运行时间较长而没有结束,就会报错。
扮演一种打开其他程序组件的角色,比如创建一条状态栏通知,启动一个服务等等。
2. 注册方式
广播接收者有两种注册方式,分别为动态注册和静态注册,其区别如下:
动态注册
可以自由的控制广播接收者注册和注销,有很大的灵活性,缺点是必须在程序启动后才能接收到广播。
静态注册可以让程序在未启动的情况下就能接收到广播。
2.1 动态注册
2.1.1 方式
registerReceiver()方法进行注册。
2.1.2 案例
如下案例所示:监听网络状态的变化
1)在MainActivity中,动态注册广播
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
//设置监听广播的类型
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeReceiver = new NetworkChangeReceiver();
//注册广播
registerReceiver(networkChangeReceiver,intentFilter);
}
//自定义广播接受者
public class NetworkChangeReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager connectivityManager = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
//判断是否有网络连接
if(networkInfo != null && networkInfo.isConnected()){
Toast.makeText(context,"网络开启",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(context,"网络关闭",Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//取消注册广播
unregisterReceiver(networkChangeReceiver);
}
}
说明:ConnectivityManager是一个系统服务类,专门用于管理网络连接的。
2)在AndroidManifest.xml中添加申请访问系统网络状态的权限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
3)展示效果
当开启网络时:
当关闭网络时:
2.2 静态注册
2.2.1 方式
选择【new】->【Other】->【BroadcastReceiver】,如下图:
Exported复选框用于选择是否接收当前程序之外的广播,Enabled复选框用于选择广播是否可以由系统实例化。创建好的广播如下所示:继承自BroadcastReceiver,并重写其onReceive()方法,当有广播到来时,onReceive()方法就会得到执行。
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
用于该方法还没实现,故抛出异常,在实现方法时,删除该异常即可。广播接收者创建完成之后,清单文件中会注册,如下所示:
<application
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
</receiver>
</application>
2.2.2 案例
这里以监听手机开机的广播接收者为案例,手机开机后,显示手机开机提示消息。
1)创建自定义的BroadcastReceiver
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "监听到手机开机了!", Toast.LENGTH_SHORT).show();
}
}
2)在AndroidManifest.xml中添加监听系统开机广播权限,注册广播和指定action
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcaststudy">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<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">
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3)效果图
3. 自定义广播的发送与接收
广播的发送与接收如下图所示:
公共消息区中,而公共消息区中如果存在对应的广播接收者,就会及时的接收到这条消息。
4. 广播的类型
有序广播和无序广播两种类型。两者区别如下所示:
有序广播按照优先级别同步执行,每次只有一个广播接收者接收到广播消息,可被拦截,效率低无序广播按照异步执行,所有广播接受者同时接收到广播消息,不可被拦截,效率高
4.1 无序广播
4.1.1 定义
异步执行的,发送广播时,所有监听这个广播的广播接收者都会直接接受到此广播消息,但接收和执行顺序不确定。无序广播的效率比较高,但无法被拦截。其工作流程如下图:
4.1.2 方法
发送广播消息通过如下方法:
1.sendBroadcast(Intent intent)参数1:Intent对象2.sendBroadcast(Intent intent,String receiverPermission)
参数1:Intent对象
参数2:跟权限有关的字符串
4.1.3 案例
1)说明
自定义广播接受者,点击发送广播消息后,如果自定义的广播接收到了广播消息,则显示接收到的消息。
2)效果图
未点击发送广播消息按钮前:
点击发送广播消息按钮后:
3)在MainActivity.java中:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//创建Intent,指定要发送的广播的类型action
Intent intent = new Intent("com.luckyliuqs.MyBroadcastReceiver");
//发送显示广播,设置广播接收者的路径:第一个参数是包名路径;第二个参数是类名路径
intent.setComponent(new ComponentName("com.example.broadcaststudy",
"com.example.broadcaststudy.MyBroadcastReceiver"));
//创建Bundle
Bundle bundle = new Bundle();
//储存要发送的广播消息内容
bundle.putString("message","hello broadcast receiver");
intent.putExtra("bundle",bundle);
//发送广播
sendBroadcast(intent);
}
});
}
}
注意:在Android8.0之后,静态广播做了一些限制。静态注册的广播接收者无法接收到隐式广播消息。
如上面,如果没有intent.setComponent(ComponentName componentName),则广播接收者无法接收到广播消息。解决方案:
1.使用动态注册代替静态注册
2.保留静态注册,但是发送广播的时候需要
发送显示广播
①如果有单个静态广播接收者,则可以使用
intent.setComponent(ComponentName componentName),
设置广播为显式广播。如果有多个,为其指定,则只有最后一个才会生效,
其余的不会变成显示广播。
②如果有多个静态广播接收者,则可以使用intent.setPackage(getPackageName());
一次性全部设置显示广播。
如果接受不到广播,有以下两种情况:
1.静态注册的广播接收者接收不到隐式广播消息
设置广播为显式广播
setComponent(ComponentName componentName);
setPackage(getPackageName());
2.一些特殊的广播,必须动态注册,静态注册是不起效果的,
类似,屏幕的锁屏和开屏
IntentFilter recevierFilter=new IntentFilter();
recevierFilter.addAction(Intent.ACTION_SCREEN_ON);
recevierFilter.addAction(Intent.ACTION_SCREEN_OFF);
MyReceiver receiver=new MyReceiver();
registerReceiver(receiver, recevierFilter);
4)自定义广播接收者
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//获取到发送的广播的内容
String message = (String) intent.getBundleExtra("bundle").get("message");
Toast.makeText(context, "接收到发送的广播消息:"+message, Toast.LENGTH_LONG).show();
}
5)在AndroidManifest.xml中注册,并指定要监听的广播类型action
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcaststudy">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<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">
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.luckyliuqs.MyBroadcastReceiver"/>
</intent-filter>
</receiver>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
4.2 有序广播
4.2.1 定义
优先级别被依次接收,发送广播时,只有一个广播接收者能够接收此消息。当在此广播接收者逻辑执行完毕后,广播才会继续传递。相比于无序广播,有序广播的效率较低,但此类型是有先后顺序的,并可被拦截(通过abortBroadcast()方法,广播就不会继续往下进行传递)。
工作流程如下图:
【如何设置优先级】在清单文件中设置,如下:
<!-- 数值越大优先级越高 -->
<intent-filter android:priority="100">
.....
</intent-filter>
注意:当两个广播接收者优先级相同时,先注册的广播接收者会先接收到广播。
4.2.2 方法
发送有序广播通过如下方法:
1.sendOrderedBroadcast(Intent intent,String receiverPermission)
第一个参数是Intent
第二个参数是与权限有关的字符串
4.2.3 案例
1)说明:自定义两个广播接收者MyReceiver1和MyReceiver1,通过静态方式注册,设置优先级(MyReceiver1为50,MyReceiver2为100),以有序广播方式发送广播消息,然后分别展示消息内容
2)效果图:
3)自定义广播接收者
public class MyReceiver1 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("MyReceiver", "MyReceiver1 onReceive() ========================= ");
//获取广播消息内容
String message = intent.getBundleExtra("bundle").get("message").toString();
Toast.makeText(context, "MyReceiver1收到广播消息:"+message, Toast.LENGTH_SHORT).show();
}
}public class MyReceiver2 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.i("MyReceiver", "MyReceiver2 onReceive() ========================= ");
//获取广播消息内容
String message = intent.getBundleExtra("bundle").get("message").toString();
Toast.makeText(context, "MyReceiver2收到广播消息:"+message, Toast.LENGTH_SHORT).show();
//拦截广播方法,广播就不会继续往下传递了,即MyReceiver1不会接收到广播消息
//abortBroadcast();
}
}
abortBroadcast()用于拦截广播。
4)在MainActivity.java中
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.luckliuqs.broadcast_receiver");
Bundle bundle = new Bundle();
bundle.putString("message","hello all broadcast receiver");
intent.putExtra("bundle",bundle);
// intent.setComponent(new ComponentName("com.example.broadcastreceiverstudy2","com.example.broadcastreceiverstudy2.MyReceiver1"));
// intent.setComponent(new ComponentName("com.example.broadcastreceiverstudy2","com.example.broadcastreceiverstudy2.MyReceiver2"));
intent.setPackage(getPackageName());
sendOrderedBroadcast(intent,null);
}
});
}
}
5)在AndroidManifest.xml中静态注册广播接收者,设置优先级和接收广播类型action
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.broadcastreceiverstudy2">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<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">
<receiver
android:name=".MyReceiver2"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="100">
<action android:name="com.luckliuqs.broadcast_receiver"/>
</intent-filter>
</receiver>
<receiver
android:name=".MyReceiver1"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="50">
<action android:name="com.luckliuqs.broadcast_receiver"/>
</intent-filter>
</receiver>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
程序运行后,MyReceiver2会先接收到广播,然后MyReceiver1接收到广播事件。
4.3 指定广播接收者
实际开发中,遇到这样的情况:当发送一条有序广播时,有多个接收者接收这条广播,但需要保证一个广播接收者必须接收到此广播,无论优先级。要满足这样的需求,可以在Activity类使用sendOrderedBroadcast()方法发送有序广播,示例代码如下:
Intent intent = new Intent();
//定义广播的事件类型
intent.setAction("Intercept_Stitch");
//发送有序广播
MyBroadcastReceiverThree receiver = new MyBroadcastReceiver();
sendOrderedBroadcast(intent,null,receiver,null,0,null,null).
5.本地广播
5.1 引入
在前面发送和接收的广播都是属于系统全局广播,即发出的广播可以被其他任何应用程序接收到,并且发送广播者也可以接收来自于任何其他应用发送的广播,这样就很容易引起安全问题。比如发送的一些携带关键性数据的广播就可能被其他应用程序所截获;其他应用程序可以不听得向某个广播接收器里面发送各种垃圾广播。
5.2 定义
本地广播就是用来解决上述广播安全性问题。使用本地广播机制发出的广播只能够在当前应用程序内部进行传递,并且广播接收器也只能接收来自于当前应用程序发出的广播。
5.3 原理
LocalBroadcastManager来对广播进行管理,并且提供了发送广播和注册广播接收者的方法。
5.4 注意事项
本地广播是无法通过静态注册的方式来获取的。因为静态注册主要就是为了让程序在未启动的情况下也能接收到广播,而发送本地广播时,应用程序已经启动了,因此就不需要使用静态注册的功能。
5.5 优点
1.可以明确的知道正在发送的广播不会离开当前应用程序,因此不必担心泄密。2.其他应用程序无法将广播发送到当前应用程序内部,因此不必担心安全漏洞隐患。3.发送本地广播比发送全局广播更加高效。
5.6 案例
1)展示效果:点击发送本地广播之后,广播接收者接收到了广播
2)在MainActivity.java中
public class Main2Activity extends AppCompatActivity {
//创建Intent过滤器
private IntentFilter intentFilter;
//创建LocalBroadcastManager管理广播
private LocalBroadcastManager localBroadcastManager;
//创建广播接收者
private LocalReceiver localReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
//获取实例
localBroadcastManager = LocalBroadcastManager.getInstance(this);
findViewById(R.id.btn_local_broadcast).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.luckyliuqs.local_broadcast");
//发送本地广播
localBroadcastManager.sendBroadcast(intent);
}
});
intentFilter = new IntentFilter();
intentFilter.addAction("com.luckyliuqs.local_broadcast");
localReceiver = new LocalReceiver();
//注册本地广播监听器
localBroadcastManager.registerReceiver(localReceiver,intentFilter);
}
class LocalReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"接收到本地广播!",Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//注销操作
localBroadcastManager.unregisterReceiver(localReceiver);
}
}
6.实战:实现另一台设备登录强制下线
6.1 说明
类似于QQ一样的,在另一台设备上登录则会强制下线。
6.2 效果图
1)登录界面效果图
2)登录成功效果图
3)点击强制下线按钮,发送广播
4)接收广播后强制下线,返回登录界面
6.3 代码
6.3.1 定义BaseActivity,作为所有Activity的父类
public class BaseActivity extends AppCompatActivity {
private OfflineBroadcastReceiver offlineBroadcastReceiver;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
/**
* 让处于栈顶的Activity才能接收到此广播消息
*/
@Override
protected void onResume() {
super.onResume();
//Intent过滤器
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.luckyliuqs.offline_broadcast");
offlineBroadcastReceiver = new OfflineBroadcastReceiver();
//注册广播接收者
registerReceiver(offlineBroadcastReceiver,intentFilter);
}
@Override
protected void onStop() {
super.onStop();
if(offlineBroadcastReceiver != null){
//销毁注册
unregisterReceiver(offlineBroadcastReceiver);
offlineBroadcastReceiver = null;
}
}
/**
* 动态注册强制下线广播接收者
*/
public class OfflineBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(final Context context, Intent intent) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("警告");
builder.setMessage("另一处设备登录,你将被强制下线!");
builder.setCancelable(false);
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCollector.finishAllActivity();
Intent backLoginIntent = new Intent(context,LoginActivity.class);
context.startActivity(backLoginIntent);
}
});
builder.show();
}
}
}
6.3.2 定义ActivityCollector,管理所有的Activity
public class ActivityCollector {
public static List<Activity> activityList = new ArrayList<Activity>();
public static void addActivity(Activity activity){
activityList.add(activity);
}
public static void removeActivity(Activity activity){
activityList.remove(activity);
}
public static void finishAllActivity(){
for(Activity activity : activityList){
if(!activity.isFinishing()){
activity.finish();
}
}
//清空
activityList.clear();
}
}
6.2.1 登录逻辑及界面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal"
android:layout_marginTop="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Account"
android:layout_marginRight="10dp"
android:layout_gravity="center_vertical"/>
<EditText
android:id="@+id/account"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal"
android:layout_marginTop="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Password"
android:layout_marginRight="10dp"
android:layout_gravity="center_vertical"/>
<EditText
android:id="@+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"/>
</LinearLayout>
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Login"
android:textColor="#F30202"/>
</LinearLayout>public class LoginActivity extends BaseActivity {
private EditText accountEt;
private EditText passwordEt;
private Button login;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
accountEt = findViewById(R.id.account);
passwordEt = findViewById(R.id.password);
login = findViewById(R.id.login);
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String account = accountEt.getText().toString();
String password = passwordEt.getText().toString();
if(account.equals("account") && password.equals("123456")){
Intent intent = new Intent(LoginActivity.this, IndexActivity.class);
Bundle bundle = new Bundle();
bundle.putString("account",account);
bundle.putString("password",password);
intent.putExtra("bundle",bundle);
startActivity(intent);
finish();
}
}
});
}
}
6.2.2 首页逻辑及页面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/accountTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/passwordTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_offline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="强制下线"/>
</LinearLayout>public class IndexActivity extends BaseActivity {
private TextView accountTv;
private TextView passwordTv;
private Button offlineBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_index);
accountTv = findViewById(R.id.accountTv);
passwordTv = findViewById(R.id.passwordTv);
offlineBtn = findViewById(R.id.btn_offline);
//获取到登录的账号及密码信息
Intent intent = getIntent();
Bundle bundle = intent.getBundleExtra("bundle");
String account = bundle.getString("account").toString();
String password = bundle.getString("password").toString();
accountTv.setText(account);
passwordTv.setText(password);
offlineBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent offlineIntent = new Intent("com.luckyliuqs.offline_broadcast");
//发送强制下线广播
sendBroadcast(offlineIntent);
}
});
}
}