简介
某个网络的IP范围是192.168.0.XXX,子网 掩码是255.255.255.0,那么这个网络的广播地址就是192.168.0.255。广播数据包会被发送到同一 网络上的所有端口,这样在该网络中的每台主机都将会收到这条广播。为了便于进行系统级别的消息通知,Android也引入了一套类似的广播消息机制。这是因为Android中的每个应用程序都可以对自己感 兴趣的广播进行注册,这样该程序就只会接收到自己所关心的广播内容,这些广播可能是来自 于系统的,也可能是来自于其他应用程序的。Android提供了一套完整的API,允许应用程序自 由地发送和接收广播。发送广播的方法其实之前稍微提到过,如果你记性好的话可能还会有印 象,就是借助我们第2章学过的Intent。而接收广播的方法则需要引入一个新的概念------广播接 收器(Broadcast Receiver)。广播接收器的具体用法将会在下一节中做介绍,这里我们先来了解一下广播的类型。Android中 的广播主要可以分为两种类型:标准广播 和有序广播。
一、广播的方式
标准广播:
标准广播 (Normal broadcasts)是一种完全异步执行的广播,在广播发出之后,所有的广播 接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。 这种广播的效率会比较高,但同时也意味着它是无法被截断的。标准广播的工作流程如图所示。
有序广播:
有序广播 (Ordered broadcasts)则是一种同步执行的广播,在广播发出之后,同一时刻只 会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广 播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以 先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接 收器就无法收到广播消息了。有序广播的工作流程如图所示。
二、动态注册监听网络变化
Android内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种系统 的状态信息。比如手机开机完成后会发出一条广播,电池的电量发生变化会发出一条广播,时 间或时区发生改变也会发出一条广播,等等。如果想要接收到这些广播,就需要使用广播接收 器,下面我们就来看一下它的具体用法。广播接收器可以自由地对自己感兴趣的广播进行注册,这样当有相应的广播发出时,广播接收 器就能够收到该广播,并在内部处理相应的逻辑。注册广播的方式一般有两种,在代码中注册(动态注册) 和在AndroidManifest.xml中注册(静态注册)。
三、动态注册
1.定义NetworkChangeReceived监听网络状态变化
第一种代码逻辑:
java
public class NetworkChangeReceived extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
boolean isConnected = isNetworkConnected(context);
String statusMessage = isConnected ? "连接" : "断开";
String networkType = NetworkType(context);
Toast.makeText(context, "网络已" + statusMessage + ",类型" + networkType, Toast.LENGTH_SHORT).show();
}
}
private boolean isNetworkConnected(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
}
private String NetworkType(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null ? activeNetwork.getTypeName() : "未知";
}
}
/*代码解释*/
// 定义一个BroadcastReceiver子类,用于监听网络状态变化
public class NetworkChangeReceived extends BroadcastReceiver {
// 当接收到广播时调用此方法
@Override
public void onReceive(Context context, Intent intent) {
// 检查Intent的动作是否为网络连接状态改变
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
// 调用私有方法检查网络是否连接
boolean isConnected = isNetworkConnected(context);
// 根据网络连接状态构建提示消息
String statusMessage = isConnected ? "连接" : "断开";
// 获取网络类型
String networkType = NetworkType(context);
// 使用Toast显示网络状态和类型
Toast.makeText(context, "网络已" + statusMessage + ",类型" + networkType, Toast.LENGTH_SHORT).show();
}
}
// 私有方法,用于检查网络是否处于连接状态
private boolean isNetworkConnected(Context context) {
// 获取系统的ConnectivityManager服务
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
// 获取当前活动的网络信息
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
// 判断网络信息是否非空并且网络正在连接或已经连接
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
}
// 私有方法,用于获取网络类型
private String NetworkType(Context context) {
// 获取系统的ConnectivityManager服务
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
// 获取当前活动的网络信息
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
// 如果网络信息非空,返回网络类型名;否则返回"未知"
return activeNetwork != null ? activeNetwork.getTypeName() : "未知";
}
}
第二种代码逻辑:
java
public class NetworkChangeReceived extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
Toast.makeText(context,checkNetworkConnectionAndGetType(context), Toast.LENGTH_SHORT).show();
}
}
public static String checkNetworkConnectionAndGetType(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null && info.isConnectedOrConnecting()) {
// 网络已连接,返回网络类型名称
return "连接"+info.getTypeName();
} else {
// 未连接网络
return "断开";
}
}
}
/*代码解释*/
/**
* NetworkChangeReceived 类,继承自 BroadcastReceiver,用于监听和响应网络状态变化的广播事件。
*/
public class NetworkChangeReceived extends BroadcastReceiver {
/**
* 当接收到广播时调用此方法。
*
* @param context 上下文,提供了应用程序的环境信息。
* @param intent Intent 对象,包含了广播的数据和动作。
*/
@Override
public void onReceive(Context context, Intent intent) {
/**
* 检查 Intent 的动作是否是网络状态改变的广播。
* 如果是,则获取网络状态和类型的信息,并通过 Toast 显示给用户。
*/
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
Toast.makeText(context, checkNetworkConnectionAndGetType(context), Toast.LENGTH_SHORT).show();
}
}
/**
* 检查当前设备的网络连接状态,并返回连接状态和网络类型。
*
* @param context 上下文,提供了应用程序的环境信息。
* @return 返回一个字符串,描述了当前网络的连接状态和类型。
*/
public static String checkNetworkConnectionAndGetType(Context context) {
// 从系统服务中获取 ConnectivityManager 实例,用于管理网络连接。
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
// 获取当前活动的网络信息。
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null && info.isConnectedOrConnecting()) {
// 如果网络已连接,返回网络类型名称,并且前面加上"连接"两个字。
return "连接" + info.getTypeName();
} else {
// 如果网络未连接,返回"断开"。
return "断开";
}
}
}
2.Mainactivity中应用NetworkChangeReceived
java
public class MainActivity extends AppCompatActivity {
private NetworkChangeReceived networkChangeReceived;
private static final int PERMISSION_REQUEST_CODE = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
if (ContextCompat.checkSelfPermission(this,
android.Manifest.permission.ACCESS_NETWORK_STATE)!=PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_NETWORK_STATE}, PERMISSION_REQUEST_CODE);
}
else {
registerReceiver();
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE){
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
registerReceiver();
}
else {
Toast.makeText(this, "没有网络权限,无法检测网络状态", Toast.LENGTH_SHORT).show();
}
}
}
private void registerReceiver(){
networkChangeReceived = new NetworkChangeReceived();
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(networkChangeReceived,filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (networkChangeReceived != null) {
unregisterReceiver(networkChangeReceived);
}
}
}
/*代码解释*/
/**
* MainActivity 类,继承自 AppCompatActivity,是应用的主要活动入口点。
*/
public class MainActivity extends AppCompatActivity {
// 声明一个 NetworkChangeReceived 对象,用于接收网络状态变化的广播。
private NetworkChangeReceived networkChangeReceived;
// 定义一个常量,用于请求权限时的请求码。
private static final int PERMISSION_REQUEST_CODE = 1;
/**
* 在 Activity 创建时调用。
*
* @param savedInstanceState 可能包含先前实例状态的 Bundle 对象。
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this); // 这里假设 EdgeToEdge 是一个库的方法,用于设置全屏边缘到边缘的界面。
setContentView(R.layout.activity_main); // 设置 Activity 的布局资源。
// 检查是否已有访问网络状态的权限。
if (ContextCompat.checkSelfPermission(this,
android.Manifest.permission.ACCESS_NETWORK_STATE) != PackageManager.PERMISSION_GRANTED) {
// 请求访问网络状态的权限。
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_NETWORK_STATE}, PERMISSION_REQUEST_CODE);
} else {
// 如果已经有权限,则注册网络状态变化的广播接收器。
registerReceiver();
}
}
/**
* 当权限请求的结果返回时调用。
*
* @param requestCode 请求码,用于识别哪个权限请求的回调。
* @param permissions 请求的权限数组。
* @param grantResults 权限请求的结果数组。
*/
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 如果权限被授予,注册网络状态变化的广播接收器。
registerReceiver();
} else {
// 如果权限被拒绝,显示一个 Toast 提示用户。
Toast.makeText(this, "没有网络权限,无法检测网络状态", Toast.LENGTH_SHORT).show();
}
}
}
/**
* 注册网络状态变化的广播接收器。
*/
private void registerReceiver() {
networkChangeReceived = new NetworkChangeReceived();
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(networkChangeReceived, filter);
}
/**
* 在 Activity 销毁时调用。
*/
@Override
protected void onDestroy() {
super.onDestroy();
if (networkChangeReceived != null) {
// 在 Activity 销毁前,取消注册网络状态变化的广播接收器。
unregisterReceiver(networkChangeReceived);
}
}
}
四、静态注册实现开机启动
动态注册的广播接收器可以自由地控制注册与注销,在灵活性方面有很大的优势,但是它也存 在着一个缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写 在onCreate() 方法中的。这里我们准备让程序接收一条开机广播,当收到这条广播时就可以在onReceive() 方法里执 行相应的逻辑,从而实现开机启动的功能。可以使用Android Studio提供的快捷方式来创建一个 广播接收器,右击com.example.broadcasttest包→New→Other→Broadcast Receiver。
创建广播接收器的窗口
Exported 属性表示是否允许 这个广播接收器接收本程序以外的广播,Enabled 属性表示是否启用这个广播接收器。勾选这 两个属性,点击Finish完成创建。
自动新建一个类,并继承BroadcastReceiver
java
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "My Receiver", Toast.LENGTH_LONG).show();
}
}
五、AndroidManifest.xml文件中注册
静态的广播接收器一定要在AndroidManifest.xml文件中注册才可以使用,不过由于我们是 使用Android Studio的快捷方式创建的广播接收器,因此注册这一步已经被自动完成了。打开 AndroidManifest.xml文件瞧一瞧,代码如下所示:
html
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31">
<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"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<!-- 代码解释 -->
<?xml version="1.0" encoding="utf-8"?>
<!-- 指定文档的版本和编码方式 -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 声明命名空间,定义了 Android 和工具属性的前缀 -->
<!-- 声明应用需要的权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 允许应用访问网络状态 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- 允许应用接收设备启动完成的广播 -->
<application
android:allowBackup="true"
<!-- 应用的数据是否允许被备份 -->
android:dataExtractionRules="@xml/data_extraction_rules"
<!-- 数据提取规则,定义哪些数据可以被提取 -->
android:fullBackupContent="@xml/backup_rules"
<!-- 完整备份内容规则,定义哪些数据应该包含在完整备份中 -->
android:icon="@mipmap/ic_launcher"
<!-- 应用图标资源的引用 -->
android:label="@string/app_name"
<!-- 应用的显示名称 -->
android:roundIcon="@mipmap/ic_launcher_round"
<!-- 圆形应用图标资源的引用,适用于圆形图标显示的场景 -->
android:supportsRtl="true"
<!-- 是否支持从右到左的语言布局方向 -->
android:theme="@style/Theme.MyApplication"
<!-- 应用主题的样式引用 -->
tools:targetApi="31">
<!-- 工具属性,指定应用的目标 API 级别 -->
<!-- 定义一个广播接收器 -->
<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"
android:exported="true">
<!-- 活动的类名和是否允许外部应用启动 -->
<intent-filter>
<!-- 定义活动能够响应的意图类型 -->
<action android:name="android.intent.action.MAIN" />
<!-- 活动是应用的入口点 -->
<category android:name="android.intent.category.LAUNCHER" />
<!-- 活动属于 Launcher 类别,意味着它会出现在应用启动器中 -->
</intent-filter>
</activity>
</application>
</manifest>
由于Android系统启动完成后会发出一条值为android.intent.action.BOOT_COMPLETED 的广播,因此我们在 标签里添加了相应的action。另外,监听系统开机广播 也是需要声明权限的,可以看到,我们使用 标签又加入了一 条android.permission.RECEIVE_BOOT_COMPLETED 权限。目前为止,我们在广播接收器的onReceive() 方法中都只是简单地使用Toast提示了一段文 本信息,当你真正在项目中使用到它的时候,就可以在里面编写自己的逻辑。需要注意的是, 不要在onReceive() 方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中 是不允许开启线程的,当onReceive() 方法运行了较长时间而没有结束时,程序就会报错。 因此广播接收器更多的是扮演一种打开程序其他组件的角色,比如创建一条状态栏通知,或者 启动一个服务等,