在网络通信中,一个IP网络范围中最大的IP 地址是被保留作为广播地址来使用的。比如某个网络的IP 范围是192.168.0.XXX,子网掩码是255.255.255.0,那么这个网络的广播地址就是192.168.0.255。广播数据包会被发送到同一网络上的所有端口,这样在该网络中的每台主机都将会收到这条广播。
为了方便于进行系统级别的消息通知,Android 也引入了一套类似的广播消息机制。相比于我前面举出的两个例子,Android 中的广播机制会显得更加的灵活,本章就将对这一机制的方方面面进行详细的讲解。
一、 广播机制简介
为什么说Android 中的广播机制更加灵活呢?这是因为Android 中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会接收到自己所关心的广播内容,这些广播可能是来自于系统的,也可能是来自于其他应用程序的。Android 提供了一套完整的API,允许应用程序自由地发送和接收广播。发送广播的方法其实之前稍微有提到过一下,如果你记性好的话可能还会有印象,就是借助我们学过的Intent。而接收广播的方法则需要引入一个新的概念,广播接收器(Broadcast Receiver)。
广播接收器的具体用法将会在下一节中做介绍,这里我们先来了解一下广播的类型。Android 中的广播主要可以分为两种类型,标准广播和有序广播。
标准广播(Normal broadcasts)是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的。标准广播的工作流程如下图:
有序广播(Ordered broadcasts)则是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。有序广播的工作流程如图:
二、接收系统广播
Android 内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种系统的状态信息。比如手机开机完成后会发出一条广播,电池的电量发生变化会发出一条广播,时间或时区发生改变也会发出一条广播等等。如果想要接收到这些广播,就需要使用广播接收器,下面我们就来看一下它的具体用法。
1、 动态注册监听网络变化
广播接收器可以自由地对自己感兴趣的广播进行注册,这样当有相应的广播发出时,广播接收器就能够收到该广播,并在内部处理相应的逻辑。注册广播的方式一般有两种,在代码中注册和在AndroidManifest.xml 中注册,其中前者也被称为动态注册,后者也被称为静态注册。
那么该如何创建一个广播接收器呢?其实只需要新建一个类,让它继承自BroadcastReceiver,并重写父类的onReceive()方法就行了。这样当有广播到来时,onReceive()方法就会得到执行,具体的逻辑就可以在这个方法中处理。
那我们就先通过动态注册的方式编写一个能够监听网络变化的程序,借此学习一下广播接收器的基本用法吧。新建一个BroadcastTest 项目,然后修改MainActivity 中的代码,其中重要的代码如下所示,该部分在onCreate 方法中实现
IntentFilter myintentfilter = new IntentFilter();
myintentfilter.addAction("time");
MyBroadCast mybroadcast = new MyBroadCast();
registerReceiver(mybroadcast,myintentfilter);
第一步:这里先创建一个IntentFilter 实例,并在其addAction 中添加我们要监听的广播,这里是"time”,静态注册则把这一步在AndroidManifest.xml 中完成;
第二步:创建了一个MyBroadCast 实例,然后调用registerReceiver 方法进行注册,将MyBroadCast 实例和IntentFilter 实例都传播出去;
这样,mybroadcast 就会收到所有值为"time" 的广播,实现监听功能。
当然,这里我们新建一个类继承自BroadcastReceiver ,并重写 onReceive() 方法:
class MyBroadCast extends BroadcastReceiver{
Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
int s = intent.getIntExtra("key",0);
TextView text1 = (TextView) findViewById(R.id.text1);
text1.setText("计时: "+s+"秒");
}
}
通过这个广播,我们也可以利用Intent 的方法,实现传送数据的功能,并对其进行处理。
2、静态注册实现开机启动
动态注册的广播接收器可以自由地控制注册与注销,在灵活性方面有很大的优势,但是它也存在着一个缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在onCreate()方法中的。那么有没有什么办法可以让程序在未启动的情况下就能接收到广播呢?这就需要使用静态注册的方式了。
我们准备让程序接收一条开机广播,当收到这条广播时就可以在onReceive()方法里执行相应的逻辑,从而实现开机启动的功能。新建一个MyReceiver继承自
BroadcastReceiver,代码如下所示:
public class MyReceive extends BroadcastReceiver {
@Override
ublic void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Log.v("MainActivity", "我是广播!!!我来了!!!");
Toast.makeText(context, "我是广播!!我出来了!!!", 0).show();
}
}
可以看到,这里不再使用内部类的方式来定义广播接收器,因为稍后我们需要在AndroidManifest.xml 中将这个广播接收器的类名注册进去。在onReceive()方法中,还是简单地使用Toast 弹出一段提示信息。
然后修改AndroidManifest.xml 文件,代码如下所示:
<receiver
android:name="cn.com.qiang.sendbroadcast.MyReceive">
<intent-filter >
<action android:name="BroadCast"/>
</intent-filter>
</receiver>
终于,<application>标签内出现了一个新的标签<receiver>,所有静态注册的广播接收器都是在这里进行注册的。它的用法其实和<activity>标签非常相似,首先通过android:name来指定具体注册哪一个广播接收器,然后在<intent-filter>标签里加入想要接收的广播就行了,由于Android 系统启动完成后会发出一条值为BroadCast的广播,因此我们在这里添加了相应的action。
另外,监听系统开机广播也是需要声明权限的,可以看到,我们使用<uses-permission>标签又加入了一条android.permission.RECEIVE_BOOT_COMPLETED 权限。
三、发送自定义广播
广播主要分为两种类型,标准广播和有序广播,在本节中我们就将通过实践的方式来看下这两种广播具体的区别:
1、发送标准广播
我们来修改来修改activity_main.xml 中的代码,通过按钮来发送广播
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发送广播"/>
这里在布局文件中定义了一个按钮,用于作为发送广播的触发点。然后修改MainActivity中的代码,如下所示:
Button button1 = (Button)findViewById(R.id.button1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
/ TODO Auto-generated method stub
Intent intent = new Intent();
intent.setAction("BroadCast");
sendBroadcast(intent);
}
});
可以看到,我们在按钮的点击事件里面加入了发送自定义广播的逻辑。首先构建出了一个Intent 对象,并把要发送的广播的值传入,然后调用了Context 的sendBroadcast()方法将广播发送出去,这样所有监听BroadCast 条广播的广播接收器就会收到消息。此时发出去的广播就是一条标准广播。
注意:由于广播是使用Intent 进行传递的,因此你还可以在Intent 中携带一些数据传递给广播接收器。
2、发送有序广播
广播是一种可以跨进程的通信方式,这一点从前面接收系统广播的时候就可以看出来了。因此在我们应用程序内发出的广播,其他的应用程序应该也是可以收到的。
有序广播和标准广播的区别之一是,我们发送广播时,调用的是sendOrderedBroadcast 方法:
sendOrderedBroadcast(intent, null);
其中sendOrderedBroadcast()方法接收两个参数,第一个参数仍然是Intent,第二个参数是一个与权限相关的字符串,这里传入null 就行了。
广播接收器是有先后顺序的,而且前面的广播接收器还可以将广播截断,以阻止其继续传播。那么该如何设定广播接收器的先后顺序呢?当然是在注册的时候进行设定的了,修改AndroidManifest.xml 中的代码,如下所示:
<receiver android:name=".MyBroadcastReceiver">
<intent-filter android:priority="100" >
<action android:name="com.example.broadcasttest.MY_BROADCAST"/>
</intent-filter>
</receiver>
我们通过android:priority 属性给广播接收器设置了优先级,优先级比较高的广播接收器就可以先收到广播。这里将MyBroadcastReceiver 的优先级设成了100,以保证它一定会在AnotherBroadcastReceiver 之前收到广播。
既然已经获得了接收广播的优先权,那么MyBroadcastReceiver 就可以选择是否允许广播继续传递了。修改MyBroadcastReceiver 中的代码,如下所示:
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in MyBroadcastReceive",
Toast.LENGTH_SHORT).show();
abortBroadcast();
}
}
如果在onReceive()方法中调用了abortBroadcast()方法,就表示将这条广播截断,后面的广播接收器将无法再接收到这条广播。现在重新运行程序,并点击一下Send Broadcast 按钮,你会发现, 只有MyBroadcastReceiver 中的Toast 信息能够弹出, 说明这条广播经过MyBroadcastReceiver 之后确实是终止传递了。
下面我们用一个实例——计时器,将前面学到的活动、服务、广播综合在一起进行学习整合,程序实现的功能很简单,就是在前台实现计时的功能,并能暂停计时,且继续计时时,数字会接上暂停前数据继续跳转,实例图如下:
具体代码如下:
1、MainActivty 端
主要实现广播的动态注册,广播的发送与接收,开启服务与停止服务的功能,具体代码如下:
package cn.com.qiang.timeservice;
import android.os.Bundle;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IntentFilter myintentfilter = new IntentFilter();
myintentfilter.addAction("time");
MyBroadCast mybroadcast = new MyBroadCast();
registerReceiver(mybroadcast,myintentfilter);
Button button1 = (Button)findViewById(R.id.button1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent startintent = new Intent(MainActivity.this,MyService.class);
startService(startintent);
}
});
Button button2 = (Button)findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent stopintent = new Intent(MainActivity.this,MyService.class);
stopService(stopintent);
TextView text1 = (TextView) findViewById(R.id.text1);
text1.setText("计时: "+"0"+"秒");
}
});
Button button3 = (Button)findViewById(R.id.button3);
button3.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent = new Intent();
intent.setAction("pause");
sendBroadcast(intent);
}
});
}
class MyBroadCast extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
int s = intent.getIntExtra("key",0);
TextView text1 = (TextView) findViewById(R.id.text1);
text1.setText("计时: "+s+"秒");
}
}
}
2、MySevice 端
具体实现计时服务,与广播的接受与发送,具体代码如下:
package cn.com.qiang.timeservice;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
public class MyService extends Service {
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
boolean flag;
private Thread mythread;
private int count;
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
IntentFilter myfilter = new IntentFilter();
myfilter.addAction("pause");
Pause pause = new Pause();
registerReceiver(pause,myfilter);
}
@Override
public void onStart(Intent intent, int startId) {
// TODO Auto-generated method stub
super.onStart(intent, startId);
mythread = new MyThread();
mythread.start();
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
((MyThread) mythread).setflag();
}
class Pause extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
flag = true;
}
}
class MyThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
flag = false;
while(!flag){
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
// Log.v("MainActivity", "计时"+(++count)+"秒");
Intent intent = new Intent();
intent.setAction("time");
intent.putExtra("key", ++count);
sendBroadcast(intent);
}
}
public void setflag(){
flag = true;
}
}
}
3、AndroidManiTest 端就不写了,该注册的注册就可以了;
附:在活动中使用Toast
Toast 是Android 系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间,我们现在就尝试一下如何在活动中使用Toast。
首先需要定义一个弹出Toast 的触发点,正好界面上有个按钮,那我们就让点击这个按钮的时候弹出一个Toast 吧。在onCreate()方法中添加代码:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.first_layout);
Button button1 = (Button) findViewById(R.id.button_1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FirstActivity.this, "You clicked Button 1",
Toast.LENGTH_SHORT).show();
}
});
}
在活动中,可以通过findViewById()方法获取到在布局文件中定义的元素,这里我们传入R.id.button_1,来得到按钮的实例,这个值是刚才在first_layout.xml 中通过android:id 属性指定的。findViewById()方法返回的是一个View 对象,我们需要向下转型将它转成Button对象。得到了按钮的实例之后,我们通过调用setOnClickListener()方法为按钮注册一个监听器,点击按钮时就会执行监听器中的onClick()方法。因此,弹出Toast 的功能当然是要在onClick()方法中编写了。
Toast 的用法非常简单,通过静态方法makeText()创建出一个Toast 对象,然后调用show()将Toast 显示出来就可以了。这里需要注意的是,makeText()方法需要传入三个参数。第一个参数是Context,也就是Toast 要求的上下文,由于活动本身就是一个Context 对象,因此这里直接传入FirstActivity.this即可。第二个参数是Toast显示的文本内容,第三个参数是Toast显示的时长,有两个内置常量可以选择Toast.LENGTH_SHORT 和Toast.LENGTH_LONG。