Android应用界面开发——BroadcastReceiver
BroadcastReceiver:广播接收者,Android四大组件之一,这个组件本质上就是一个全局监听器,用于监听系统全局的广播消息。由于BroadcastReceiver是一个全局监听器,因此它可以方便的实现系统中不同组件之间的通信。
BroadcastReceiver简介
BroadcastReceiver用于接收程序(开发者开发的程序和系统程序)发出的Broadcast Intent,程序启动BroadcastReceiver需要两步:
- 创建需要启动的BroadcastReceiver的Intent。
- 调用Context的sendBroadcast()或sendOrderedBroadcast()方法来启动指定的BroadcastReceiver。
实现BroadcastReceiver只要重写BroadcastReceiver的onReceive(Context context, Intent intent)方法即可。
实现了BroadcastReceiver,接着应该指定该BroadcastReceiver能匹配的Intent,有两种方式:
-
静态注册:
- 在AndroidManifest.xml中配置:
<receiver android:name=".MyReceiver"> <intent-filter> <action android:name="com.trampcr.musicplayer.PLAY_ACTION"/> </intent-filter> </receiver>
-
动态注册:
- 使用代码进行指定,调用BroadcastReceiver的Context的registerReceiver(BroadcastReceiver receiver, IntentFilter filter)方法指定:
IntentFilter intentFilter = new IntentFilter("com.trampcr.musicplayer.PLAY_ACTION"); MyReceiver receiver = new MyReceiver(); registerReceiver(receiver, intentFilter);
每次系统广播事件发生后,系统会创建对应的BroadcastReceiver实例,并自动触发它的onReceiver()方法,如果onReceiver()方法不能在10秒内完成,Android就会认为该程序无响应(所以onReceiver()方法中不能进行耗时操作)。onReceiver()方法执行完后,BroadcastReceiver实例就会被销毁。
如果需要根据Broadcast完成比较耗时的操作,则应该考虑通过Intent启动一个Service来完成,不考虑使用新线程完成耗时操作的原因:
BroadcastReceiver本身的生命周期很短,很可能子线程还没有结束,BroadcastReceiver就已经退出了。
发送广播
调用Context的sendBroadcast(Intent intent)方法发送广播,这条广播将会启动intent参数所对应的BroadcastReceiver。
该程序的Activity界面包含一个按钮,用于向外发送广播。代码如下:
public class MainActivity extends AppCompatActivity {
private Button mSendBroadcast;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSendBroadcast = (Button) findViewById(R.id.send_broadcast);
mSendBroadcast.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
//设置Intent的Action属性
intent.setAction("com.trampcr.musicplayer.PLAY_ACTION");
intent.putExtra("msg", "simple message");
//发送广播
sendBroadcast(intent);
}
});
}
}
上述程序用于创建一个Intent对象,并使用该Intent对象对外发送了一条广播。
发送了广播,就得接收广播,接收广播代码如下:
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
String[] msg = intent.getStringArrayExtra("msg");
Toast.makeText(context, "接收的Intent的Action为:" + action + "\n消息内容是" + msg, Toast.LENGTH_SHORT).show();
}
}
当符合该MyReceiver的广播出现时,MyReceiver的onReceiver()方法就会被触发,从而在该方法中显示广播所携带的消息。
发送广播时Intent的Action为com.trampcr.musicplayer.PLAY_ACTION,这就需要配置MyReceiver应监听Action为该字符串的Intent,在AndroidManifest.xml中配置:
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="com.trampcr.musicplayer.PLAY_ACTION"/>
</intent-filter>
</receiver>
点击发送广播按钮,可以看到收到广播的提示,如下:
广播类型
广播分为两种:
- Normal Broadcast(普通广播):完全异步,可以在同一时刻被所有接收者接收到。
- sendBroadcast():发送Normal Broadcast。
- Ordered Broadcast(有序广播):接收者按预先声明的优先级依次接收Broadcast。
- 优先级声明在<intent-filter.../>元素的android:priority属性中,数越大优先级越高。
- Ordered Broadcast接收者可以调用abortBroadcast()方法终止Broadcast Intent的传播,一旦终止,后面的接收者就无法接收到Broadcast。
- sendOrderedBroadcast():发送Ordered Broadcast。
上面发送广播中举了一个发送普通广播的例子,这里再举一个发送有序分广播的例子:
该程序的Activity界面只有一个按钮,用于发送一条有序广播,代码如下:
public class MainActivity extends AppCompatActivity {
private Button mSendBroadcast;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSendBroadcast = (Button) findViewById(R.id.send_broadcast);
mSendBroadcast.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
//设置Intent的Action属性
intent.setAction("com.trampcr.musicplayer.PLAY_ACTION");
intent.putExtra("msg", "simple message");
//发送有序广播
sendOrderedBroadcast(intent, null);
}
});
}
}
代码中指定了Intent的Action属性,再调用sendOrderedBroadcast()方法来发送有序广播。对于有序广播,它会按优先级依次触发每个BroadcastReceiver的onReceiver()方法。
第一个BroadcastReceiver代码:
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
String msg = intent.getStringExtra("msg");
Toast.makeText(context, "接收的Intent的Action为:" + action + "\n消息内容是" + msg, Toast.LENGTH_SHORT).show();
//创建一个Bundle对象,并存入数据
Bundle bundle = new Bundle();
bundle.putString("first", "第一个BroadcastReceiver存入的消息");
//将bundle放入结果中
setResultExtras(bundle);
//取消Broadcast的继续传播
//abortBroadcast();
}
}
MyReceiver不仅处理了它所接收的消息,而且向处理结果中存入了key为first的消息,这个消息将可以被第二个BroadcastReceiver解析出来。
abortBroadcast()用于取消广播,如果这条代码生效,那么优先级比MyReceiver低的BroadcastReceiver都将不会被触发。
在AndroidManifest.xml中部署该BroadcastReceiver,并指定其优先级为20,代码如下:
<receiver android:name=".MyReceiver">
<intent-filter android:priority="20">
<action android:name="com.trampcr.musicplayer.PLAY_ACTION"/>
</intent-filter>
</receiver>
接下来提供第二个BroadcastReceiver,将会解析前一个BroadcastReceiver存入的key为first的消息,代码如下:
public class MyReceiver2 extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = getResultExtras(true);
//解析前一个BroadcastReceiver所存入的key为first的消息
String first = bundle.getString("first");
Toast.makeText(context, "第一个Broadcast存入的消息为:" + first, Toast.LENGTH_SHORT).show();
}
}
解析出前一个BroadcastReceiver存入结果中的key为first的消息。
在AndroidManifest.xml中配置MyReceiver2的优先级为0,如下:
<receiver android:name=".MyReceiver2">
<intent-filter android:priority="0">
<action android:name="com.trampcr.musicplayer.PLAY_ACTION" />
</intent-filter>
</receiver>
先注释掉abortBroadcast(),点击发送有序广播按钮,可以看到先显示第一个广播接收器中的内容,再显示第二个广播接收器中的内容,如下:
如果不注释abortBroadcast(),将会阻止消息广播,消息将传不到MyReceiver2。
系统广播
广播接收器除了可以接收用户发送的广播,还可以接收系统广播,常用的系统广播如下:
- ACTION_TIME_CHANGED:系统时间被改变。
- ACTION_DATE_CHANGED:系统日期被改变。
- ACTION_TIMEZONE_CHANGED:系统时区被改变。
- ACTION_BOOT_COMPLETED:系统启动完成。
- ACTION_PACKAGE_ADDED:系统添加包。
- ACTION_PACKAGE_CHANGED:系统的包改变。
- ACTION_PACKAGE_REMOVED:系统的包被删除。
- ACTION_PACKAGE_RESTARTED:系统的包被重启。
- ACTION_PACKAGE_DATA_CLEARED:系统的包数据被清空。
- ACTION_BATTERY_CHANGED:电池电量改变。
- ACTION_BATTERY_LOW:电池电量低。
- ACTION_POWER_CONNECTED:系统连接电源。
- ACTION_POWER_DISCONNECTED:系统与电源断开。
- ACTION_SHUTDOWN:系统被关闭。
基于Service的音乐播放器
这里开发一个基于Service的音乐播放器,音乐由后台运行的Service负责播放,当后台的播放状态发生变化时,程序将会通过发送广播通知前台Activity更新界面;当点击Activity的界面按钮时,系统将通过发送广播通知后台Service来改变播放状态。
前台Activity界面有两个按钮,分别用于控制播放/暂停、停止,另外还有两个文本框,用于显示正在播放的歌曲名、歌手名。前台Activity的代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private ImageButton mStart;
private ImageButton mStop;
private TextView mMusicName;
private TextView mSongerName;
private ActivityReceiver mActivityReceiver;
public static final String CTL_ACTION = "com.trampcr.action.CTL_ACTION";
public static final String UPDATE_ACTION = "com.trampcr.action.UPDATE_ACTION";
//定义音乐播放状态,0x11代表没有播放,0x12代表正在播放,0x13代表暂停
int status = 0x11;
String[] musicNames = new String[]{"完美生活", "那一年", "故乡"};
String[] songerNames = new String[]{"许巍", "许巍", "许巍"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mStart = (ImageButton) findViewById(R.id.start);
mStop = (ImageButton) findViewById(R.id.stop);
mMusicName = (TextView) findViewById(R.id.music_name);
mSongerName = (TextView) findViewById(R.id.songer_name);
mStart.setOnClickListener(this);
mStop.setOnClickListener(this);
mActivityReceiver = new ActivityReceiver();
//创建IntentFilter
IntentFilter filter = new IntentFilter();
//指定BroadcastReceiver监听的Action
filter.addAction(UPDATE_ACTION);
//注册BroadcastReceiver
registerReceiver(mActivityReceiver, filter);
Intent intent = new Intent(MainActivity.this, MusicService.class);
//启动后台Service
startService(intent);
}
public class ActivityReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//获取Intent中的update消息,update代表播放状态
int update = intent.getIntExtra("update", -1);
//获取Intent中的current消息,current代表当前正在播放的歌曲
int current = intent.getIntExtra("current", -1);
if (current >= 0){
mMusicName.setText(musicNames[current]);
mSongerName.setText(songerNames[current]);
}
switch (update){
case 0x11:
mStart.setBackgroundResource(R.drawable.play);
status = 0x11;
break;
//控制系统进入播放状态
case 0x12:
//在播放状态下设置使用暂停图标
mStart.setBackgroundResource(R.drawable.pause);
status = 0x12;
break;
case 0x13:
//在暂停状态下设置使用播放图标
mStart.setBackgroundResource(R.drawable.play);
status = 0x13;
break;
}
}
}
@Override
public void onClick(View v) {
Intent intent = new Intent(CTL_ACTION);
switch (v.getId()){
case R.id.start:
intent.putExtra("control", 1);
break;
case R.id.stop:
intent.putExtra("control", 2);
break;
}
//发送广播,将被Service中的BroadcastReceiver接收到
sendBroadcast(intent);
}
}
ActivityReceiver()用于响应后台Service所发出的广播,该程序将会根据广播Intent里的消息来改变播放状态,并更新程序界面中按钮的图标。
onClick中根据点击的按钮发送广播,发送广播时会把所按下的按钮标识发送出来。
接下来是后台Service,会在播放状态发生改变时对外发送广播。代码如下:
public class MusicService extends Service {
MyReceiver serviceReceiver;
AssetManager mAssetManager;
String[] musics = new String[]{"prefectLife.mp3", "thatYear.mp3", "country.mp3"};
MediaPlayer mMediaPlayer;
int status = 0x11;
int current = 0; // 记录当前正在播放的音乐
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
mAssetManager = getAssets();
serviceReceiver = new MyReceiver();
//创建IntentFilter
IntentFilter filter = new IntentFilter();
filter.addAction(MainActivity.CTL_ACTION);
registerReceiver(serviceReceiver, filter);
//创建MediaPlayer
mMediaPlayer = new MediaPlayer();
//为MediaPlayer播放完成事件绑定监听器
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
current++;
if (current >= 3) {
current = 0;
}
//发送广播通知Activity更改文本框
Intent sendIntent = new Intent(MainActivity.UPDATE_ACTION);
sendIntent.putExtra("current", current);
//发送广播,将被Activity中的BroadcastReceiver接收到
sendBroadcast(sendIntent);
//准备并播放音乐
prepareAndPlay(musics[current]);
}
});
}
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int control = intent.getIntExtra("control", -1);
switch (control){
case 1: // 播放或暂停
//原来处于没有播放状态
if (status ==0x11){
//准备播放音乐
prepareAndPlay(musics[current]);
status = 0x12;
}
//原来处于播放状态
else if (status == 0x12){
//暂停
mMediaPlayer.pause();
status = 0x13; // 改变为暂停状态
}
//原来处于暂停状态
else if (status == 0x13){
//播放
mMediaPlayer.start();
status = 0x12; // 改变状态
}
break;
//停止声音
case 2:
//如果原来正在播放或暂停
if (status == 0x12 || status == 0x13){
//停止播放
mMediaPlayer.stop();
status = 0x11;
}
}
//广播通知Activity更改图标、文本框
Intent sendIntent = new Intent(MainActivity.UPDATE_ACTION);
sendIntent.putExtra("update", status);
sendIntent.putExtra("current", current);
//发送广播,将被Activity中的BroadcastReceiver接收到
sendBroadcast(sendIntent);
}
}
private void prepareAndPlay(String music) {
try {
//打开指定的音乐文件
AssetFileDescriptor assetFileDescriptor = mAssetManager.openFd(music);
mMediaPlayer.reset();
//使用MediaPlayer加载指定的声音文件
mMediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(), assetFileDescriptor.getStartOffset(), assetFileDescriptor.getLength());
mMediaPlayer.prepare(); // 准备声音
mMediaPlayer.start(); // 播放
} catch (IOException e) {
e.printStackTrace();
}
}
}
MyReceiver用于接收前台Activity所发出的广播,并根据广播的消息内容改变Service的播放状态,当播放状态改变时,该Service对外发送一条广播,广播消息将会被前台Activity接收,前台Activity将会根据广播消息更新界面。
为了让该音乐播放器能按顺序依次播放歌曲,程序为MediaPlayer增加了OnCompletionListener监听器,当MediaPlayer播放完成后将自动播放下一首歌曲。
运行程序,效果图如下: