转载请注明出处:http://blog.csdn.net/qinjuning

在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";@Overridepublic void onReceive(Context context, Intent intent) {// 获得ActionString 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();// 获取按键码 keyCodeStringBuilder sb = new StringBuilder();// 这些都是可能的按键码 , 打印出来用户按下的键if (KeyEvent.KEYCODE_MEDIA_NEXT == keyCode) {sb.append("KEYCODE_MEDIA_NEXT");}// 说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是// KEYCODE_MEDIA_PLAY_PAUSEif (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" ;@Overridepublic 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_PAUSEif(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文件定义:

  <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>


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,这样应用程序之间

的交互就更具逻辑性了。

更多相关文章

  1. Android旋转屏幕不销毁数据的方法
  2. Android程序设置成横屏方法
  3. android 开发中将十六进制 颜色代码 转换为int类型数值 方法 :
  4. android按键灯流程分析
  5. Android 拦截 HOME 按键
  6. 详解Android读取本地图片和网络图片的方法
  7. android onSaveInstanceState的使用方法
  8. android获取sd卡路径方法:
  9. android中常见的二种数据解析方法----XML和Json

随机推荐

  1. android使用xml布局文件设计提示对话框
  2. Android(安卓)Lint常见问题分析(for stud
  3. Android(安卓)UI设计技巧总结归纳
  4. android radiogroup样式(设置切换背景与文
  5. android https之三
  6. [Android]实现Spinner控件
  7. android 设置中添加首选项 通过Intent调
  8. Android(安卓)TextView跑马灯效果
  9. [置顶] Android(安卓)Studio设置Android(
  10. 一张图片覆盖在另一个图片上