Android中MediaButtonReceiver广播监听器的机制分析

在Android中并没有定义MediaButtonReceive这个广播类,MediaButtonReceive只是作为一种通俗的命名方式来响应

插入耳机后,点击耳机上的按钮(名称:MEDIA_BUTTON)接受该广播事件的类。所有该MEDIA_BUTTON的按下我们就简称

为MEDIA_BUTTON广播吧。

顾名思义:它显然是一个广播接收器类(BroadbcastReceiver),那么它就具备了BroadbcastReceiver类的使用方式,

但是,因为它需要通过AudioManager对象注册,所以它有着自己的独特之处(否则我也不会单独拿出来分析,--),后面我们

会慢慢的讲解。

点击MEDIA_BUTTON发送的IntentAction为:

ACTION_MEDIA_BUTTON="android.intent.action.MEDIA_BUTTON"

Intent附加值为(Extra)点击MEDIA_BUTTON的按键码:

//获得KeyEvent对象

KeyEventkeyEvent=(KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);

//获得Action

StringintentAction=intent.getAction();

AudioManager对象注册MEDIA_BUTTON广播的方法原型为:

publicvoidregisterMediaButtonEventReceiver(ComponentNameeventReceiver)

RegisteracomponenttobethesolereceiverofMEDIA_BUTTONintents

Parameters:

eventReceiver:identifierofaBroadcastReceiverthatwillreceivethemediabuttonintent.Thisbroadcastreceiver

mustbedeclaredintheapplicationmanifest.

从注释可知以下两点:

1、在AudioManager对象注册一个MediaoButtonRecevie,使它成为MEDIA_BUTTON的唯一接收器(这很重要,

我们会放在后面讲解)也就是说只有我能收到,其他的都收不到这个广播了,否则的话大家都收到会照成一定的混乱;

2、该广播必须在AndroidManifest.xml文件中进行声明,否则就监听不到该MEDIA_BUTTON广播了。

下面我们就简单的写一个MediaButtonReceiver类,并且在AndroidManifest.xml定义

1、自定义的MediaButtonReceiver广播类

packagecom.qin.mediabutton;

importandroid.content.BroadcastReceiver;

importandroid.content.Context;

importandroid.content.Intent;

importandroid.util.Log;

importandroid.view.KeyEvent;

publicclassMediaButtonReceiverextendsBroadcastReceiver{

privatestaticStringTAG="MediaButtonReceiver";

@Override

publicvoidonReceive(Contextcontext,Intentintent){

//获得Action

StringintentAction=intent.getAction();

//获得KeyEvent对象

KeyEventkeyEvent=(KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);

Log.i(TAG,"Action---->"+intentAction+"KeyEvent----->"+keyEvent.toString());

if(Intent.ACTION_MEDIA_BUTTON.equals(intentAction)){

//获得按键字节码

intkeyCode=keyEvent.getKeyCode();

//按下/松开按钮

intkeyAction=keyEvent.getAction();

//获得事件的时间

longdowntime=keyEvent.getEventTime();

//获取按键码keyCode

StringBuildersb=newStringBuilder();

//这些都是可能的按键码,打印出来用户按下的键

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声明我们定义的广播类。

<receiverandroid:name="MediaButtonReceiver">

<intent-filter>

<actionandroid:name="android.intent.action.MEDIA_BUTTON"></action>

</intent-filter>

</receiver>

复制代码

在模拟器上,我们可以手动构造MEDA_BUTTON的广播,并且将它发送出去(后面会介绍)。

如果有真机测试的话,按下MEDIA_BUTTON是可以接受到MEDIA_BUTTON广播的,如果没有接受到,请关闭所有应用

程序,在观察效果。

继续我们的下一步分析:

前面我们说明通过registerMediaButtonEventReceiver(eventReceiver)方法注册时,使它成为MEDIA_BUTTON的

唯一接收器。这个唯一是怎么实现的呢?我们在源码中,一步步追本溯源,相信一定可以找到答案,知道这“唯一“是

怎么来的。

第一步、为AudioManager注册一个MediaButtonReceiver();

//获得AudioManager对象

AudioManagermAudioManager=(AudioManager)getSystemService(Context.AUDIO_SERVICE);

//构造一个ComponentName,指向MediaoButtonReceiver类

//下面为了叙述方便,我直接使用ComponentName类来替代MediaoButtonReceiver类

ComponentNamembCN=newComponentName(getPackageName(),MediaButtonReceiver.class.getName());

//注册一个MedioButtonReceiver广播监听

mAudioManager.registerMediaButtonEventReceiver(mbCN);

//取消注册的方法

mAudioManager.unregisterMediaButtonEventReceiver(mbCN);

复制代码

MediaButtonReceiver就是我们用来接收MEDIA_BUTTON的广播类,下面为了叙述方便和直观上得体验,我直接使用

ComponentName类来替代真正的MediaoButtonReceiver广播类。

说明接下来分析的文件路径全部在frameworks/base/media/java/android/media/下

第二步、进入AudioManager.java进行查看,发现如下方法:

//注册的方法为:

publicvoidregisterMediaButtonEventReceiver(ComponentNameeventReceiver){

//TODOenforcetheruleaboutthereceiverbeingdeclaredinthemanifest

//我们继续查看getService()方法,看看IAudioService类到底是什么?

IAudioServiceservice=getService();

try{

//只是简单的调用了service的方法来完成注册,继续跟踪

service.registerMediaButtonEventReceiver(eventReceiver);

}catch(RemoteExceptione){

Log.e(TAG,"DeadobjectinregisterMediaButtonEventReceiver"+e);

}

}

//取消注册的方法为

publicvoidunregisterMediaButtonEventReceiver(ComponentNameeventReceiver){

IAudioServiceservice=getService();

try{

//只是简单的调用了service的方法来取消注册,,继续跟踪

service.unregisterMediaButtonEventReceiver(eventReceiver);

}catch(RemoteExceptione){

Log.e(TAG,"DeadobjectinunregisterMediaButtonEventReceiver"+e);

}

}

复制代码

找到getService()方法,其实现为:

//看看它到底是什么

privatestaticIAudioServicegetService()

{

//单例模式,大家懂得

if(sService!=null){

returnsService;

}

//了解Binder机制以及AIDL文件的使用,就明白了这不过是通过AIDL文件定义的Java层Binder机制

//b为IBinder基类接口

IBinderb=ServiceManager.getService(Context.AUDIO_SERVICE);

//强制转换后,sService不过是一个客户端对象,IAudioService就是aidl文件定义的接口了

sService=IAudioService.Stub.asInterface(b);

returnsService;

}

//sService对象的声明

privatestaticIAudioServicesService;//单例模式,不足为奇了

复制代码

我们知道了AudiaoManager只不过是一个傀儡,所有的方法都是由IAudioService对象去实现的,通过它的构造方式,

可以知道它应该是有AIDL文件形成的Binder机制,sService只是客户端对象,那么它的服务端对象在什么地方呢?

也就是继承了IAudioService.Stub桩的类。

第三步、接下来我们需要找到该IAudioService.aidl文件和真正的服务端对象

IAudioService.aidl定义如下:

packageandroid.media;

importandroid.content.ComponentName;

importandroid.media.IAudioFocusDispatcher;

/**

*{@hide}

*/

interfaceIAudioService{

voidadjustVolume(intdirection,intflags);

voidadjustSuggestedStreamVolume(intdirection,intsuggestedStreamType,intflags);

voidadjustStreamVolume(intstreamType,intdirection,intflags);

voidsetStreamVolume(intstreamType,intindex,intflags);

voidsetStreamSolo(intstreamType,booleanstate,IBindercb);

voidsetStreamMute(intstreamType,booleanstate,IBindercb);

intgetStreamVolume(intstreamType);

intgetStreamMaxVolume(intstreamType);

voidsetRingerMode(intringerMode);

intgetRingerMode();

voidsetVibrateSetting(intvibrateType,intvibrateSetting);

intgetVibrateSetting(intvibrateType);

booleanshouldVibrate(intvibrateType);

voidsetMode(intmode,IBindercb);

intgetMode();

onewayvoidplaySoundEffect(inteffectType);

onewayvoidplaySoundEffectVolume(inteffectType,floatvolume);

booleanloadSoundEffects();

onewayvoidunloadSoundEffects();

onewayvoidreloadAudioSettings();

voidsetSpeakerphoneOn(booleanon);

booleanisSpeakerphoneOn();

voidsetBluetoothScoOn(booleanon);

booleanisBluetoothScoOn();

intrequestAudioFocus(intmainStreamType,intdurationHint,IBindercb,IAudioFocusDispatcherl,StringclientId);

intabandonAudioFocus(IAudioFocusDispatcherl,StringclientId);

voidunregisterAudioFocusClient(StringclientId);

voidregisterMediaButtonEventReceiver(inComponentNameeventReceiver);//这个方法是我们需要弄懂的

voidunregisterMediaButtonEventReceiver(inComponentNameeventReceiver);//这个方法也是是我们需要弄懂的

voidstartBluetoothSco(IBindercb);

voidstopBluetoothSco(IBindercb);

}

复制代码

真正的服务端对象就是继承了IAudioService.Stub桩的类,AudioService就是该服务端对象,其实AudioManager的

所有操作都是由AudioService来实现的,它才是真正的老大。

第五步、AudioService.java

//AudioService类

publicclassAudioServiceextendsIAudioService.Stub{

//.....

//仅仅列出我们需要的方法

//这儿才是真正的注册MediaButtonReceiver的方法

publicvoidregisterMediaButtonEventReceiver(ComponentNameeventReceiver){

Log.i(TAG,"RemoteControlregisterMediaButtonEventReceiver()for"+eventReceiver);

synchronized(mRCStack){

//调用它去实现注册ComponentName

pushMediaButtonReceiver(eventReceiver);

}

}

//在查看pushMediaButtonReceiver()方法先理解一下两个知识点,很重要的。

//RemoteControlStackEntry内部类不过是对ComponentName类的进一步封装(感觉没必要在加一层进行封装了)

privatestaticclassRemoteControlStackEntry{

publicComponentNamemReceiverComponent;//属性

//TODOimplementregistrationexpiration?

//publicintmRegistrationTime;

publicRemoteControlStackEntry(){

}

publicRemoteControlStackEntry(ComponentNamer){

mReceiverComponent=r;//构造函数赋值给mReceiverComponent对象

}

}

//采用了栈存储结构(先进后出)来保存所有RemoteControlStackEntry对象,也就是保存了ComponentName对象

privateStack<RemoteControlStackEntry>mRCStack=newStack<RemoteControlStackEntry>();

//回到pushMediaButtonReceiver()查看,这下该拨开云雾了吧,继续学习

privatevoidpushMediaButtonReceiver(ComponentNamenewReceiver){

//alreadyattopofstack?

//采用了一个栈(前面我们介绍的知识点)来保存所有注册的ComponentName对象

//如果当前栈不为空并且栈顶的对象与新注册的ComponentName对象一样,不做任何事,直接返回

if(!mRCStack.empty()&&mRCStack.peek().mReceiverComponent.equals(newReceiver)){

return;

}

//获得mRCStack栈的迭代器

Iterator<RemoteControlStackEntry>stackIterator=mRCStack.iterator();

//循环

while(stackIterator.hasNext()){

RemoteControlStackEntryrcse=(RemoteControlStackEntry)stackIterator.next();

//如果当前栈内保存该新注册的ComponentName对象,将它移除,跳出循环

if(rcse.mReceiverComponent.equals(newReceiver)){

mRCStack.remove(rcse);

break;

}

}

//将新注册的ComponentName对象放入栈顶

mRCStack.push(newRemoteControlStackEntry(newReceiver));

}

}

复制代码

小结一下:

栈(mRCStack)维护了所有CompoentName对象,对每个CompoentName对象,保证它有且仅有一个,

新注册的CompoentName对象永远处于栈顶

我们看下取消注册的方法:

//我们看下取消注册的方法

/**seeAudioManager.unregisterMediaButtonEventReceiver(ComponentNameeventReceiver)*/

publicvoidunregisterMediaButtonEventReceiver(ComponentNameeventReceiver){

Log.i(TAG,"RemoteControlunregisterMediaButtonEventReceiver()for"+eventReceiver);

synchronized(mRCStack){

//调用removeMediaButtonReceiver方法去实现

removeMediaButtonReceiver(eventReceiver);

}

}

privatevoidremoveMediaButtonReceiver(ComponentNamenewReceiver){

Iterator<RemoteControlStackEntry>stackIterator=mRCStack.iterator();

while(stackIterator.hasNext()){

//获得mRCStack栈的迭代器

RemoteControlStackEntryrcse=(RemoteControlStackEntry)stackIterator.next();

//如果存在该对象,则移除,跳出循环

if(rcse.mReceiverComponent.equals(newReceiver)){

mRCStack.remove(rcse);

break;

}

}

}

复制代码

通过对前面的学习,我们知道了AudioManager内部利用一个栈来管理包括加入和移除ComponentName对象,

新的疑问来了?这个MEDIA_BUTTON广播是如何分发的呢?

其实,AudioService.java文件中也存在这么一个MediaoButtonReceiver的广播类,它为系统广播接收器,即用来接收

系统的MEDIA_BUTTON广播,当它接收到了这个MEDIA_BUTTON广播,它会对这个广播进行进一步处理,这个处理过程

就是我们需要的弄清楚。

MediaButtonBroadcastReceiver内部类如下:

privateclassMediaButtonBroadcastReceiverextendsBroadcastReceiver{

@Override

publicvoidonReceive(Contextcontext,Intentintent){

//获得action,系统MEDIA_BUTTON广播来了

Stringaction=intent.getAction();

//action不正确直接返回

if(!Intent.ACTION_MEDIA_BUTTON.equals(action)){

return;

}

//获得KeyEvent对象

KeyEventevent=(KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);

if(event!=null){

//ifinacallorringing,donotbreakthecurrentphoneappbehavior

//TODOmodifythistoletthephoneappspecificallygettheRCfocus

//addmodifythephoneapptotakeadvantageofthenewAPI

//来电或通话中,不做处理直接返回

if((getMode()==AudioSystem.MODE_IN_CALL)||(getMode()==AudioSystem.MODE_RINGTONE)){

return;

}

synchronized(mRCStack){

//栈不为空

if(!mRCStack.empty()){

//createanewintentspecificallyaimedatthecurrentregisteredlistener

//构造一个Intent对象,并且赋予Action和KeyEvent

IntenttargetedIntent=newIntent(Intent.ACTION_MEDIA_BUTTON);

targetedIntent.putExtras(intent.getExtras());

//指定该处理Intent的对象为栈顶ComponentName对象的广播类

targetedIntent.setComponent(mRCStack.peek().mReceiverComponent);

//trapthecurrentbroadcast

//终止系统广播

abortBroadcast();

//Log.v(TAG,"Sendingintent"+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

packagecom.qin.mediabutton;

importandroid.content.BroadcastReceiver;

importandroid.content.Context;

importandroid.content.Intent;

importandroid.util.Log;

importandroid.view.KeyEvent;

publicclassChina_MBReceiverextendsBroadcastReceiver{

privatestaticStringTAG="China_MBReceiver";

@Override

publicvoidonReceive(Contextcontext,Intentintent){

//获得Action

StringintentAction=intent.getAction();

//获得KeyEvent对象

KeyEventkeyEvent=(KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);

Log.i(TAG,"Action---->"+intentAction+"KeyEvent----->"+keyEvent.toString());

if(Intent.ACTION_MEDIA_BUTTON.equals(intentAction)){

//获得按键字节码

intkeyCode=keyEvent.getKeyCode();

//按下/松开按钮

intkeyAction=keyEvent.getAction();

//获得事件的时间

longdowntime=keyEvent.getEventTime();

//获取按键码keyCode

StringBuildersb=newStringBuilder();

//这些都是可能的按键码,打印出来用户按下的键

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,打印LogTAG="England_MBReceiver"

3、在AndroidManifest.xml文件定义:

<strong><receiverandroid:name=".China_MBReceiver">

<intent-filter>

<actionandroid:name="android.intent.action.MEDIA_BUTTON"></action>

</intent-filter>

</receiver>

<receiverandroid:name=".Enaland_MBReceiver">

<intent-filter>

<actionandroid:name="android.intent.action.MEDIA_BUTTON"></action>

</intent-filter>

</receiver></strong>

复制代码

4、MainActivity.java我们通过手动构造一个MEDIA_BUTTON广播去查看我们的MediaButtonReceiver类的打印信息。

packagecom.qin.mediabutton;

importandroid.app.Activity;

importandroid.content.ComponentName;

importandroid.content.Context;

importandroid.content.Intent;

importandroid.media.AudioManager;

importandroid.os.Bundle;

importandroid.view.KeyEvent;

publicclassMainActivityextendsActivity{

/**Calledwhentheactivityisfirstcreated.*/

@Override

publicvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

//由于在模拟器上测试,我们手动发送一个MEDIA_BUTTON的广播,有真机更好处理了

IntentmbIntent=newIntent(Intent.ACTION_MEDIA_BUTTON);

//构造一个KeyEvent对象

KeyEventkeyEvent=newKeyEvent(KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_HEADSETHOOK);

//作为附加值添加至mbIntent对象中

mbIntent.putExtra(Intent.EXTRA_KEY_EVENT,keyEvent);

//此时China_MBReceiver和England_MBReceiver都会接收到该广播

sendBroadcast(mbIntent);

AudioManagermAudioManager=(AudioManager)getSystemService(Context.AUDIO_SERVICE);

//AudioManager注册一个MediaButton对象

ComponentNamechinaCN=newComponentName(getPackageName(),China_MBReceiver.class.getName());

//只有China_MBReceiver能够接收到了,它是出于栈顶的。

//不过,在模拟上检测不到这个效果,因为这个广播是我们发送的,流程不是我们在上面介绍的。

mAudioManager.registerMediaButtonEventReceiver(chinaCN);

//sendBroadcast(mbIntent,null);

}

//当一个Activity/Service死去时,我们需要取消这个MediaoButtonReceiver的注册,如下

protectedvoidonDestroy(){

super.onDestroy();

AudioManagermAudioManager=(AudioManager)getSystemService(Context.AUDIO_SERVICE);

ComponentNamechinaCN=newComponentName(getPackageName(),China_MBReceiver.class.getName());

//取消注册

mAudioManager.unregisterMediaButtonEventReceiver(chinaCN);

}

}

复制代码

值得注意的一点时,当我们为一个应用程序注册了MediaoButtonReceiver时,在程序离开时,我们需要取消该

MediaoButtonReceiver的注册,在onDestroy()调用unregisterMediaButtonEventReceiver()方法就OK,这样应用程序之间

的交互就更具逻辑性了Android中MediaButtonReceiver广播监听器的机制分析

相关推荐