•何为 broadcast ?
broadcast 直译广播,接下来举个形象的例子来理解下 broadcast;
上学的时候,每个班级都会有一个挂在墙上的大喇叭,用来广播一些通知,比如,开学要去搬书,
广播: "每个班级找几个同学教务处拿书",发出这个广播后,所有同学都会在同一时刻收到这条广播通知,
但并不意味着每个同学都会去搬书,一般去搬书的都是班里的男同学,这些男同学接到这条广播后就会动身去把书搬回教室;
上面这个就是一个广播传递的一个很形象的例子:大喇叭--> 发送广播 --> 所有学生都能收到广播 --> 男同学处理广播 ;
回到我们的概念,其实 broadcast 就是应用程序间的全局大喇叭,即通信的一个手段,
系统自己在很多时候都会发送广播,比如电量改变,插入耳机,输入法改变等等,
发生这些变化时,系统都会发送广播,这个叫系统广播,每个 APP 都会收到;
•广播机制简介
Andorid 中的每个应用可以对自己感兴趣的广播进行注册,这些广播可能是来自于系统的,也可能是来自其他应用程序的;
而接收广播的方法则需要引入一个新的概念——广播接收器(broadcast Receiver),有关广播接收器的用法,将会在下文提及;
Android 中的广播主要可以分为两种类型:标准广播和有序广播;
标准广播
- 一种完全异步执行的广播
- 在广播发出后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息
- 因此它们之间没有先后顺序可言
- 这种广播效率较高,但无法被截断
有序广播
•广播接收器
广播接收器用于响应来自 其他应用程序或者系统 的广播消息,这些消息有时被称为事件或者意图;
广播接收器需要实现为 broadcastReceiver 类的子类,并重写 onReceive() 方法来接收以 Intent 对象为参数的消息;
public class Receiver extends broadcastReceiver { @Override public void onReceive(Context context, Intent intent) { } }
•接收系统广播
Android 内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种 系统的状态信息;
比如手机开机完成后会发出一条广播,电池的电量发生变化会发出一条广播等等;
如果想要接收到这些广播,就需要使用广播接收器;
注册广播的方式一般有两种:
下面,我们通过动态注册的方式编写一个能够监听网络变化的程序;
动态注册监听网络变化
新建一个 broadcastTest 项目,修改 activity_main.xml 中的代码,如下所示;
activity_main.xml
<?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:orientation="vertical" android:gravity="center"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="测试动态注册监听网络变化" android:textSize="20sp" android:textColor="@color/black" /> </LinearLayout>MainActivity.java
public class MainActivity extends AppCompatActivity { private Receiver receiver; private IntentFilter filter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); filter = new IntentFilter(); filter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); receiver = new Receiver(); registerReceiver(receiver,filter); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(receiver); } class Receiver extends broadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context,"网络发生变化",Toast.LENGTH_SHORT).show(); } } }可以看到,我们在 MainActivity 中定义了一个内部类 Receiver,这个类继承自 broadcastReceiver 的,并重写了 onReceive() 方法;
这样,每当网络状态发生变化的时候, onReceive() 方法就会得到执行,这里只是简单的使用 Toast 提示了一段文本信息;
然后,观察 onCreate() 方法,首先,我们创建了一个 IntentFilter 的实例,
并给他添加了一个值为 android.net.conn.CONNECTIVITY_CHANGE 的 action;
之所以使用这个,是因为当网络状态发生变化时,系统发出的广播就是这个;
也就是说,我们的广播接收器想要监听什么广播,就在 filter.addAction(action) 中添加相应的 action;
接下来创建了一个 Receiver 实例,然后调用 registerReceiver(broadcastReceiver receiver,IntentFilter filter) 方法进行注册;
将 Receiver 实例和 IntentFilter 实例传入,这样 Receiver 就会收到所有值为 android.net.conn.CONNECTIVITY_CHANGE 的广播,
也就实现了监听网络变化的功能;
需要注意的是,动态注册的广播接收器,一定要取消注册,也就是在 onDestroy() 方法中调用 unregisterReceiver() 方法;
推荐阅读博文:【广播的registerReceiver() 和 unregisterReceiver()要成对出现】
运行效果
通过运行效果,你会发现,在 开启/关闭 WiFi 的时候,都弹出了 “网络发生变化”;
不过,只提醒网络发生了变化还不够人性化,最好是能准确地告诉用户当前是有网络还是没有网络;
因此我们还需要对上面的代码进行进一步的优化;
MainActivity.java
public class MainActivity extends AppCompatActivity { ... @Override protected void onCreate(Bundle savedInstanceState) {...} @Override protected void onDestroy() {...} class Receiver extends broadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String s = "网络不可用"; ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo info = manager.getActiveNetworkInfo(); if(info != null && info.isAvailable()){ s = "网络可用"; } Toast.makeText(context,s,Toast.LENGTH_SHORT).show(); } } }在 onReceive() 方法中,首先通过 getSystemService() 方法得到了 ConnectivityManager 实例;
这是一个系统服务类,专门用于管理网络连接的;
然后调用他的 getActiveNetworkInfo() 方法可以得到 NetworkInfo 实例;
接着调用 NetworkInfo 的 isAvailable() 方法就可以判断出当前是否有网络了;
需要注意的是,在调用 getSystemService() 的时候,需要提前在 AndroidManifest.xml 文件中添加如下语句:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.broadcasttest"> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application ... <activity android:name=".MainActivity"> ... </activity> </application> </manifest>运行效果
小节
动态注册的广播接收器可以自由的控制 注册与销毁,在灵活性方面有很大的优势,
但是,它也存在一个缺点,即必须在程序启动后才能接收到广播;
静态注册实现开机启动
这次,我们准备让程序接收一条开机广播,当收到这条广播时,
就可以在 onReceive() 方法里执行相应的逻辑,从而实现开机启动的功能;
可以使用 Android Studio 提供的快捷方式来创建一个广播接收器,
右击 com.example.broadcasttest 包 -> New -> Other -> broadcast Receiver;
会弹出如下图所示窗口:
- Class Name : 广播接收器名字
- Exported : 是否允许这个广播接收器接收本程序以外的广播
- Enabled : 是否启用这个广播接收器
勾选这两个属性,点击 Finish 完成创建;
因为我已经创建过 MyReciver 了,所以给我提示文件名重复;
MyReciver.java
public class MyReceiver extends broadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context,"Hi~",Toast.LENGTH_LONG).show(); } }需要注意的是,静态的广播接收器一定要在 AndiroidManifest.xml 文件中注册才可以使用;
AndiroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.broadcasttest"> <!-- 开机启动权限 --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <application ...... <!-- 接收开机启动广播 --> <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"> ...... </activity> </application> </manifest>由于 Android 系统启动完成后会发出一条值为 android.intent.action.BOOT_COMPLETED 的广播,
因此,我们在 <intent-filter> 标签里添加了相应的 action;
另外,监听系统开机广播是需要声明权限的,所以,
我们使用 <uses-permission> 标签加入了 android.permission.RECEIVE_BOOT_COMPLETED 权限;
上述代码添加完成后,编译,运行,然后,重启模拟器,来查看是否设置成功;
运行效果
•发送自定义广播
发送标准广播
上面我们都是接收系统的广播,系统发我们收,我们不能老这么被动,总得主动点是吧;
下面我们就来看下怎么实现;
发送广播前,要先定义一个接收器,接着使用上文新建好的 MyReceiver,内容保持不变;
接下来,在 AndroidManifest.xml 中对这个广播接收器进行修改;
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.broadcasttest"> <application ...... <receiver android:name=".MyReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="MyReceiver"/> </intent-filter> </receiver> <activity android:name=".MainActivity"> <intent-filter> ...... </intent-filter> </activity> </application> </manifest>可以看到,这里让 MyReceiver 接收一条值为 MyReceiver 的广播,因此,待会儿在发送广播的时候,需要发送这样的一条广播;
activity_main.xml
<?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" android:padding="10dp"> <Button android:id="@+id/btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Send broadcast" android:textAllCaps="false" /> </LinearLayout>MainActivity.java
public class MainActivity extends AppCompatActivity { private Button btn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = findViewById(R.id.btn); btn.setonClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { Intent intent = new Intent("MyReceiver"); sendbroadcast(intent); } }); } }可以看到,我们在按钮的点击事件里面加入了发送自定义广播的逻辑;
首先构建出一个 Intent 对象,并把要发送的广播的值传入,
然后调用 Context 的 sendbroadcast() 方法将广播发送出去;
这样所有监听 MyReceiver 广播的广播接收器都会收到消息,此时发出去的广播就是一条标准广播;
运行效果
需要注意的是,如果使用的模拟器 android 版本 ≥ 8.0,那么,还需要在 MainActivity.java 中额外添加一句代码:
intent.setComponent(new ComponentName("com.example.broadcasttest","com.example.broadcasttest.MyReceiver"));
ComponentName() 方法中:
- 第一个参数表示广播接收器所在包名
- 第二个参数表示广播接收器所在类名
MainActivity.java
public class MainActivity extends AppCompatActivity { private Button btn; @Override protected void onCreate(Bundle savedInstanceState) { ...... btn.setonClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { Intent intent = new Intent("MyReceiver"); intent.setComponent(new ComponentName("com.example.broadcasttest","com.example.broadcasttest.MyReceiver")); sendbroadcast(intent); } }); } }这样,在 ≥ 8.0 的 android 版本上,才可以静态发送广播;
另外,由于广播是使用 Intent 进行传递的,因此还可以在 Intent 中携带一些数据传递给广播接收器;
MainActivity.java
public class MainActivity extends AppCompatActivity { private Button btn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = findViewById(R.id.btn); btn.setonClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { Intent intent = new Intent("MyReceiver"); intent.putExtra("greet","Hello"); sendbroadcast(intent); } }); } }我们在 Intent 中保存了键值对 greet -> Hello;
接下来,修改 MyReceiver.java 中的代码调用 greet;
MyReceiver.java
public class MyReceiver extends broadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String s = intent.getStringExtra("greet"); Toast.makeText(context,s,Toast.LENGTH_LONG).show(); } }运行效果
接收其他应用的广播
新建三个项目,分别命名为 broadcastTest1 , broadcastTest2 , broadcastTest3;
让 broadcastTest1 发送值为 com.example.broadcast.a 的广播,
并让 broadcastTest2 和 broadcastTest3 接收值为 com.example.broadcast.a 的广播;
通过 右击 com.example.broadcasttest 包 -> New -> Other -> broadcast Receiver 方式,
分别为 broadcastTest2 和 broadcastTest3 新建一个 MyReceiver 类,该类继承自 broadcastReceiver;
broadcastTest2.MyReceiver.java
public class MyReceiver extends broadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context,"broadcast Receiver 2",Toast.LENGTH_SHORT).show(); } }broadcastTest3.MyReceiver.java
public class MyReceiver extends broadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context,"broadcast Receiver 3",Toast.LENGTH_SHORT).show(); } }broadcastTest2.AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.broadcasttest2"> <application ...... <receiver android:name=".MyReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="com.example.broadcast.a"/> </intent-filter> </receiver> <activity android:name=".MainActivity"> ...... </activity> </application> </manifest>broadcastTest3.AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.broadcasttest3"> <application ...... <receiver android:name=".MyReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="com.example.broadcast.a"/> </intent-filter> </receiver> <activity android:name=".MainActivity"> ...... </activity> </application> </manifest>通过配置文件可以看出,broadcastTest2 和 broadcastTest3 分别接收值为 com.example.broadcast.a 的广播;
broadcastTest1.activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Send broadcast" android:textAllCaps="false" /> </RelativeLayout>broadcastTest1.MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private Button btn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = findViewById(R.id.btn); btn.setonClickListener(this); } @Override public void onClick(View v) { Intent broadcast = new Intent(); broadcast.setAction("com.example.broadcast.a"); sendbroadcast(broadcast); } }通过点击按钮来发送值为 com.example.broadcast.a 广播;
将这三个项目分别安装到模拟器上,并运行,点击 broadcastTest1 中的按钮,观察运行效果;
运行效果
如果你是在 android ≥ 8.0 的模拟器上调试的该项目,需要 broadcastTest1.MainActivity.java 中额外添加 setComponent() 方法;
broadcastTest1.MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ ...... @Override protected void onCreate(Bundle savedInstanceState) { ...... } @Override public void onClick(View v) { Intent broadcast = new Intent(); broadcast.setAction("com.example.broadcast.a"); //参数分别表示 broadcastTest2 中 MyReceiver 所在的的包名和类名 broadcast.setComponent(new ComponentName("com.example.broadcasttest2", "com.example.broadcasttest2.MyReceiver")); sendbroadcast(broadcast); //参数分别表示 broadcastTest3 中 MyReceiver 所在的的包名和类名 broadcast.setComponent(new ComponentName("com.example.broadcasttest3", "com.example.broadcasttest3.MyReceiver")); sendbroadcast(broadcast); } }这样,在 android ≥ 8.0 的模拟器上也可以正常接收广播;