文章目录
9.1 广播机制简介
在前边的学习到Intent可以用来启动一个新的Activity,但是Intent的作用远远不止这些。Intent 还有一个重要的机制就是作为不同进程间传递数据和事件的媒介。
通常我们自己的应用或者是Android系统本身在某些事件来临的时候会将Intent广播出去,而注册的Broadcast Receiver可以监听到这些Intent,并且可以获得保存在Intent里边的数据。
例如,在电池电量发生变化,网络连接发生变化的时候或者来电、来短信的时候,Android 系统都会将相关的Intent进行广播。如果注册了针对这些事件的BroadcastReceiver,那么就可以处理这些事件。
为什么说Android中的广播机制更加灵活呢?这是因为Android中每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会收到自己所关心的广播内容,这些广播可能是来自于系统的,也可能是来自其他应用程序的。Android 提供了一套完整的API,允许应用程序自由地发送和接收广播。发送广播的方法其实之前稍微提到过的,就是借助第4章学过的Intent。而接收广播则需要引入一个新的概念-----广播(Broadcast Receiver)。
广播接收器的具体用法将会在下一节中做介绍,这里我们先来了解一-下广播的类型。Android 中的广播主要可以分为两种类型,标准广播和有序广播。
9.1.1 标准广播(Normal Recevier)
标准广播是一种完全异步执行的广播,在广播发出之后,所有的广播几乎都会在同一时刻接收到这条广播,因此他们之间没有任何先后顺序而言。这种广播的效率比较高,但同时是无法被截断的。标准广播的工作流图如图所示。
9.1.2 有序广播(Order broadcasts)
有序广播则是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到收到广播消息了。有序广播的工作流程图如图所示。
9.2 接收系统广播
Android内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种系统的状态信息。比如手机开机完成后会发出一条广 播,电池的电量发生变化会发出一条广 播,时间或时区发生改变也会发出一条广播等等。如果想要接收到这些广播,就需要使用广播接收器,下面我们就来看一下它的具体用法。
9.2.1 动态注册监听网络变化
广播接收器可以自由地对自已感兴趣的广播进行注册,这样当有相应的广播发出时,广播接收器就能够收到该广播,并在内部处理相应的逻辑。注册广播的方式一般有两种,在代码中注册和在AndroidManifest.xml中注册,其中前者也被称为动态注册,后者也被称为静态注册。
那么该如何创建一个广 播接收器呢?其实只需要新建一个类, 让它继承自BradastRecciver,并重写父类的onReccive()方法就行了。这样当有广播到来时,onReceive()方 法就会得到执行,具体的逻辑就可以在这个方法中处理。
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private NetworkChangeRecevier networkChangeRecevier;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeRecevier=new NetworkChangeRecevier();
registerReceiver(networkChangeRecevier,intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(networkChangeRecevier);
}
class NetworkChangeRecevier extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "网络状态已更改...", Toast.LENGTH_SHORT).show();
}
}
}
可以看到,我们在MainActivity中定义了一个内部类NetworkChangeReceiver,这个类是继承自BroadcastReceiver的,并重写了父类的onReceive()方法。这样每当网络状态发生变化时,onReceive()方 法就会得到执行,这里只是简单地使用Toast提示了一段文本信息。
然后观察onCreate()方法,首先我们创建了一个IntentFiter的实例,并给它添加了一个值为
android.net.conn.CONNECTIVITY_ CHANGE的action,为什么要添加这个值呢?因为当网络状态发生变化时,系统发出的正是一条值为android.net.conn.CONNECTIVITY_ CHANGE 的广播,也就是说我们的广播接收器想要监听什么广播,就在这里添加相应的action就行了。接下来创建了一个NetworkChangeReceiver的实例,然后调用registerReceiver()方法进行注册,将NetworkChangeReceiver的实例和IntentFilter的实例都传了进去,这样NetworkChangeReceiver就会收到所有值为android.net.conn.CONNECTIVITY_ CHANGE的广播,也就实现了监听网络变化的功能。
最后要记得,动态注册的广播接收器一定都要取消注册才行,这里我们是在onDestroy()方法中通过调用unregisterReceiver()方法来实现的。
整体来说,代码还是非常简单的,现在运行一下程序。首先你会在注册完成的时候收到一条广播,然后按下Home键回到主界面(注意不能按Back键,否则onDestroy(方法会执行),接着按下Menu键→Systcm sttings →Datausage进入到数据使用详情界面,然后尝试着开关MobileData来启动和禁用网络,你就会看到有Toast提醒你网络发生了变化。
不过只是提醒网络发生了变化还不够人性化,最好是能准确地告诉用户当前是有网络还是没有网络,因此我们还需要对上面的代码进行进一步的优化。 修改MainActivity中的代码,如下所示:
class NetworkChangeRecevier 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.isAvailable()){
Toast.makeText(context, "网络可用", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(context, "网络不可用", Toast.LENGTH_SHORT).show();
}
}
}
在onReceive()方法中,首先通过getSystemService()方法得到了ConnectivityManager 的实例,这是一个系统服务类,专门用于管理网络连接的。然后调用它的getActiveNetworkInfo()方法 可以得到NetworkInfo的实例,接着调用NetworkInfo的isAvailable(方法,就可以判断出当前是否有网络了,最后我们还是通过Toast的方式对用户进行提示。
另外,这里有非常重要的一点需要说明,Android 系统为了保证应用程序的安全性做了规定,如果程序需要访问一些系统的关键性信息,必须在配置文件中声明权限才可以,否则程序将会直接崩溃,比如这里查询系统的网络状态就是需要声明权限的。打开AndroidManifest.xml文件,在里面加入如下权限就可以查询系统网络状态了:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
访问https:/developer.android.google.cn/reference/android/Manifest.permission.html 可以查看Android系统所有可声明的权限。
9.2.2 静态注册实现开机启动
动态注册的广播接收器可以自由地控制注册与注销,在灵活性方面有很大的优势,但是它也存在着一个缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在onCreate()方法中的。那么有没有什么办法可以让程序在未启动的情况下就能接收到广播呢?这就需要使用静态注册的方式了。
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "开机启动完成", Toast.LENGTH_SHORT).show();
}
}
可以看到,这里不再使用内部类的方式来定义广播接收器,因为稍后我们需要在AndroidManifest.xml中将这个广播接收器的类名注册进去。在onReceive()方法中,还是简单地使用Toast弹出一段提示信息。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jingyi.broadcastdemo">
<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/Theme.Application">
<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>
<receiver android:name=".BootCompleteReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
终于,标签内出现了一 个新的标签,所有静态注册的广播接收器都是在这里进行注册的。它的用法其实和标签非常相似,首先通过android:name来指定具体注册哪一个广 播接收器,然后在标签里加入想要接收的广播就行了,由于Android系统启动完成后会发出一条值为android.intent.action.BOOT_ COMPLETED的广播,因此我们在这里添加了相应的action。
另外,监听系统开机广播也是需要声明权限的,可以看到,我们使用标签又加入了一条android.permission.RECEIVE BOOT_ COMPLETED权限。
然后将模拟器关闭并重新启动,在启动完成之后就会收到开机厂播了
到目前为止,我们在广播接收器的onReceive()方法中都只是简单地使用Toast提示了一段文本信息,当你真正在项目中使用到它的时候,就可以在里面编写自己的逻辑。需要注意的是,不要在onReceive()方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中是不允许开启线程的,当onReceive()方法运行了较长时间而没有结束时,程序就会报错。因此广播接收器更多的是扮演一种打开程序其他组件的角色,比如创建一条状态栏通知,或者启动一个服务等。
注意: Google从Android8.0版本开始,对在清单文件中静态注册广播做了限制。特殊的广播:指那些操作比较频繁的广播事件类型。如:屏幕的开、关广播,电量的变化广播等等这种特殊的广播事件在AndroidManifest.xml中注册是无效的!
因为这种特殊的广播如果在清单文件中注册,会浪费内存资源。你可以想象下,如果有100个应用在清单文件中注册了手机电量变化广播接收者,那当手机电量发生变化时,这100个应用的广播接收者就有可能都运行…那会造成什么结果.所以:只能动态注册(在代码中注册)。
9.3 发送自定义广播
9.3.1 发送标准广播
在发送广播之前,我们还是需要先定义一个广播接收器来准备接收此广播才行,不然发出去也是徒劳的。
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
}
}
这里当MyBroadcastReceiver收到自定义的广播时,就会弹出received in MyBroadcastReceiver的提示。然后在AndroidManifest.xml中对这个广播接收器进行注册:
<!--静态注册一个广播接收者-->
<receiver android:name=".MyBroadcastReceiver"
android:exported="false">
<!--定义一个意图过滤器来接收(监听)指定的action-->
<intent-filter>
<!--配置自定义的action(事件类型)-->
<action android:name="com.android.MY_BROADCAST"/>
</intent-filter>
</receiver>
可以看到,这里让MyBroadcastReceiver接收一条值为com.android. MY_BROADCAST的广播,因此待会儿在发送广播的时候,我们就需要发出这样的一条广 播。
接下来修改activity_ main.xml 中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:text="点击发送自定义广播"
/>
</LinearLayout>
Android 8.0 以前
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button = (Button) findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.android.MY_BROADCAST");
sendBroadcast(intent);
}
});
Android 8.0之后,必须使用全类名方式
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button = (Button) findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.android.MY_BROADCAST");
intent.setComponent(new ComponentName("com.android","com.android.MY_BROADCAST"));
sendBroadcast(intent);
}
});
说明: Android 8版本之后的变化:setComponent(new ComponentName(“目标广播接收器所在应用ID(build.gradle文件中声明的applicationId的值)”, “目标广播接收器类全路径”)); 通常Android的应用ID与包名是绑定的,所以在Android API中,一些方法和参数从名称上看似乎它们返回的是包名,事实上它们返回的是应用ID值.例如,Context.getPackageName()方法返回的是应用ID,而不是包名.
这样所有监听com.android.MY_ BROADCAST 这条广播的广播接收器就会收到消息。此时发出去的广播就是一条标准广播。
这样我们就成功完成了发送自定义广播的功能。另外,由于广播是使用Intent 进行传递的,因此你还可以在Intent中携带一些数据传递给广播接收器。
广播是一种可以跨进程的通信方式,这一点从前面接收系统广播的时候就可以看出来了。因此在我们应用程序内发出的广播,其他的应用程序应该也是可以收到的。
再新建一个Android项目。将项目创建好之后,还需要在这个项目下定义一个广播接收器,用于接收上一小节中的自定义广播。新建AnotherBroadcastReceiver继承自BroadcastReceiver,代码如下所示:
public class AnotherBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in AnotherBroadcastReceiver", Toast.LENGTH_SHORT).show();
}
}
这里仍然是在广播接收器的onReceive)方法中弹出了一段文本信息。然后在AndroidManifest.xml中对这个广播接收器进行注册,代码如下所示:
<receiver android:name=".AnotherBroadcastReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.android.broadcastdemo.MY_BROADCAST"/>
</intent-filter>
</receiver>
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn1"
android:text="发送自定义广播"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn2"
android:text="发送自定义广播使其他应用接收"
/>
</LinearLayout>
MainActivity.java
package com.jingyi.anotherbroadcastdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
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.main);
Button btn1 = findViewById(R.id.btn1);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent1 = new Intent();
intent1.setAction("com.android.broadcstdemo.MY_BROADCAST");
intent1.setComponent(new ComponentName("com.android.anotherbroadcastdemo","com.android.anotherbroadcastdemo.AnotherBroadcastReceiver"));
sendBroadcast(intent1);
}
});
Button btn2 = findViewById(R.id.btn2);
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent2 = new Intent();
intent2.setAction("com.android.MY_BROADCAST");
intent2.setComponent(new ComponentName("com.android.broadcastdemo","com.android.broadcastdemo.MyBroadcastReceiver"));
sendBroadcast(intent2);
}
});
}
}
注意: sctComponent()的第一 个 参数是该应用的applicationId。
运行这两个应用程式,可以看到,AnotherBroadcastReceiver 同样接收的是com.android.broadcstdemo.MY_ BROADCAST这条广播。现在运行BroadcastTest2项目将这个程序安装到模拟器上,然后重新回到BroadcastTest项目的主界面,并点击一下 Send Broadcast按钮,就会分别弹出两次提示信息。
这样就强有力地证明了,我们的应用程序发出的广播是可以被其他的应用程序接收到的。
以上代码只能在安卓8.0运行,因为在安卓9.0之后静态注册的自定义广播无法接收,需要改为动态注册
于是,上面的代码应该为
broadcastdemo项目
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:text="点击发送自定义广播"
/>
</LinearLayout>
MainActivity.java
package com.jingyi.broadcastdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private MyBroadcastReceiver myBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button =findViewById(R.id.btn);
button.setOnClickListener(v -> {
Intent intent = new Intent("com.jingyi.broadcastdemo.MY_BROADCAST");
//intent.setComponent(new ComponentName("com.jingyi.broadcastdemo","com.jingyi.broadcastdemo.MY_BROADCAST"));
sendBroadcast(intent);
});
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.jingyi.broadcastdemo.MY_BROADCAST");
myBroadcastReceiver = new MyBroadcastReceiver();
registerReceiver(myBroadcastReceiver,intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(myBroadcastReceiver);
}
}
MyBroadcastReceiver.java
package com.jingyi.broadcastdemo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("com.jingyi.broadcastdemo.MY_BROADCAST")){
Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
}
}
}
anotherbroadcastdemo项目
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn1"
android:text="发送自定义广播"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn2"
android:text="发送自定义广播使其他应用接收"
/>
</LinearLayout>
AnotherBroadcastReceiver.java
package com.jingyi.anotherbroadcastdemo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class AnotherBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("com.jingyi.broadcastdemo.MY_BROADCAST")){
Toast.makeText(context, "received in AnotherBroadcastReceiver", Toast.LENGTH_SHORT).show();
}
}
}
MainActivity.java
package com.jingyi.anotherbroadcastdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private AnotherBroadcastReceiver anotherBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button btn1 = findViewById(R.id.btn1);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent1 = new Intent();
intent1.setAction("com.jingyi.anotherbroadcastdemo.MY_BROADCAST");
//intent1.setComponent(new ComponentName("com.jingyi.anotherbroadcastdemo","ccom.jingyi.anotherbroadcastdemo.AnotherBroadcastReceiver"));
sendBroadcast(intent1);
}
});
IntentFilter intentFilter1 = new IntentFilter();
intentFilter1.addAction("com.jingyi.anotherbroadcastdemo.MY_BROADCAST");
anotherBroadcastReceiver = new AnotherBroadcastReceiver();
registerReceiver(anotherBroadcastReceiver,intentFilter1);
Button btn2 = findViewById(R.id.btn2);
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent2 = new Intent();
intent2.setAction("com.jingyi.broadcastdemo.MY_BROADCAST");
//intent2.setComponent(new ComponentName("com.jingyi.broadcastdemo","com.jingyi.broadcastdemo.MyBroadcastReceiver"));
sendBroadcast(intent2);
}
});
IntentFilter intentFilter2= new IntentFilter();
intentFilter2.addAction("com.jingyi.broadcastdemo.MY_BROADCAST");
anotherBroadcastReceiver = new AnotherBroadcastReceiver();
registerReceiver(anotherBroadcastReceiver,intentFilter2);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(anotherBroadcastReceiver);
}
}
运行结果
会得到两条消息
9.3.2 发送有序广播
Android版本8.0之前(不包含android8.0 版本),只需要在清单文件中静态配置就可以。
在配置文件中(AndroidManifest.xml),如果没有设置优先级(android:priority= “XXX’”),那么接收到广播的先后顺序会根据配置文件中书写的顺序产生变化,接收顺序会由上而下。这种接收顺序不可取,所以,为了避免这种情况,有序广播的接收者必须配置优先级,防止接收顺序错乱。
priority的优先级:最高1000 ~最低 -1000
静态注册代码如下:
<receiver android :name= " . receiver.Test1Receiver">
<intent -filter android ; priority= "1000" >
<action android:name="x. xx.xxx.有序" />
</intent-filter>
</receiver>
<receiver android : name= " . receiver. Test2Receiver">
<intent -filter android: priority= "999">
<action android:name="x. xx.xxx.有序" />
</intent-filter>
</receiver>
<receiver android : name="。receiver.Test3Receiver">
< intent-filter android: priority= "998">
<action android:name= "x. xx.xxx.有序"/>
</intent-filter>
</receiver>
从Android版本8.0开始,由于Google对清单文件中静态注册广播接收者做了限制,只能通过动态注册的方式,实现有序广播。
动态注册代码如下:
IntentFilter filter2 = new IntentFilter();
filter2.addAction("x.xx. xxx.有序");
filter2.setPriority(999);
this.registerReceiver (new Test2Receiver(), filter2);
IntentFilter filter1 = new IntentFilter();
filter1.addAction("x. xx.xxx.有序");
filter1.setPriority(1000);
this.registerReceiver(new Test1Receiver(), filter1);
IntentFilter filter3 = new IntentFilter();
filter3. addAction("x.xx. xxx.有序");
filter3. setPriority(998);
this.registerReceiver ( new Test3Receiver(), filter3);
有序广播例子
Main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:text="点击发送有序广播"
android:layout_gravity="center"
/>
</LinearLayout>
MainActivity.java
package com.jingyi.orderbroadcast;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private OrderBroadcastReceiver orderBroadcastReceiver;
private OrderBroadcastReceiver2 orderBroadcastReceiver2;
private OrderBroadcastReceiver3 orderBroadcastReceiver3;
private OrderBroadcastReceiver4 orderBroadcastReceiver4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button =findViewById(R.id.btn);
button.setOnClickListener(v -> {
Intent intent = new Intent();
intent.setAction("com.jingyi.orderbroadcast.MY_OrderedBROADCAST");
sendOrderedBroadcast(intent,null);//第一个参数是intent,第二个参数是一个与权限相关的字符串
});
orderBroadcastReceiver = new OrderBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.jingyi.orderbroadcast.MY_OrderedBROADCAST");
intentFilter.setPriority(88);//设置优先级
registerReceiver(orderBroadcastReceiver,intentFilter);
//orderBroadcastReceiver2优先级比orderBroadcastReceiver高
orderBroadcastReceiver2 = new OrderBroadcastReceiver2();
IntentFilter intentFilter2 = new IntentFilter();
intentFilter2.addAction("com.jingyi.orderbroadcast.MY_OrderedBROADCAST");
intentFilter2.setPriority(100);//设置优先级
registerReceiver(orderBroadcastReceiver2,intentFilter2);
//orderBroadcastReceiver3在接收到广播之后会截断广播
orderBroadcastReceiver3 = new OrderBroadcastReceiver3();
IntentFilter intentFilter3 = new IntentFilter();
intentFilter3.addAction("com.jingyi.orderbroadcast.MY_OrderedBROADCAST");
registerReceiver(orderBroadcastReceiver3,intentFilter3);
//orderBroadcastReceiver4接收不到广播
orderBroadcastReceiver4 = new OrderBroadcastReceiver4();
IntentFilter intentFilter4 = new IntentFilter();
intentFilter4.addAction("com.jingyi.orderbroadcast.MY_OrderedBROADCAST");
registerReceiver(orderBroadcastReceiver4,intentFilter4);
}
}
OrderBroadcastReceiver.java
public class OrderBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Receiver in OrderBroadcastReceiver1", Toast.LENGTH_SHORT).show();
}
}
OrderBroadcastReceiver2.java
public class OrderBroadcastReceiver2 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Receiver in OrderBroadcastReceiver2", Toast.LENGTH_SHORT).show();
}
}
OrderBroadcastReceiver3.java
public class OrderBroadcastReceiver3 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Receiver in OrderBroadcastReceiver3", Toast.LENGTH_SHORT).show();
abortBroadcast();//截断广播
}
}
OrderBroadcastReceiver4.java
public class OrderBroadcastReceiver4 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Receiver in OrderBroadcastReceiver4", Toast.LENGTH_SHORT).show();
}
}
可以看到,我们在MainActivity类中动态注册广播接收器时,四个android:priority属性给广播接收器设置了优先级,优先级比较高的广播接收器就可以先收到广播。这里将OrderedBroadcastReceiver2的优先级设成了100, 以保证它一定 会在orderedBroadcastReceiver和orderedBroadcastReceiver3之前收到广播。
既然已经获得了接收广播的优先权,那么orderedBroadcastReceiver3就可以选择是否允许广播继续传递了。orderedBroadcatReceiver3就可以决定广播是否继续向后传递。如果在onReceive()方法中调用了abortBroadcat()方法,就表示将这条广播截断,后面的广播接收器将无法再接收到这条广播。
运行结果:
Receiver in OrderBroadcastReceiver2
Receiver in OrderBroadcastReceiver1
Receiver in OrderBroadcastReceiver3
9.4 使用本地广播
前面我们发送和接收的广播全部都是属于系统全局广播,即发出的广播可以被其他任何的任何应用程序接收到,并且我们也可以接收来自于其他任何应用程序的广播。这样就很容易会引起安全性的问题,比如说我们发送的一些携带关键性数据的广播有可能被其他的应用程序截获,或者其他的程序不停地向我们的广播接收器里发送各种垃圾广播。
为了能够简单地解决广播的安全性问题,Android 引入了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播,这样所有的安全性问题就都不存在了。
本地广播的用法并不复杂,主要就是使用了一个LocalBroadcastManager来对广播进行管理,并提供了发送广播和注册广播接收器的方法。下面我们就通 过具体的实例来尝试一下 它的用法。
Main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:text="点击发送一条本地广播信息"
android:layout_gravity="center"
/>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private LocalBroadcastReceiver localBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button =findViewById(R.id.btn);
localBroadcastReceiver = new LocalBroadcastReceiver();
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction("com.jingyi.localbroadcast.MY_LOCALBROADCAST");
LocalBroadcastManager.getInstance(MainActivity.this).sendBroadcast(intent);
}
});
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.jingyi.localbroadcast.MY_LOCALBROADCAST");
LocalBroadcastManager.getInstance(MainActivity.this).registerReceiver(localBroadcastReceiver,intentFilter);
}
class LocalBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "这是一条本地广播的信息", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(MainActivity.this).unregisterReceiver(localBroadcastReceiver);
}
}
有没有感觉这些代码很熟悉?没错,其实这基本上就和我们前面所学的动态注册广播接收器以及发送广播的代码是一样。 只不过现在首先是通过LocalBroadcastManager的gtnstance()方法得到了它的一个实例, 然后在注册广播接收器的时候调用的是LocalBroadcastManager 的registrReceiver0方法,在发送广播的时候调用的是LocalBroadcastManager的sendBroadcast()方法,仅此而已。这里我们在按钮的点击事件里面发出了一条
com.android.localbroadcastmanager.MY_ LOCALBROADCAST广播,然后在LocalReceiver里去接收这条广播。
运行程序可以看到,LocalReceiver 成功接收到了
这条本地广播,并通过Toast提示了出来。如果你还有兴趣进行实验,可以尝试在BroadcastTest2中也去接收这条广播,答案是显而易见的,肯定无法收到,因"com. android.localbroadcastmanager.MY_ LOCALBROADCAST为这条广播只会在BroadcastTest程序内传播。
另外还有一点需要说明,本地广播是无法通过静态注册的方式来接收的。其实这也完全可以理解,因为静态注册主要就是为了让程序在未启动的情况下也能收到广播,而发送本地广播时,我们的程序肯定是已经启动了,因此也完全不需要使用静态注册的功能。最后我们再来盘点一下使用本地广播的几点优势吧。
- 可以明确地知道正在发送的广播不会离开我们的程序,因此不需要担心机密数据泄漏的问题。
- 其 他的程序无法将广播发送到我们程序的内部,因此不需要担心会有安全漏洞的隐患。
- 发送本地广播比起发送系统全局广播将会更加高效。
练习9
应用自启动后,在后台检查如下功能:
- 检查系统属性是否与预置的值一致(预置的值可以放在assets的一个xml文件里面)
- 检查开机完成时候开机完成后10分钟系统的内存使用情况
应用中增加界面提供随时查询如上结果。
systemservicelayout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView2"
/>
<ProgressBar
android:id="@+id/bar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:progressBarStyleHorizontal"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView3"
/>
<ProgressBar
android:id="@+id/bar2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:progressBarStyleHorizontal"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView4"
/>
<ProgressBar
android:id="@+id/bar3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:progressBarStyleHorizontal"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/linearLayout2"
></LinearLayout>
</LinearLayout>
SystemBootCompleteReceiver.java
package com.jingyi.broadcastdemo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class SystemBootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "系统启动完成,开机自检服务启动...", Toast.LENGTH_SHORT).show();
Intent service = new Intent(context, SystemCheckedService.class);//启动服务
context.startService(service);
}
}
SystemCheckedService.java
package com.jingyi.broadcastdemo;
import android.app.ActivityManager;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.LongFunction;
public class SystemCheckedService extends Service {
private Thread thread;
int x = 0;
/*private final Binder binder=new SystemCheckedService.LocalBinder();
public class LocalBinder extends Binder{
SystemCheckedService getSystemCheckedService(){
return SystemCheckedService.this;//返回当前服务实例
}
}*/
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
//发送系统资源检查结果广播
Intent intent = new Intent("com.jingyi.work1_login.SYSYTEM_PROPERTIES");
String brand = getProperties("ro.product.odm.brand");
String version_sdk = getProperties("ro.system.build.version.sdk");
intent.putExtra("Brand", brand);
intent.putExtra("VERSION_SDK", version_sdk);
sendBroadcast(intent);
//发送系统内存结果广播
Timer timer = new Timer();
TimerTask timerTask = new TimerTask(){
@Override
public void run() {
Intent intent2 = new Intent("com.jingyi.work1_login.SYSTEM_MEMORY");
HashMap<String, Object> map2 = getMemoryInfo();
intent2.putExtra("time", x);
intent2.putExtra("System_Memory", map2);
sendBroadcast(intent2);
x++;
}
};
timer.schedule(timerTask,0,60000);//立即执行并每60000毫秒执行一次
if (x>10){
timer.cancel();//十分钟后任务终止
}
}
public String getProperties(String type) {
String result = SystemPropertiesUtil.get(type);
return result;
}
public HashMap<String, Object> getMemoryInfo() {
ActivityManager activityManager = (ActivityManager) this.getSystemService(this.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
HashMap<String, Object> map = new HashMap<>();
int maxMemory = (int) (Runtime.getRuntime().maxMemory() * 1.0 / (1024 * 1024));
map.put("MaxMemory", maxMemory);
int totalMemory = (int) (Runtime.getRuntime().totalMemory() * 1.0 / (1024 * 1024));
map.put("TotalMemory", totalMemory);
int freeMemory = (int) (Runtime.getRuntime().freeMemory() * 1.0 / (1024 * 1024));
map.put("FreeMemory", freeMemory);
String nowMemory = (memoryInfo.availMem / (1024 * 1024) )+ "MB";
Log.i("NOW",nowMemory);
map.put("NowMemory", nowMemory);
return map;
}
}
SystemPropertiesUtil.java
package com.jingyi.broadcastdemo;
import java.lang.reflect.Method;
/**工具类
* 获取系统对应的属性值,使用android.os.SystemProperties这个类反射完成
*/
public class SystemPropertiesUtil {
private static final String TAG = "MySystemProperties";
public static String get(String key) {
init();
String value = null;
try {
value = (String) mGetMethod.invoke(mClassType, key);
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
public static int getInt(String key, int def) {
init();
int value = def;
try {
Integer v = (Integer) mGetIntMethod.invoke(mClassType, key, def);
value = v.intValue();
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
public static int getSdkVersion() {
return getInt("ro.build.version.sdk", -1);
}
//-------------------------------------------------------------------
private static Class<?> mClassType = null;
private static Method mGetMethod = null;
private static Method mGetIntMethod = null;
private static void init() {
try {
if (mClassType == null) {
mClassType = Class.forName("android.os.SystemProperties");
mGetMethod = mClassType.getDeclaredMethod("get", String.class);
mGetIntMethod = mClassType.getDeclaredMethod("getInt", String.class, int.class);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
SystemServiceActivity.java
package com.jingyi.broadcastdemo;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.Nullable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
public class SystemServiceActivity extends Activity {
private MyBroadcastReceiver myBroadcastReceiver;
private AnotherBroadcastReceiver anotherBroadcastReceiver;
private TextView textView, textView2, textView3, textView4;
private ProgressBar bar1, bar2, bar3;
private LinearLayout linearLayout;
private HashMap<String, String> map;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.systemservicelayout);
Intent service = new Intent(this, SystemCheckedService.class);//启动服务
this.startService(service);
textView = findViewById(R.id.textView);
textView2 = findViewById(R.id.textView2);
textView3 = findViewById(R.id.textView3);
textView4 = findViewById(R.id.textView4);
bar1 = findViewById(R.id.bar1);
bar2 = findViewById(R.id.bar2);
bar3 = findViewById(R.id.bar3);
linearLayout = findViewById(R.id.linearLayout2);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.jingyi.work1_login.SYSYTEM_PROPERTIES");
myBroadcastReceiver = new MyBroadcastReceiver();
registerReceiver(myBroadcastReceiver, intentFilter);
IntentFilter intentFilter2 = new IntentFilter();
intentFilter2.addAction("com.jingyi.work1_login.SYSTEM_MEMORY");
anotherBroadcastReceiver = new AnotherBroadcastReceiver();
registerReceiver(anotherBroadcastReceiver, intentFilter2);
map = getMyProperties("set.xml");
}
//接收服务的广播消息
class MyBroadcastReceiver extends BroadcastReceiver {
@SuppressLint("SetTextI18n")
@Override
public void onReceive(Context context, Intent intent) {
String brand = intent.getStringExtra("Brand");
String version_sdk = intent.getStringExtra("VERSION_SDK");
for (String key : map.keySet()) {
if (key.equals("Brand")) {
textView.append("[Brand]" + "预设值:" + map.get(key) + ",实际值:" + brand + ",是否一致:" + (map.get(key).equals(brand) ? "是" : "否") + "\n");
} else {
textView.append("[VERSION_SDK]" + "预设值:" + map.get(key) + ",实际值:" + version_sdk + ",是否一致:" + (map.get(key).equals(version_sdk) ? "是" : "否"));
}
}
}
}
class AnotherBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
HashMap<String, Object> map2 = (HashMap<String, Object>) intent.getSerializableExtra("System_Memory");
int time = intent.getIntExtra("time", 0);
for (String key : map2.keySet()) {
if (key.equals("MaxMemory")) {
textView2.setText("虚拟机分配的最大内存为:" + map2.get("MaxMemory"));
bar1.setMax(20);
bar1.setProgress((Integer) map2.get("MaxMemory"));
}
if (key.equals("TotalMemory")) {
textView3.setText("虚拟机已分配内存为:" + map2.get("TotalMemory"));
bar2.setMax(20);
bar2.setProgress((Integer) map2.get("TotalMemory"));
}
if (key.equals("FreeMemory")) {
textView4.setText("虚拟机已分配的内存中未使用内存为:" + map2.get("FreeMemory"));
bar3.setMax(20);
bar3.setProgress((Integer) map2.get("FreeMemory"));
}
if (key.equals("NowMemory")) {
TextView tv = new TextView(SystemServiceActivity.this);
tv.setText("[" + time + "]" + "min,系统剩余RAM为:" + map2.get("NowMemory"));
linearLayout.addView(tv);
}
}
}
}
/*
* 获取预设配置
* */
public HashMap<String, String> getMyProperties(String file) {
HashMap<String, String> map = new HashMap<>();
try {
//传入文件名:settings.xml;用来获取流
InputStream is = getAssets().open(file);
//首先创造:DocumentBuilderFactory对象
DocumentBuilderFactory dBuilderFactory = DocumentBuilderFactory.newInstance();
//获取:DocumentBuilder对象
DocumentBuilder dBuilder = dBuilderFactory.newDocumentBuilder();
//将数据源转换成:document 对象
Document document = dBuilder.parse(is);
//获取根元素
Element element = (Element) document.getDocumentElement();
//获取子对象的数值 读取item标签的内容
NodeList nodeList = element.getElementsByTagName("item");
for (int i = 0; i < nodeList.getLength(); i++) {
//获取对应的对象
Element item = (Element) nodeList.item(i);
String name = item.getElementsByTagName("name").item(0).getTextContent();
String value = item.getElementsByTagName("value").item(0).getTextContent();
map.put(name, value);
}
} catch (IOException | ParserConfigurationException | SAXException e) {
e.printStackTrace();
}
return map;
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(myBroadcastReceiver);
unregisterReceiver(anotherBroadcastReceiver);
}
}
assets/set.xml
<resources>
<item id="1">
<name>Brand</name>
<value>Meizu</value>
</item>
<item id="2">
<name>VERSION_SDK</name>
<value>29</value>
</item>
</resources>
Androidmanifest.xml
<service android:name=".SystemCheckedService"></service>
<receiver android:name=".SystemBootCompleteReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>