`

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

    博客分类:
阅读更多
在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广播监听器的机制分析
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics