提到广播,因为我是农村的,首先想到的就是我们村大队的喇叭,如果平常有交电费,或者重要的事情,这个喇叭就会响起,全村的都能听见,可能有时候邻村的也能听见,足见广播的推送面积之大。
提到广播这种机制,学过iOS的都知道通知,我想这两种机制应该都是类似的。在iOS中,我们如果想监听系统的某些事件,比如APP退到后台,我们只需要在把监听对象添加到系统的通知中心中,并且关注APP退到后台的事件,就可以随时监听了。通知还有个好处就是,我们可以夸线程,夸界面的处理事件。
那么,在Android中,广播又是怎么回事呢,这篇文章就来详细分析一下广播在Android中的应用。(ps:广播还有另外一个名字——全局大喇叭~~)
广播机制简介 在广播机制中,存在两种角色:广播发送者 和广播接收者 。
我们来看看广播的使用场景:
同一APP内部的同一组件内部的消息通信(单个或多个线程之间)
同一APP内部的不同组件之间的消息通信(单个进程)
同一APP具有多个进程的不同组件之间的消息通信
不同APP之间的组件之间消息通信
Android系统在特定情况下与APP之间的消息通信
另外,Android中的广播主要可以分为两种类型:标准广播 和有序广播 。
标准广播 标准广播是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但是却不能被截断。标准广播的工作流程是:
有序广播 有序广播是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑处理执行完毕后,广播才会继续传播,而且优先级高的广播接收器可以优先接收到广播。并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。有序广播的工作流程是:
接收系统广播 Android内置了很多系统级别的广播,如果我们想要监听系统的这些广播,就需要用到广播接收器(BroadcastReceiver)。注册广播的方式一般有两种,在代码中注册和在AndroidManifest.xml中注册,其中前者也被称为动态注册,后者也被称为静态注册。
动态注册监听网络变化 凡是广播接收者都必须继承自BroadcastReceiver
,并重写抽象方法onReceive()
,当有广播到来时,onReceive()
方法就会得到执行。
我们使用动态注册的方式去监听网络的变化。代码如下:
public class MainActivity extends AppCompatActivity {
private NetworkChangeReceiver mNetworkChangeReceiver;
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE" );
mNetworkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(mNetworkChangeReceiver,intentFilter);
}
@Override
protected void onDestroy () {
super .onDestroy();
unregisterReceiver(mNetworkChangeReceiver);
}
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.isAvailable()){
Toast.makeText(context,"有网了!" ,Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(context,"没网了!" ,Toast.LENGTH_SHORT).show();
}
}
}
}
不要忘了,要在AndroidManifest.xml中添加网络权限<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
可以看到,我在MainActivity中定义了一个内部类NetworkChangeReceiver
并继承BroadcastReceiver
,重写了父类的onReceive()
方法。每当网络发生变化的时候,onReceive()
方法就会得到执行。
在onCreate()
方法中,我创建了一个IntentFilter的实例,并给它添加了一个值为android.net.conn.CONNECTIVITY_CHANGE
的action,因外当网络状态发生变化时,系统会发出一条值为android.net.conn.CONNECTIVITY_CHANGE
的广播。因为我们是动态注册广播,所以一定要在onDestroy()
方法中调用unregisterReceiver()
注销广播的监听。
静态注册实现开机启动 动态注册的广播接收器可以自由的控制注册与注销,在灵活性方面有很大的优势,但是它存在着一个缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在onCreate()
方法里的。如果我们想在程序未启动的时候,就能接收到广播,那就要用到静态注册了。
首先我们需要创建一个广播接收器类,创建方式为:选择新建->Other->Broadcast Receiver。Android Studio会让我们选择谢谢东西,Exported
表示是否允许这个广播接收器接收本程序以外的广播,Enabled
表示是否启用这个广播接收器,这里我们都勾选。
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive (Context context, Intent intent) {
Toast.makeText(context,"手机开机了" ,Toast.LENGTH_SHORT).show();
}
}
可以看到,BootCompleteReceiver
类里面的代码很简单。
另外,静态的广播接收器一定要在AndroidManifest.xml文件中注册才可以使用,但是注册这一步已经被Android Studio自动完成了。
<manifest xmlns:android ="http://schemas.android.com/apk/res/android"
package ="com.guiyongdong.broadreceiverdemo" >
<uses-permission android:name ="android.permission.ACCESS_NETWORK_STATE" />
<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" >
<activity android:name =".MainActivity" >
<intent-filter >
<action android:name ="android.intent.action.MAIN" />
<category android:name ="android.intent.category.LAUNCHER" />
</intent-filter >
</activity >
<receiver
android:name =".BootCompleteReceiver"
android:enabled ="true"
android:exported ="true" >
</receiver >
</application >
</manifest >
目前,我们还不能接收到开机广播,因为我们还没有为它添加响应的action。添加代码如下:
<manifest xmlns:android ="http://schemas.android.com/apk/res/android"
package ="com.guiyongdong.broadreceiverdemo" >
<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" >
<activity android:name =".MainActivity" >
<intent-filter >
<action android:name ="android.intent.action.MAIN" />
<category android:name ="android.intent.category.LAUNCHER" />
</intent-filter >
</activity >
<receiver
android:name =".BootCompleteReceiver"
android:enabled ="true"
android:exported ="true" >
<intent-filter >
<action android:name ="android.intent.action.BOOT_COMPLETED" />
</intent-filter >
</receiver >
</application >
</manifest >
由于Android系统自动完成后会发送一条值为android.intent.action.BOOT_COMPLETED
的广播,因此需要在<intent-filter>
标签中添加相应的action,当然,监听系统开机广播的权限也是需要声明的。如此,我们的APP便可监听手机的开机了。
发送自定义广播 说了这么多,一直都是系统在发广播,但是有时候我们根据业务需求要想发送自己的广播该这么办呢?前面说了,发广播的形式有两种,标准广播和有序广播,那就来看看这两种广播如何发送。
发送标准广播 在发送广播之前,我先定义一个广播接收器,这里约定action为com.guiyongdong.MY_BROADCAST
,代码如下:
<manifest xmlns:android ="http://schemas.android.com/apk/res/android"
package ="com.guiyongdong.broadreceiverdemo" >
<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.guiyongdong.MY_BROADCAST" />
</intent-filter >
</receiver >
</application >
</manifest >
MyBroadcastReceiver
广播接收器的代码如下:
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive (Context context, Intent intent) {
Toast.makeText(context,"接收到自定义的广播" ,Toast.LENGTH_SHORT).show();
}
}
我在MainActivity中添加了一个按钮,按钮添加点击事件,会发送名为com.guiyongdong.MY_BROADCAST
的广播,代码如下:public class MainActivity extends AppCompatActivity {
private NetworkChangeReceiver mNetworkChangeReceiver;
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.sendButton);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick (View v) {
Intent intent = new Intent("com.guiyongdong.MY_BROADCAST" );
sendBroadcast(intent);
}
});
}
}
重新运行,并点击按钮,效果图如下:
发送有序广播 public class MainActivity extends AppCompatActivity {
private NetworkChangeReceiver mNetworkChangeReceiver;
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.sendButton);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick (View v) {
Intent intent = new Intent("com.guiyongdong.MY_BROADCAST" );
sendOrderedBroadcast(intent,null );
}
});
}
}
可以看到,发送有序广播只需要改动一行代码,即sendBroadcast()
方法改成sendOrderedBroadcast()
方法。sendOrderedBroadcast()
方法接收两个参数,第一个参数任然是Intent,第二个参数是一个与权限相关的字符串,这里传null就行了。
如何定义有序广播的接收者的优先级呢?通过android:priority
可以设置优先级。需要修改AndroidManifest.xml
<manifest xmlns:android ="http://schemas.android.com/apk/res/android"
package ="com.guiyongdong.broadreceiverdemo" >
<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 android:priority ="100" >
<action android:name ="com.guiyongdong.MY_BROADCAST" />
</intent-filter >
</receiver >
</application >
</manifest >
那么,当接收者接到广播,也已经处理好逻辑后,如果接收者不想让广播继续传递了呢?这时候,需要调用abdortBroadcast()
方法,来中断传播。
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive (Context context, Intent intent) {
Toast.makeText(context,"接收到自定义的广播" ,Toast.LENGTH_SHORT).show();
abortBroadcast();
}
}
本地广播 前面我所说的发送的广播都是全局广播,这些广播可以被其他任何应用程序接收到,并且我们也可以接收来自其他应用的广播,这样容易引起安全问题。
当然,Android已经为我们想到了这点,利用本地广播就可以解决广播的安全问题,而且这类广播只能在应用程序内部进行传递,并且广播接收者只能接收来自本应用程序发出的广播。
本地广播并不复杂,它主要使用LocalBroadcastManager
来管理广播,并且提供了发送广播和注册广播接收器的方法,我们来看如何使用:
public class MainActivity extends AppCompatActivity {
private LocalBroadcastManager mLocalBroadcastManager;
private LocalReceiver mLocalReceiver;
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this );
Button sendLocalButton = (Button) findViewById(R.id.sendLocalButton);
sendLocalButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick (View v) {
Intent intent = new Intent("com.guiyongdong.LOCAL_BROADCAST" );
mLocalBroadcastManager.sendBroadcast(intent);
}
});
IntentFilter intentFilter1 = new IntentFilter();
intentFilter.addAction("com.guiyongdong.LOCAL_BROADCAST" );
mLocalReceiver = new LocalReceiver();
mLocalBroadcastManager.registerReceiver(mLocalReceiver,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();
mLocalBroadcastManager.unregisterReceiver(mLocalReceiver);
}
}
注意:本地广播是无法通过静态注册的方式来接收的。
总结一下本地广播的几点优势:
可以明确知道正在发送的广播不会离开我们的程序,因此不用担心数据泄露。
其他的程序无法将广播发送到我们程序内部,因此不用担心会有安全漏洞的隐患。
发送本地广播比全局广播更加高效。