•何为 Broadcast ?

  Broadcast 直译广播,接下来举个形象的例子来理解下 Broadcast;

  上学的时候,每个班级都会有一个挂在墙上的大喇叭,用来广播一些通知,比如,开学要去搬书,

  广播: "每个班级找几个同学教务处拿书",发出这个广播后,所有同学都会在同一时刻收到这条广播通知,

  但并不意味着每个同学都会去搬书,一般去搬书的都是班里的男同学,这些男同学接到这条广播后就会动身去把书搬回教室;

  上面这个就是一个广播传递的一个很形象的例子:大喇叭--> 发送广播 --> 所有学生都能收到广播 --> 男同学处理广播 ;

  回到我们的概念,其实 Broadcast 就是应用程序间的全局大喇叭,即通信的一个手段,

  系统自己在很多时候都会发送广播,比如电量改变,插入耳机,输入法改变等等,

  发生这些变化时,系统都会发送广播,这个叫系统广播,每个 APP 都会收到;

•广播机制简介

  Andorid 中的每个应用可以对自己感兴趣的广播进行注册,这些广播可能是来自于系统的,也可能是来自其他应用程序的;

  而接收广播的方法则需要引入一个新的概念——广播接收器(Broadcast Receiver),有关广播接收器的用法,将会在下文提及;

  Android 中的广播主要可以分为两种类型:标准广播和有序广播;

  • 标准广播

    • 一种完全异步执行的广播
    • 在广播发出后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息
    • 因此它们之间没有先后顺序可言
    • 这种广播效率较高,但无法被截断
  • 有序广播

    • 一种同步执行的广播
    • 在广播发出之后,同一时刻只会有一个广播接收器能收到这条广播消息
    • 当该广播接收器中的逻辑执行完毕后,广播才会继续传递
    • 因此广播接收器有先后顺序,通过  android:priority  来设置优先级
    • android:priority  参数取值范围为 [-1000,1000]
      • 无论是静态接收器还是动态接收器,优先级高的先接收
      • 当优先级相同时,动态的接收器优先于静态接收器接收
      • 当优先级和注册方式都相同时:先注册的接收器先接收
      • 先接收广播的广播接收器还可以截断正在传递的广播

  

  有关同步异步的理解,移步这篇博客【异步和同步的区别】;

•广播接收器

  广播接收器用于响应来自 其他应用程序或者系统 的广播消息,这些消息有时被称为事件或者意图;

  广播接收器需要实现为 BroadcastReceiver 类的子类,并重写 onReceive() 方法来接收以 Intent 对象为参数的消息;

public class Receiver extends BroadcastReceiver {

    @Override
public void onReceive(Context context, Intent intent) { }
}

  上述代码就创建了一个广播接收器;

•接收系统广播 

  Android 内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种 系统的状态信息;

  比如手机开机完成后会发出一条广播,电池的电量发生变化会发出一条广播等等;

  如果想要接收到这些广播,就需要使用广播接收器;

  注册广播的方式一般有两种:

  • 在代码中注册,也被称为动态注册
  • 在  AndroidManifest.xml  中注册,也被称为静态注册

  下面,我们通过动态注册的方式编写一个能够监听网络变化的程序;

动态注册监听网络变化

  新建一个 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 中的代码;

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 中的代码;

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 中的代码;

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 中的代码;

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 中的代码;

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 中的代码;

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 中的代码;

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;

  并修改 MyReceiver.java 中的代码;

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();
}
}

  接下来修改 AndroidManifest.xml 文件;

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 中的代码;

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 的模拟器上也可以正常接收广播;

  在解决 android ≥ 8.0 的模拟器上接收其他 App 的广播的时候,翻阅到了这篇博客;

  虽然对我解决这个问题并没有帮助,但是以后遇到这种 bug 可以尝试一下该方法,记录一下;

•使用广播的注意事项

  不要在广播里添加过多逻辑或者进行任何耗时操作,因为在广播中是不允许开辟线程的;

  当  onReceiver()  方法运行较长时间(超过10秒)还没有结束的话,那么程序会报错(ANR);

  广播更多的时候扮演的是一个打开其他组件的角色,比如启动 Service , Notification 提示 , Activity 等。

•声明

  参考资料:

  【BroadcastReceiver牛刀小试

  【Android8 自定义广播接收不到的问题

Android学习之Broadcast初体验的更多相关文章

  1. Android学习之服务初体验

    •概念 Service(服务)是一个长期运行在后台,没有用户界面的应用组件,即使切换到另一个应用程序或者后台,服务也可以正常运行: 因此,服务适合执行一些不需要显示界面的后台耗时操作,比如下载网络数据 ...

  2. 第三次随笔--安装虚拟机及学习linux系统初体验

    第三次随笔--安装虚拟机及学习linux系统初体验 ·学习基于VirtualBox虚拟机安装Ubuntu图文教程在自己笔记本上安装Linux操作系统 首先按照老师的提示步骤进行VirtualBox虚拟 ...

  3. 算法学习:并行化初体验_JAVA实现并行化归并算法

    这个系列包括算法导论学习过程的记录. 最初学习归并算法,对不会使其具体跑在不同的核上报有深深地怨念,刚好算倒重温了这个算法,闲来无事,利用java的thread来体验一下并行归并算法.理论上开的thr ...

  4. Spring Boot 学习笔记1——初体验之3分钟启动你的Web应用[z]

    前言 早在去年就简单的使用了一下Spring Boot,当时就被其便捷的功能所震惊.但是那是也没有深入的研究,随着其在业界被应用的越来越广泛,因此决定好好地深入学习一下,将自己的学习心得在此记录,本文 ...

  5. Yaf学习(二)----Yaf初体验

    1.hello world 1.1 用yaf输出hello world 1.首先配置host,nginx 2.host不用多说,指向虚拟机IP即可 1.2 重点说一下nginx (只说server块) ...

  6. Android学习笔记--Broadcast, BroadcastReceiver(广播)

    参考资料:http://www.cnblogs.com/playing/archive/2011/03/23/1992030.html 在 Android 中使用 Activity, Service, ...

  7. Visual Studio Code 学习.net core初体验

    一,安装 最近在用 Visual Studio Code 学习.net core ,记录下学习的过程,首先去官网下载最新的.net core2.1安装包,有windows 和mac,根据自己的开发环境 ...

  8. Android学习总结——Broadcast

    一.利用BroadcastReceiever监听短信 AndroidManifest.xml <?xml version="1.0" encoding="utf-8 ...

  9. Android学习之RecyclerView初探究

    •RecyclerView基本用法 RecyclerView是新增的控件,为了让 RecyclerView 在所有 Android 版本上都能使用; Android 团队将 RecyclerView ...

随机推荐

  1. Pure CSS Progress Chart

    Pure CSS Progress Chart CSS Progress Circle SCSS .example { text-align: center; padding: 4em; } .pie ...

  2. macOS 需要更新软件才能连接到 iOS 设备

    macOS 需要更新软件才能连接到 iOS 设备 更新 Mac 上的软件 如果您在 iPhone.iPad 或 iPod touch 上看到"需要更新软件才能连接到 iOS 设备" ...

  3. js & regex & var & highlight

    js & regex & var & highlight let key = `ali`.toLocaleUpperCase(); let name = "阿里云计算 ...

  4. YAML & .yml

    YAML & .yml YAML: YAML Ain't Markup Language https://yaml.org/ https://github.com/yaml/www.yaml. ...

  5. taro 三端开发

    taro 三端开发 wx 小程序, alipay 小程序,H5 https://taro-docs.jd.com/taro/docs/GETTING-STARTED.html#h5 https://t ...

  6. c++ 使用进程id获取打开的网络端口

    #pragma warning( disable : 4996) #include <winsock2.h> #include <ws2tcpip.h> #include &l ...

  7. wordpress注册、登录后跳转到首页

    只想注册后跳转到首页 function new_login_redirect() { return '这里换成首页地址'; } add_action( 'user_register', 'auto_l ...

  8. SpringBoot解决特殊符号 []报400问题

    当遇到特殊符号传递给后台时,如果不加处理,就会报400的错误,解决办法有两种. 1.前台解决 前台解决的方法就是把这些特殊符号转义,转义之后浏览器和后台都可以识别. //对特殊字符进行转义 encod ...

  9. SpringBoot(五):SpringBoot使用拦截器

    1.按照SpringMVC的方式编写一个拦截器: 2.配置一个类   implements WebMvcConfigurer 接口 为该类添加注解@Configuration  (等价于一个sprin ...

  10. 49元起!魅族Lipro LED灯泡发布:无可视频闪、无积热问题

    转: 49元起!魅族Lipro LED灯泡发布:无可视频闪.无积热问题 魅族智能家居品牌Lipro今日办会,公布一批新品,均面向健康照明领域打造. 魅族强调,Lipro健康照明产品主打博物馆级健康光. ...