转载请注明出处: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. unity向android通信
  2. Android期末机试考核模拟题
  3. Android(安卓)下载文件及写入SD卡
  4. Android
  5. Android
  6. Android(安卓)一个简单的登录界面
  7. ch024 Android(安卓)BroadCastReceiver
  8. Android(安卓)实例化
  9. ch024 Android(安卓)BroadCastReceiver

随机推荐

  1. 开发中总结的dart相关的技巧
  2. 如何实现一个iOS AOP框架?
  3. 怎么在日常工作中践行逻辑思维能力?
  4. 永远不要在代码中使用「User」这个单词!
  5. tsconfig.json文件各字段吐血整理
  6. PHP中针对区域语言标记信息的操作
  7. Caffeine缓存的简单介绍
  8. Java 中 long 是不是原子操作?
  9. 小白学Python:Python的基本数据类型
  10. 10月18日作业-MYSQL新增表及导出