- 浏览: 73328 次
- 性别:
- 来自: 西安
文章分类
最新评论
在Android中并没有定义MediaButtonReceive这个广播类,MediaButtonReceive只是作为一种通俗的命名方式来响应
插入耳机后,点击耳机上的按钮(名称:MEDIA_BUTTON)接受该广播事件的类。所有该MEDIA_BUTTON的按下我们就简称
为MEDIA_BUTTON广播吧。
顾名思义:它显然是一个广播接收器类(BroadbcastReceiver),那么它就具备了BroadbcastReceiver类的使用方式,
但是,因为它需要通过AudioManager对象注册,所以它有着自己的独特之处(否则我也不会单独拿出来分析,- -),后面我们
会慢慢的讲解。
点击MEDIA_BUTTON发送的Intent Action 为:
ACTION_MEDIA_BUTTON ="android.intent.action.MEDIA_BUTTON"
Intent 附加值为(Extra)点击MEDIA_BUTTON的按键码 :
//获得KeyEvent对象
KeyEvent keyEvent = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
//获得Action
String intentAction = intent.getAction() ;
AudioManager对象注册MEDIA_BUTTON广播的方法原型为:
public voidregisterMediaButtonEventReceiver(ComponentNameeventReceiver)
Register a component to be the sole receiverof MEDIA_BUTTON intents
Parameters:
eventReceiver : identifier of a BroadcastReceiver that will receive the media button intent. This broadcast receiver
must be declared in the application manifest.
从注释可知以下两点:
1、 在AudioManager对象注册一个MediaoButtonRecevie,使它成为MEDIA_BUTTON的唯一接收器(这很重要,
我们会放在后面讲解) 也就是说只有我能收到,其他的都收不到这个广播了,否则的话大家都收到会照成一定的混乱;
2、 该广播必须在AndroidManifest.xml文件中进行声明,否则就监听不到该MEDIA_BUTTON广播了。
下面我们就简单的写一个MediaButtonReceiver类,并且在AndroidManifest.xml定义
1、 自定义的MediaButtonReceiver 广播类
package com.qin.mediabutton;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.KeyEvent;
public class MediaButtonReceiver extends BroadcastReceiver {
private static String TAG = "MediaButtonReceiver";
@Override
public void onReceive(Context context, Intent intent) {
// 获得Action
String intentAction = intent.getAction();
// 获得KeyEvent对象
KeyEvent keyEvent = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
Log.i(TAG, "Action ---->" + intentAction + " KeyEvent----->"+ keyEvent.toString());
if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
// 获得按键字节码
int keyCode = keyEvent.getKeyCode();
// 按下 / 松开 按钮
int keyAction = keyEvent.getAction();
// 获得事件的时间
long downtime = keyEvent.getEventTime();
// 获取按键码 keyCode
StringBuilder sb = new StringBuilder();
// 这些都是可能的按键码 , 打印出来用户按下的键
if (KeyEvent.KEYCODE_MEDIA_NEXT == keyCode) {
sb.append("KEYCODE_MEDIA_NEXT");
}
// 说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是
// KEYCODE_MEDIA_PLAY_PAUSE
if (KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE == keyCode) {
sb.append("KEYCODE_MEDIA_PLAY_PAUSE");
}
if (KeyEvent.KEYCODE_HEADSETHOOK == keyCode) {
sb.append("KEYCODE_HEADSETHOOK");
}
if (KeyEvent.KEYCODE_MEDIA_PREVIOUS == keyCode) {
sb.append("KEYCODE_MEDIA_PREVIOUS");
}
if (KeyEvent.KEYCODE_MEDIA_STOP == keyCode) {
sb.append("KEYCODE_MEDIA_STOP");
}
// 输出点击的按键码
Log.i(TAG, sb.toString());
}
}
}
复制代码
2、 在AndroidManifest.xml声明我们定义的广播类。
<receiver android:name="MediaButtonReceiver">
<intent-filter >
<action android:name="android.intent.action.MEDIA_BUTTON"></action>
</intent-filter>
</receiver>
复制代码
在模拟器上,我们可以手动构造MEDA_BUTTON的广播,并且将它发送出去(后面会介绍)。
如果有真机测试的话,按下MEDIA_BUTTON是可以接受到MEDIA_BUTTON广播的,如果没有接受到,请关闭所有应用
程序,在观察效果。
继续我们的下一步分析:
前面我们说明通过registerMediaButtonEventReceiver(eventReceiver)方法注册时,使它成为MEDIA_BUTTON的
唯一 接收器。这个唯一是怎么实现的呢? 我们在源码中,一步步追本溯源,相信一定可以找到答案,知道这“唯一“是
怎么来的。
第一步、 为AudioManager注册一个MediaButtonReceiver() ;
//获得AudioManager对象
AudioManager mAudioManager =(AudioManager)getSystemService(Context.AUDIO_SERVICE);
//构造一个ComponentName,指向MediaoButtonReceiver类
//下面为了叙述方便,我直接使用ComponentName类来替代MediaoButtonReceiver类
ComponentName mbCN = new ComponentName(getPackageName(),MediaButtonReceiver.class.getName());
//注册一个MedioButtonReceiver广播监听
mAudioManager.registerMediaButtonEventReceiver(mbCN);
//取消注册的方法
mAudioManager.unregisterMediaButtonEventReceiver(mbCN);
复制代码
MediaButtonReceiver就是我们用来接收MEDIA_BUTTON的广播类,下面为了叙述方便和直观上得体验,我直接使用
ComponentName类来替代真正的MediaoButtonReceiver广播类。
说明 接下来分析的文件路径全部在 frameworks/base/media/java/android/media/ 下
第二步、 进入AudioManager.java进行查看 ,发现如下方法:
//注册的方法为:
public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
//TODO enforce the rule about the receiver being declared in the manifest
//我们继续查看getService()方法,看看IAudioService类到底是什么?
IAudioService service = getService();
try {
//只是简单的调用了service的方法来完成注册,继续跟踪
service.registerMediaButtonEventReceiver(eventReceiver);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerMediaButtonEventReceiver"+e);
}
}
//取消注册的方法为
public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) {
IAudioService service = getService();
try {
//只是简单的调用了service的方法来取消注册,,继续跟踪
service.unregisterMediaButtonEventReceiver(eventReceiver);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiver"+e);
}
}
复制代码
找到getService()方法,其实现为:
//看看它到底是什么
private static IAudioService getService()
{
// 单例模式,大家懂得
if (sService != null) {
return sService;
}
//了解Binder机制 以及AIDL文件的使用,就明白了这不过是通过AIDL文件定义的Java层Binder机制
//b为IBinder基类接口
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
//强制转换后,sService不过是一个客户端对象,IAudioService就是aidl文件定义的接口了
sService = IAudioService.Stub.asInterface(b);
return sService;
}
//sService对象的声明
private static IAudioService sService; //单例模式,不足为奇了
复制代码
我们知道了AudiaoManager只不过是一个傀儡,所有的方法都是由IAudioService 对象去实现的,通过它的构造方式,
可以知道它应该是有AIDL文件形成的Binder机制, sService只是客户端对象,那么它的服务端对象在什么地方呢?
也就是继承了IAudioService.Stub桩的类。
第三步、接下来我们需要找到该IAudioService.aidl文件和真正的服务端对象
IAudioService.aidl定义如下:
package android.media;
import android.content.ComponentName;
import android.media.IAudioFocusDispatcher;
/**
* {@hide}
*/
interface IAudioService {
void adjustVolume(int direction, int flags);
void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags);
void adjustStreamVolume(int streamType, int direction, int flags);
void setStreamVolume(int streamType, int index, int flags);
void setStreamSolo(int streamType, boolean state, IBinder cb);
void setStreamMute(int streamType, boolean state, IBinder cb);
int getStreamVolume(int streamType);
int getStreamMaxVolume(int streamType);
void setRingerMode(int ringerMode);
int getRingerMode();
void setVibrateSetting(int vibrateType, int vibrateSetting);
int getVibrateSetting(int vibrateType);
boolean shouldVibrate(int vibrateType);
void setMode(int mode, IBinder cb);
int getMode();
oneway void playSoundEffect(int effectType);
oneway void playSoundEffectVolume(int effectType, float volume);
boolean loadSoundEffects();
oneway void unloadSoundEffects();
oneway void reloadAudioSettings();
void setSpeakerphoneOn(boolean on);
boolean isSpeakerphoneOn();
void setBluetoothScoOn(boolean on);
boolean isBluetoothScoOn();
int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher l, String clientId);
int abandonAudioFocus(IAudioFocusDispatcher l, String clientId);
void unregisterAudioFocusClient(String clientId);
void registerMediaButtonEventReceiver(in ComponentName eventReceiver); //这个方法是我们需要弄懂的
void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver); //这个方法也是是我们需要弄懂的
void startBluetoothSco(IBinder cb);
void stopBluetoothSco(IBinder cb);
}
复制代码
真正的服务端对象就是继承了 IAudioService.Stub 桩的类,AudioService就是该服务端对象,其实AudioManager的
所有操作都是由AudioService来实现的,它才是真正的老大。
第五步、 AudioService.java
//AudioService类
public class AudioService extends IAudioService.Stub {
//.....
//仅仅列出我们需要的方法
//这儿才是真正的注册MediaButtonReceiver的方法
public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
Log.i(TAG, " Remote Control registerMediaButtonEventReceiver() for " + eventReceiver);
synchronized(mRCStack) {
//调用它去实现注册ComponentName
pushMediaButtonReceiver(eventReceiver);
}
}
//在查看pushMediaButtonReceiver()方法 先理解一下两个知识点,很重要的。
//RemoteControlStackEntry内部类不过是对ComponentName类的进一步封装(感觉没必要在加一层进行封装了)
private static class RemoteControlStackEntry {
public ComponentName mReceiverComponent;// 属性
//TODO implement registration expiration?
//public int mRegistrationTime;
public RemoteControlStackEntry() {
}
public RemoteControlStackEntry(ComponentName r) {
mReceiverComponent = r;// 构造函数赋值给mReceiverComponent对象
}
}
//采用了栈存储结构(先进后出)来保存所有RemoteControlStackEntry对象,也就是保存了ComponentName对象
private Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>();
//回到pushMediaButtonReceiver()查看,这下该拨开云雾了吧,继续学习
private void pushMediaButtonReceiver(ComponentName newReceiver) {
// already at top of stack?
//采用了一个栈(前面我们介绍的知识点)来保存所有注册的ComponentName对象
//如果当前栈不为空并且栈顶的对象与新注册的ComponentName对象一样,不做任何事,直接返回
if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(newReceiver)) {
return;
}
//获得mRCStack栈的迭代器
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
//循环
while(stackIterator.hasNext()) {
RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
//如果当前栈内保存该新注册的ComponentName对象,将它移除,跳出循环
if(rcse.mReceiverComponent.equals(newReceiver)) {
mRCStack.remove(rcse);
break;
}
}
//将新注册的ComponentName对象放入栈顶
mRCStack.push(new RemoteControlStackEntry(newReceiver));
}
}
复制代码
小结一下:
栈(mRCStack)维护了所有CompoentName对象,对每个CompoentName对象,保证它有且仅有一个,
新注册的CompoentName对象永远处于栈顶
我们看下取消注册的方法:
//我们看下取消注册的方法
/** see AudioManager.unregisterMediaButtonEventReceiver(ComponentName eventReceiver) */
public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) {
Log.i(TAG, " Remote Control unregisterMediaButtonEventReceiver() for " + eventReceiver);
synchronized(mRCStack) {
//调用removeMediaButtonReceiver方法去实现
removeMediaButtonReceiver(eventReceiver);
}
}
private void removeMediaButtonReceiver(ComponentName newReceiver) {
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
while(stackIterator.hasNext()) {
//获得mRCStack栈的迭代器
RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
//如果存在该对象,则移除,跳出循环
if(rcse.mReceiverComponent.equals(newReceiver)) {
mRCStack.remove(rcse);
break;
}
}
}
复制代码
通过对前面的学习,我们知道了AudioManager内部利用一个栈来管理包括加入和移除ComponentName对象,
新的疑问来了?这个MEDIA_BUTTON广播是如何分发的呢 ?
其实,AudioService.java文件中也存在这么一个MediaoButtonReceiver的广播类,它为系统广播接收器,即用来接收
系统的MEDIA_BUTTON广播,当它接收到了这个MEDIA_BUTTON广播 ,它会对这个广播进行进一步处理,这个处理过程
就是我们需要的弄清楚。
MediaButtonBroadcastReceiver 内部类如下:
private class MediaButtonBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//获得action ,系统MEDIA_BUTTON广播来了
String action = intent.getAction();
//action不正确 直接返回
if (!Intent.ACTION_MEDIA_BUTTON.equals(action)) {
return;
}
//获得KeyEvent对象
KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event != null) {
// if in a call or ringing, do not break the current phone app behavior
// TODO modify this to let the phone app specifically get the RC focus
// add modify the phone app to take advantage of the new API
//来电或通话中,不做处理直接返回
if ((getMode() == AudioSystem.MODE_IN_CALL) ||(getMode() == AudioSystem.MODE_RINGTONE)) {
return;
}
synchronized(mRCStack) {
//栈不为空
if (!mRCStack.empty()) {
// create a new intent specifically aimed at the current registered listener
//构造一个Intent对象 ,并且赋予Action和KeyEvent
Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
targetedIntent.putExtras(intent.getExtras());
//指定该处理Intent的对象为栈顶ComponentName对象的广播类
targetedIntent.setComponent(mRCStack.peek().mReceiverComponent);
// trap the current broadcast
// 终止系统广播
abortBroadcast();
//Log.v(TAG, " Sending intent" + targetedIntent);
//手动发送该广播至目标对象去处理,该广播不再是系统发送的了
context.sendBroadcast(targetedIntent, null);
}
//假设栈为空,那么所有定义在AndroidManifest.xml的监听MEDIA_BUTTON的广播都会处理,
//在此过程中如果有任何应用程注册了registerMediaButton 该广播也会立即终止
}
}
}
}
复制代码
总结一下MEDIA_BUTTON广播:
AudioManager也就是AudioService服务端对象内部会利用一个栈来管理所有ComponentName对象,所有对象有且仅有一个,
新注册的ComponentName总是会位于栈顶。
当系统发送MEDIA_BUTTON,系统MediaButtonBroadcastReceiver 监听到系统广播,它会做如下处理:
1、 如果栈为空,则所有注册了该Action的广播都会接受到,因为它是由系统发送的。
2、 如果栈不为空,那么只有栈顶的那个广播能接受到MEDIA_BUTTON的广播,手动发送了MEDIA_BUTTON
广播,并且指定了目标对象(栈顶对象)去处理该MEDIA_BUTTON 。
下面分析一下KeyEvent对象里的KeyCode按键,可能的按键码有:
1、KeyEvent.KEYCODE_MEDIA_NEXT
2、KeyEvent.KEYCODE_HEADSETHOOK
3、KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE(已废除,等同于KEYCODE_HEADSETHOOK)
4、KeyEvent.KEYCODE_MEDIA_PREVIOUS
5、KeyEvent.KEYCODE_MEDIA_STOP
PS : 在我的真机测试中,按下MEDIA_BUTTON只有KEYCODE_HEADSETHOOK可以打印出来了。
下面给出一个小DEMO检验一下我们之前所做的一切,看看MEDIA_BUTTON是如何处理分发广播的。
编写两个MediaButtonReceiver类用来监听MEDIA_BUTTON广播:
1 、China_MBReceiver.java
package com.qin.mediabutton;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.KeyEvent;
public class China_MBReceiver extends BroadcastReceiver {
private static String TAG = "China_MBReceiver" ;
@Override
public void onReceive(Context context, Intent intent) {
//获得Action
String intentAction = intent.getAction() ;
//获得KeyEvent对象
KeyEvent keyEvent = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
Log.i(TAG, "Action ---->"+intentAction + " KeyEvent----->"+keyEvent.toString());
if(Intent.ACTION_MEDIA_BUTTON.equals(intentAction)){
//获得按键字节码
int keyCode = keyEvent.getKeyCode() ;
//按下 / 松开 按钮
int keyAction = keyEvent.getAction() ;
//获得事件的时间
long downtime = keyEvent.getEventTime();
//获取按键码 keyCode
StringBuilder sb = new StringBuilder();
//这些都是可能的按键码 , 打印出来用户按下的键
if(KeyEvent.KEYCODE_MEDIA_NEXT == keyCode){
sb.append("KEYCODE_MEDIA_NEXT");
}
//说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是 KEYCODE_MEDIA_PLAY_PAUSE
if(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ==keyCode){
sb.append("KEYCODE_MEDIA_PLAY_PAUSE");
}
if(KeyEvent.KEYCODE_HEADSETHOOK == keyCode){
sb.append("KEYCODE_HEADSETHOOK");
}
if(KeyEvent.KEYCODE_MEDIA_PREVIOUS ==keyCode){
sb.append("KEYCODE_MEDIA_PREVIOUS");
}
if(KeyEvent.KEYCODE_MEDIA_STOP ==keyCode){
sb.append("KEYCODE_MEDIA_STOP");
}
//输出点击的按键码
Log.i(TAG, sb.toString());
}
}
}
复制代码
2 、England_MBReceiver.java同于China_MBRreceiver ,打印Log TAG= "England_MBReceiver"
3、在AndroidManifest.xml文件定义:
<strong> <receiver android:name=".China_MBReceiver">
<intent-filter >
<action android:name="android.intent.action.MEDIA_BUTTON"></action>
</intent-filter>
</receiver>
<receiver android:name=".Enaland_MBReceiver">
<intent-filter >
<action android:name="android.intent.action.MEDIA_BUTTON"></action>
</intent-filter>
</receiver></strong>
复制代码
4、MainActivity .java 我们通过手动构造一个MEDIA_BUTTON广播去查看我们的MediaButtonReceiver类的打印信息。
package com.qin.mediabutton;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Bundle;
import android.view.KeyEvent;
public class MainActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//由于在模拟器上测试,我们手动发送一个MEDIA_BUTTON的广播,有真机更好处理了
Intent mbIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
//构造一个KeyEvent对象
KeyEvent keyEvent = new KeyEvent (KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_HEADSETHOOK) ;
//作为附加值添加至mbIntent对象中
mbIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
//此时China_MBReceiver和England_MBReceiver都会接收到该广播
sendBroadcast(mbIntent);
AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
//AudioManager注册一个MediaButton对象
ComponentName chinaCN = new ComponentName(getPackageName(),China_MBReceiver.class.getName());
//只有China_MBReceiver能够接收到了,它是出于栈顶的。
//不过,在模拟上检测不到这个效果,因为这个广播是我们发送的,流程不是我们在上面介绍的。
mAudioManager.registerMediaButtonEventReceiver(chinaCN);
//sendBroadcast(mbIntent,null);
}
//当一个Activity/Service死去时,我们需要取消这个MediaoButtonReceiver的注册,如下
protected void onDestroy(){
super.onDestroy() ;
AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
ComponentName chinaCN = new ComponentName(getPackageName(),China_MBReceiver.class.getName());
//取消注册
mAudioManager.unregisterMediaButtonEventReceiver(chinaCN);
}
}
复制代码
值得注意的一点时,当我们为一个应用程序注册了MediaoButtonReceiver时,在程序离开时,我们需要取消该
MediaoButtonReceiver的注册,在onDestroy()调用unregisterMediaButtonEventReceiver()方法就OK,这样应用程序之间
的交互就更具逻辑性了Android中MediaButtonReceiver广播监听器的机制分析
插入耳机后,点击耳机上的按钮(名称:MEDIA_BUTTON)接受该广播事件的类。所有该MEDIA_BUTTON的按下我们就简称
为MEDIA_BUTTON广播吧。
顾名思义:它显然是一个广播接收器类(BroadbcastReceiver),那么它就具备了BroadbcastReceiver类的使用方式,
但是,因为它需要通过AudioManager对象注册,所以它有着自己的独特之处(否则我也不会单独拿出来分析,- -),后面我们
会慢慢的讲解。
点击MEDIA_BUTTON发送的Intent Action 为:
ACTION_MEDIA_BUTTON ="android.intent.action.MEDIA_BUTTON"
Intent 附加值为(Extra)点击MEDIA_BUTTON的按键码 :
//获得KeyEvent对象
KeyEvent keyEvent = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
//获得Action
String intentAction = intent.getAction() ;
AudioManager对象注册MEDIA_BUTTON广播的方法原型为:
public voidregisterMediaButtonEventReceiver(ComponentNameeventReceiver)
Register a component to be the sole receiverof MEDIA_BUTTON intents
Parameters:
eventReceiver : identifier of a BroadcastReceiver that will receive the media button intent. This broadcast receiver
must be declared in the application manifest.
从注释可知以下两点:
1、 在AudioManager对象注册一个MediaoButtonRecevie,使它成为MEDIA_BUTTON的唯一接收器(这很重要,
我们会放在后面讲解) 也就是说只有我能收到,其他的都收不到这个广播了,否则的话大家都收到会照成一定的混乱;
2、 该广播必须在AndroidManifest.xml文件中进行声明,否则就监听不到该MEDIA_BUTTON广播了。
下面我们就简单的写一个MediaButtonReceiver类,并且在AndroidManifest.xml定义
1、 自定义的MediaButtonReceiver 广播类
package com.qin.mediabutton;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.KeyEvent;
public class MediaButtonReceiver extends BroadcastReceiver {
private static String TAG = "MediaButtonReceiver";
@Override
public void onReceive(Context context, Intent intent) {
// 获得Action
String intentAction = intent.getAction();
// 获得KeyEvent对象
KeyEvent keyEvent = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
Log.i(TAG, "Action ---->" + intentAction + " KeyEvent----->"+ keyEvent.toString());
if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
// 获得按键字节码
int keyCode = keyEvent.getKeyCode();
// 按下 / 松开 按钮
int keyAction = keyEvent.getAction();
// 获得事件的时间
long downtime = keyEvent.getEventTime();
// 获取按键码 keyCode
StringBuilder sb = new StringBuilder();
// 这些都是可能的按键码 , 打印出来用户按下的键
if (KeyEvent.KEYCODE_MEDIA_NEXT == keyCode) {
sb.append("KEYCODE_MEDIA_NEXT");
}
// 说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是
// KEYCODE_MEDIA_PLAY_PAUSE
if (KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE == keyCode) {
sb.append("KEYCODE_MEDIA_PLAY_PAUSE");
}
if (KeyEvent.KEYCODE_HEADSETHOOK == keyCode) {
sb.append("KEYCODE_HEADSETHOOK");
}
if (KeyEvent.KEYCODE_MEDIA_PREVIOUS == keyCode) {
sb.append("KEYCODE_MEDIA_PREVIOUS");
}
if (KeyEvent.KEYCODE_MEDIA_STOP == keyCode) {
sb.append("KEYCODE_MEDIA_STOP");
}
// 输出点击的按键码
Log.i(TAG, sb.toString());
}
}
}
复制代码
2、 在AndroidManifest.xml声明我们定义的广播类。
<receiver android:name="MediaButtonReceiver">
<intent-filter >
<action android:name="android.intent.action.MEDIA_BUTTON"></action>
</intent-filter>
</receiver>
复制代码
在模拟器上,我们可以手动构造MEDA_BUTTON的广播,并且将它发送出去(后面会介绍)。
如果有真机测试的话,按下MEDIA_BUTTON是可以接受到MEDIA_BUTTON广播的,如果没有接受到,请关闭所有应用
程序,在观察效果。
继续我们的下一步分析:
前面我们说明通过registerMediaButtonEventReceiver(eventReceiver)方法注册时,使它成为MEDIA_BUTTON的
唯一 接收器。这个唯一是怎么实现的呢? 我们在源码中,一步步追本溯源,相信一定可以找到答案,知道这“唯一“是
怎么来的。
第一步、 为AudioManager注册一个MediaButtonReceiver() ;
//获得AudioManager对象
AudioManager mAudioManager =(AudioManager)getSystemService(Context.AUDIO_SERVICE);
//构造一个ComponentName,指向MediaoButtonReceiver类
//下面为了叙述方便,我直接使用ComponentName类来替代MediaoButtonReceiver类
ComponentName mbCN = new ComponentName(getPackageName(),MediaButtonReceiver.class.getName());
//注册一个MedioButtonReceiver广播监听
mAudioManager.registerMediaButtonEventReceiver(mbCN);
//取消注册的方法
mAudioManager.unregisterMediaButtonEventReceiver(mbCN);
复制代码
MediaButtonReceiver就是我们用来接收MEDIA_BUTTON的广播类,下面为了叙述方便和直观上得体验,我直接使用
ComponentName类来替代真正的MediaoButtonReceiver广播类。
说明 接下来分析的文件路径全部在 frameworks/base/media/java/android/media/ 下
第二步、 进入AudioManager.java进行查看 ,发现如下方法:
//注册的方法为:
public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
//TODO enforce the rule about the receiver being declared in the manifest
//我们继续查看getService()方法,看看IAudioService类到底是什么?
IAudioService service = getService();
try {
//只是简单的调用了service的方法来完成注册,继续跟踪
service.registerMediaButtonEventReceiver(eventReceiver);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in registerMediaButtonEventReceiver"+e);
}
}
//取消注册的方法为
public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) {
IAudioService service = getService();
try {
//只是简单的调用了service的方法来取消注册,,继续跟踪
service.unregisterMediaButtonEventReceiver(eventReceiver);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiver"+e);
}
}
复制代码
找到getService()方法,其实现为:
//看看它到底是什么
private static IAudioService getService()
{
// 单例模式,大家懂得
if (sService != null) {
return sService;
}
//了解Binder机制 以及AIDL文件的使用,就明白了这不过是通过AIDL文件定义的Java层Binder机制
//b为IBinder基类接口
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
//强制转换后,sService不过是一个客户端对象,IAudioService就是aidl文件定义的接口了
sService = IAudioService.Stub.asInterface(b);
return sService;
}
//sService对象的声明
private static IAudioService sService; //单例模式,不足为奇了
复制代码
我们知道了AudiaoManager只不过是一个傀儡,所有的方法都是由IAudioService 对象去实现的,通过它的构造方式,
可以知道它应该是有AIDL文件形成的Binder机制, sService只是客户端对象,那么它的服务端对象在什么地方呢?
也就是继承了IAudioService.Stub桩的类。
第三步、接下来我们需要找到该IAudioService.aidl文件和真正的服务端对象
IAudioService.aidl定义如下:
package android.media;
import android.content.ComponentName;
import android.media.IAudioFocusDispatcher;
/**
* {@hide}
*/
interface IAudioService {
void adjustVolume(int direction, int flags);
void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags);
void adjustStreamVolume(int streamType, int direction, int flags);
void setStreamVolume(int streamType, int index, int flags);
void setStreamSolo(int streamType, boolean state, IBinder cb);
void setStreamMute(int streamType, boolean state, IBinder cb);
int getStreamVolume(int streamType);
int getStreamMaxVolume(int streamType);
void setRingerMode(int ringerMode);
int getRingerMode();
void setVibrateSetting(int vibrateType, int vibrateSetting);
int getVibrateSetting(int vibrateType);
boolean shouldVibrate(int vibrateType);
void setMode(int mode, IBinder cb);
int getMode();
oneway void playSoundEffect(int effectType);
oneway void playSoundEffectVolume(int effectType, float volume);
boolean loadSoundEffects();
oneway void unloadSoundEffects();
oneway void reloadAudioSettings();
void setSpeakerphoneOn(boolean on);
boolean isSpeakerphoneOn();
void setBluetoothScoOn(boolean on);
boolean isBluetoothScoOn();
int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb, IAudioFocusDispatcher l, String clientId);
int abandonAudioFocus(IAudioFocusDispatcher l, String clientId);
void unregisterAudioFocusClient(String clientId);
void registerMediaButtonEventReceiver(in ComponentName eventReceiver); //这个方法是我们需要弄懂的
void unregisterMediaButtonEventReceiver(in ComponentName eventReceiver); //这个方法也是是我们需要弄懂的
void startBluetoothSco(IBinder cb);
void stopBluetoothSco(IBinder cb);
}
复制代码
真正的服务端对象就是继承了 IAudioService.Stub 桩的类,AudioService就是该服务端对象,其实AudioManager的
所有操作都是由AudioService来实现的,它才是真正的老大。
第五步、 AudioService.java
//AudioService类
public class AudioService extends IAudioService.Stub {
//.....
//仅仅列出我们需要的方法
//这儿才是真正的注册MediaButtonReceiver的方法
public void registerMediaButtonEventReceiver(ComponentName eventReceiver) {
Log.i(TAG, " Remote Control registerMediaButtonEventReceiver() for " + eventReceiver);
synchronized(mRCStack) {
//调用它去实现注册ComponentName
pushMediaButtonReceiver(eventReceiver);
}
}
//在查看pushMediaButtonReceiver()方法 先理解一下两个知识点,很重要的。
//RemoteControlStackEntry内部类不过是对ComponentName类的进一步封装(感觉没必要在加一层进行封装了)
private static class RemoteControlStackEntry {
public ComponentName mReceiverComponent;// 属性
//TODO implement registration expiration?
//public int mRegistrationTime;
public RemoteControlStackEntry() {
}
public RemoteControlStackEntry(ComponentName r) {
mReceiverComponent = r;// 构造函数赋值给mReceiverComponent对象
}
}
//采用了栈存储结构(先进后出)来保存所有RemoteControlStackEntry对象,也就是保存了ComponentName对象
private Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>();
//回到pushMediaButtonReceiver()查看,这下该拨开云雾了吧,继续学习
private void pushMediaButtonReceiver(ComponentName newReceiver) {
// already at top of stack?
//采用了一个栈(前面我们介绍的知识点)来保存所有注册的ComponentName对象
//如果当前栈不为空并且栈顶的对象与新注册的ComponentName对象一样,不做任何事,直接返回
if (!mRCStack.empty() && mRCStack.peek().mReceiverComponent.equals(newReceiver)) {
return;
}
//获得mRCStack栈的迭代器
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
//循环
while(stackIterator.hasNext()) {
RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
//如果当前栈内保存该新注册的ComponentName对象,将它移除,跳出循环
if(rcse.mReceiverComponent.equals(newReceiver)) {
mRCStack.remove(rcse);
break;
}
}
//将新注册的ComponentName对象放入栈顶
mRCStack.push(new RemoteControlStackEntry(newReceiver));
}
}
复制代码
小结一下:
栈(mRCStack)维护了所有CompoentName对象,对每个CompoentName对象,保证它有且仅有一个,
新注册的CompoentName对象永远处于栈顶
我们看下取消注册的方法:
//我们看下取消注册的方法
/** see AudioManager.unregisterMediaButtonEventReceiver(ComponentName eventReceiver) */
public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver) {
Log.i(TAG, " Remote Control unregisterMediaButtonEventReceiver() for " + eventReceiver);
synchronized(mRCStack) {
//调用removeMediaButtonReceiver方法去实现
removeMediaButtonReceiver(eventReceiver);
}
}
private void removeMediaButtonReceiver(ComponentName newReceiver) {
Iterator<RemoteControlStackEntry> stackIterator = mRCStack.iterator();
while(stackIterator.hasNext()) {
//获得mRCStack栈的迭代器
RemoteControlStackEntry rcse = (RemoteControlStackEntry)stackIterator.next();
//如果存在该对象,则移除,跳出循环
if(rcse.mReceiverComponent.equals(newReceiver)) {
mRCStack.remove(rcse);
break;
}
}
}
复制代码
通过对前面的学习,我们知道了AudioManager内部利用一个栈来管理包括加入和移除ComponentName对象,
新的疑问来了?这个MEDIA_BUTTON广播是如何分发的呢 ?
其实,AudioService.java文件中也存在这么一个MediaoButtonReceiver的广播类,它为系统广播接收器,即用来接收
系统的MEDIA_BUTTON广播,当它接收到了这个MEDIA_BUTTON广播 ,它会对这个广播进行进一步处理,这个处理过程
就是我们需要的弄清楚。
MediaButtonBroadcastReceiver 内部类如下:
private class MediaButtonBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//获得action ,系统MEDIA_BUTTON广播来了
String action = intent.getAction();
//action不正确 直接返回
if (!Intent.ACTION_MEDIA_BUTTON.equals(action)) {
return;
}
//获得KeyEvent对象
KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event != null) {
// if in a call or ringing, do not break the current phone app behavior
// TODO modify this to let the phone app specifically get the RC focus
// add modify the phone app to take advantage of the new API
//来电或通话中,不做处理直接返回
if ((getMode() == AudioSystem.MODE_IN_CALL) ||(getMode() == AudioSystem.MODE_RINGTONE)) {
return;
}
synchronized(mRCStack) {
//栈不为空
if (!mRCStack.empty()) {
// create a new intent specifically aimed at the current registered listener
//构造一个Intent对象 ,并且赋予Action和KeyEvent
Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
targetedIntent.putExtras(intent.getExtras());
//指定该处理Intent的对象为栈顶ComponentName对象的广播类
targetedIntent.setComponent(mRCStack.peek().mReceiverComponent);
// trap the current broadcast
// 终止系统广播
abortBroadcast();
//Log.v(TAG, " Sending intent" + targetedIntent);
//手动发送该广播至目标对象去处理,该广播不再是系统发送的了
context.sendBroadcast(targetedIntent, null);
}
//假设栈为空,那么所有定义在AndroidManifest.xml的监听MEDIA_BUTTON的广播都会处理,
//在此过程中如果有任何应用程注册了registerMediaButton 该广播也会立即终止
}
}
}
}
复制代码
总结一下MEDIA_BUTTON广播:
AudioManager也就是AudioService服务端对象内部会利用一个栈来管理所有ComponentName对象,所有对象有且仅有一个,
新注册的ComponentName总是会位于栈顶。
当系统发送MEDIA_BUTTON,系统MediaButtonBroadcastReceiver 监听到系统广播,它会做如下处理:
1、 如果栈为空,则所有注册了该Action的广播都会接受到,因为它是由系统发送的。
2、 如果栈不为空,那么只有栈顶的那个广播能接受到MEDIA_BUTTON的广播,手动发送了MEDIA_BUTTON
广播,并且指定了目标对象(栈顶对象)去处理该MEDIA_BUTTON 。
下面分析一下KeyEvent对象里的KeyCode按键,可能的按键码有:
1、KeyEvent.KEYCODE_MEDIA_NEXT
2、KeyEvent.KEYCODE_HEADSETHOOK
3、KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE(已废除,等同于KEYCODE_HEADSETHOOK)
4、KeyEvent.KEYCODE_MEDIA_PREVIOUS
5、KeyEvent.KEYCODE_MEDIA_STOP
PS : 在我的真机测试中,按下MEDIA_BUTTON只有KEYCODE_HEADSETHOOK可以打印出来了。
下面给出一个小DEMO检验一下我们之前所做的一切,看看MEDIA_BUTTON是如何处理分发广播的。
编写两个MediaButtonReceiver类用来监听MEDIA_BUTTON广播:
1 、China_MBReceiver.java
package com.qin.mediabutton;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.KeyEvent;
public class China_MBReceiver extends BroadcastReceiver {
private static String TAG = "China_MBReceiver" ;
@Override
public void onReceive(Context context, Intent intent) {
//获得Action
String intentAction = intent.getAction() ;
//获得KeyEvent对象
KeyEvent keyEvent = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
Log.i(TAG, "Action ---->"+intentAction + " KeyEvent----->"+keyEvent.toString());
if(Intent.ACTION_MEDIA_BUTTON.equals(intentAction)){
//获得按键字节码
int keyCode = keyEvent.getKeyCode() ;
//按下 / 松开 按钮
int keyAction = keyEvent.getAction() ;
//获得事件的时间
long downtime = keyEvent.getEventTime();
//获取按键码 keyCode
StringBuilder sb = new StringBuilder();
//这些都是可能的按键码 , 打印出来用户按下的键
if(KeyEvent.KEYCODE_MEDIA_NEXT == keyCode){
sb.append("KEYCODE_MEDIA_NEXT");
}
//说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是 KEYCODE_MEDIA_PLAY_PAUSE
if(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ==keyCode){
sb.append("KEYCODE_MEDIA_PLAY_PAUSE");
}
if(KeyEvent.KEYCODE_HEADSETHOOK == keyCode){
sb.append("KEYCODE_HEADSETHOOK");
}
if(KeyEvent.KEYCODE_MEDIA_PREVIOUS ==keyCode){
sb.append("KEYCODE_MEDIA_PREVIOUS");
}
if(KeyEvent.KEYCODE_MEDIA_STOP ==keyCode){
sb.append("KEYCODE_MEDIA_STOP");
}
//输出点击的按键码
Log.i(TAG, sb.toString());
}
}
}
复制代码
2 、England_MBReceiver.java同于China_MBRreceiver ,打印Log TAG= "England_MBReceiver"
3、在AndroidManifest.xml文件定义:
<strong> <receiver android:name=".China_MBReceiver">
<intent-filter >
<action android:name="android.intent.action.MEDIA_BUTTON"></action>
</intent-filter>
</receiver>
<receiver android:name=".Enaland_MBReceiver">
<intent-filter >
<action android:name="android.intent.action.MEDIA_BUTTON"></action>
</intent-filter>
</receiver></strong>
复制代码
4、MainActivity .java 我们通过手动构造一个MEDIA_BUTTON广播去查看我们的MediaButtonReceiver类的打印信息。
package com.qin.mediabutton;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Bundle;
import android.view.KeyEvent;
public class MainActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//由于在模拟器上测试,我们手动发送一个MEDIA_BUTTON的广播,有真机更好处理了
Intent mbIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
//构造一个KeyEvent对象
KeyEvent keyEvent = new KeyEvent (KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_HEADSETHOOK) ;
//作为附加值添加至mbIntent对象中
mbIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
//此时China_MBReceiver和England_MBReceiver都会接收到该广播
sendBroadcast(mbIntent);
AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
//AudioManager注册一个MediaButton对象
ComponentName chinaCN = new ComponentName(getPackageName(),China_MBReceiver.class.getName());
//只有China_MBReceiver能够接收到了,它是出于栈顶的。
//不过,在模拟上检测不到这个效果,因为这个广播是我们发送的,流程不是我们在上面介绍的。
mAudioManager.registerMediaButtonEventReceiver(chinaCN);
//sendBroadcast(mbIntent,null);
}
//当一个Activity/Service死去时,我们需要取消这个MediaoButtonReceiver的注册,如下
protected void onDestroy(){
super.onDestroy() ;
AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
ComponentName chinaCN = new ComponentName(getPackageName(),China_MBReceiver.class.getName());
//取消注册
mAudioManager.unregisterMediaButtonEventReceiver(chinaCN);
}
}
复制代码
值得注意的一点时,当我们为一个应用程序注册了MediaoButtonReceiver时,在程序离开时,我们需要取消该
MediaoButtonReceiver的注册,在onDestroy()调用unregisterMediaButtonEventReceiver()方法就OK,这样应用程序之间
的交互就更具逻辑性了Android中MediaButtonReceiver广播监听器的机制分析
相关推荐
静态广播监听器是Android系统中用于接收广播的一种方式,与动态注册的广播接收器相比,静态注册的监听器在应用启动之前就能接收到广播,提供了更稳定的广播接收服务。 首先,我们来看如何实现一个静态广播监听器。...
在Android系统中,开发者可以利用系统的广播机制来监听网络状态的变化。这是一项非常重要的功能,因为应用程序往往需要根据网络连接的状态来决定是否加载在线数据或者执行网络相关的操作。本篇文章将详细探讨如何在...
在Android平台上,对电话状态的监听和拦截是一个需要特别权限和精确实现的敏感操作,这涉及到Android的权限管理、广播接收器(BroadcastReceiver)、事件监听机制以及电话状态的处理。以下知识点将详细介绍如何实现这...
为了实现这一功能,Android提供了一种机制,即通过广播接收器(BroadcastReceiver)来监听特定的系统广播。 知识点一:USB连接状态广播的种类与用途 在Android中,当USB设备连接状态发生变化时,系统会发出一个...
总之,Android的广播时间处理机制是系统事件响应的重要组成部分,通过BroadcastReceiver可以监听和处理各种系统事件,包括自定义事件和系统预定义事件。理解并熟练掌握这一机制对于Android应用开发至关重要。
本文可以为开发者提供有价值的参考文献,帮助他们更好地掌握Android系统中的广播机制,并应用于实际开发中。同时,本文也可以为业界人士提供有价值的参考文献,帮助他们更好地理解Android系统中的广播机制。
广播机制在Android中之所以不可或缺,是因为它实现了发送者和接收者的松耦合。发送广播的应用不需要知道哪些应用或组件会接收到广播,而接收广播的组件也不需要预先知道广播的来源。这种设计使得系统能够动态响应...
2. 广播接收器(BroadcastReceiver):是Android系统中的一个组件,用于监听并处理系统或应用发出的广播Intent。当接收到匹配的广播时,广播接收器会执行相应的回调方法。 二、广播的种类 1. 显式广播:指定了特定...
1. 避免过度使用广播:虽然广播机制强大,但过度使用可能影响系统性能。合理设计广播监听,避免无谓的资源消耗。 2. 使用有序广播:当需要对广播进行优先级排序或数据传递时,可以使用有序广播。 3. 注意权限管理:...
在这个“Android中采用广播接收者实现短信监控器、拦截外拨电话功能案例”中,我们将探讨如何利用BroadcastReceiver来监控手机中的短信和电话事件。 首先,我们需要创建一个BroadcastReceiver的子类,比如`...
在实际开发中,这涉及到对Android广播机制的理解、广播接收器的注册与管理、onReceive()回调的使用以及安全地更新UI的策略。掌握这些知识点有助于构建一个能够及时响应系统事件并提供反馈的应用程序。
本文将详细介绍如何在Android中创建一个广播监听网络变化的机制。 首先,我们需要了解Android中的网络状态类型。Android提供了两种主要的网络连接类型:移动数据(Mobile Data)和Wi-Fi。此外,还有网络是否可用、...
在 Android 应用开发中,广播接收器(BroadcastReceiver)是一种重要的组件,它允许应用程序监听并响应系统或应用广播事件。Android Studio 是 Google 推出的官方集成开发环境(IDE),专为 Android 开发设计。在本...
本文将深入探讨Android的广播机制以及如何进行简单的广播使用。 首先,理解Android广播机制的核心概念。Android系统会发送各种广播Intent,比如设备启动、网络状态改变等。这些Intent可以被任何注册了对应意图过滤...
Android 广播监听机制允许应用程序在系统中注册监听器,以监听系统中的事件,例如短信的到达。当系统中发生某个事件时,系统将向所有注册了监听器的应用程序发送广播,应用程序可以通过接收广播来处理事件。 二、...
在Android开发中,事件监听器(Event Listeners)是实现用户交互和响应各种事件的关键机制。标题提到的“android 外部类作为事件监听器类”是指将一个非匿名类(外部类)作为事件处理的对象,这在某些情况下可能是...
Android 广播机制是Android系统中一种用于组件间通信的重要机制。BroadcastReceiver,即广播接收器,是Android四大组件之一,它允许应用程序在不互相依赖的情况下接收和响应系统或应用程序发出的广播事件。这些事件...
在Android系统中,广播接收器(Broadcast Receiver)是一种重要的组件,它允许应用程序监听并响应系统或应用程序发送的各种广播意图(Intent)。广播是Android系统中的一种全局通知机制,用于在不同的应用之间传递...
在Android系统中,广播接收器(BroadcastReceiver)是一种重要的组件,它允许应用程序对系统或其它应用发出的广播事件进行响应。动态注册广播监听者是相对于静态注册的一种方式,主要优势在于能够在运行时注册,便于...