十一月工作总结

一、工作总结

1.    打印出时间的logcat 2012-12-03

Adb logcat -v time

1.    打印出底层log 2012-11-16

Adb shell

Logcat proc/kmsg

1.    Audio Focus的应用 2012-11-09

一、AudioFocus的申请与释放

下面看与AudioFocus的相关的类:



获取/放弃AudioFocus的方法都在android.media.AudioManager中,获取AudioFocus用requestAudioFocus();用完之后,放弃AudioFocus,用abandonAudioFocus()

其中,参数

·         streamTypeAndroid中的Audio播放:音量和远程播放控制中说明的AudioStream,其值取决于AudioManager中的STREAM_xxx;

·         durationHint是持续性的指示:

o       AUDIOFOCUS_GAIN指示申请得到的Audio Focus不知道会持续多久,一般是长期占有;

o       AUDIOFOCUS_GAIN_TRANSIENT指示要申请的AudioFocus是暂时性的,会很快用完释放的;

o       AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK不但说要申请的AudioFocus是暂时性的,还指示当前正在使用AudioFocus的可以继续播放,只是要“duck”一下(降低音量)。

·         AudioManager.OnAudioFocusChangeListener是申请成功之后监听AudioFocus使用情况的Listener,后续如果有别的程序要竞争AudioFocus,都是通过这个Listener的onAudioFocusChange()方法来通知这个Audio Focus的使用者的。

返回值,可能是:

·         AUDIOFOCUS_REQUEST_GRANTED:申请成功;

·         AUDIOFOCUS_REQUEST_FAILED:申请失败。

二、AudioFocus被抢占与重新获得

由上节中知道,申请/释放AudioFocus时传入了AudioManager.OnAudioFocusChangeListener这个参数,其onAudioFocusChange()方法是Audio Focus被抢占与再次获得通知的地方。所以,每个要使用AudioFocus的程序都要小心实现这个函数,保证AudioFocus实现的一致性。

onAudioFocusChange()方法的focusChange参数指示了该AudioFocus的竞争者对AudioFocus的拥有情况,取值如下:

·         AUDIOFOCUS_GAIN:获得了Audio Focus;

·         AUDIOFOCUS_LOSS:失去了Audio Focus,并将会持续很长的时间。这里因为可能会停掉很长时间,所以不仅仅要停止Audio的播放,最好直接释放掉Media资源。而因为停止播放Audio的时间会很长,如果程序因为这个原因而失去AudioFocus,最好不要让它再次自动获得AudioFocus而继续播放,不然突然冒出来的声音会让用户感觉莫名其妙,感受很不好。这里直接放弃AudioFocus,当然也不用再侦听远程播放控制【如下面代码的处理】。要再次播放,除非用户再在界面上点击开始播放,才重新初始化Media,进行播放。

·         AUDIOFOCUS_LOSS_TRANSIENT:暂时失去Audio Focus,并会很快再次获得。必须停止Audio的播放,但是因为可能会很快再次获得AudioFocus,这里可以不释放Media资源;

·         AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:暂时失去AudioFocus,但是可以继续播放,不过要在降低音量。

下面是onAudioFocusChange()方法处理的代码片段:

1        OnAudioFocusChangeListenerafChangeListener =new OnAudioFocusChangeListener() { 

2            publicvoid onAudioFocusChange(int focusChange) { 

3                if (focusChange ==AudioManager.AUDIOFOCUS_LOSS_TRANSIENT 

4                    //Pause playback  

5                } else if (focusChange ==AudioManager.AUDIOFOCUS_LOSS) { 

6                    am.unregisterMediaButtonEventReceiver(RemoteControlReceiver); 

7                    am.abandonAudioFocus(afChangeListener); 

8                    // Stopplayback  

9                } else if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK){ 

10                //Lower the volume  

11            } else if (focusChange ==AudioManager.AUDIOFOCUS_GAIN) { 

12                //Resume playback or Raise it back to normal  

13            } 

14        } 

15    };  

三、典型的应用AudioFocus的场景

下面的时序图描述了AudioFocus被抢占与再次获取的典型场景:



Audio Focus被抢占与再次获取的时序图

注意为了描述简单,此图中除了两个竞争Audio Focus的App之外,只用AudioManager表征了Android的AudioFocus机制中内部参与的对象,实际AudioManager只是外部的表象,内部参与的对象很多,回调函数也并非简单的直接由AudioManager调用,其中还包含了复杂的IPC机制。

图中:

·        AudioFocus Client通过requestAudioFocus()获取AudioFocus,在获得AudioFocus之后,开始播放Audio[Step#1 ~ #2];

·        其它程序(Other App)也通过requestAudioFocus()获取同类AudioStream的AudioFocus [Step#3]

·        AudioFocus Client失去了Audio Focus,在onAudioFocusChanged()中,根据focusChange的值,做第二节中的处理[Step#4];

·        其它程序(Other App)获取Audio Focus之后,开始播放Audio[Step#5];

·        其它程序(Other App)使用Audio之后,通过abandonAudioFocus()归还AudioFocus [Step#6];

·        AudioFocus Client重新获得了Audio Focus,可做进一步的处理 [Step#7]

 

小结

AudioFocus机制要参与各方充分理解并统一遵照施行,有没有遵照者或者实现有误的程序存在就可能打破这一机制,带来糟糕的用户体验。在保证Built-in程序没问题的前提下,如果进入AndroidMarket之前的程序都严格执行了AudioFocus相关的测试,应该也没问题。

 

1. AudioStream

Android为不同的应用场合定义了不同的Audio Stream: Voice Call, Ring,Music,Alarm, Notification, DTMF。 这些AudioStream是相互独立的,所以也有各自的音量。AudioStream的定义在android.media.AudioManager中,如下图所示:

 

2. 硬件音量控制键Vol+/-控制Audio Stream的音量

用户按下音量控制的HardKey,希望能调出音量调整的界面。

缺省情况下,按下音量控制的硬件控制键Vol+/-,调节的是当前被激活的(ActiveAudioStream的音量,如果你的程序当前没有正在播放任何声音,按下Vol+/-调节的是来电铃声的音量。【笔者注:基本是翻译的原话,需要明确!By default, pressing the volume controlsmodifythe volume of the active audio stream. If your app isn't currentlyplaying anything, hitting the volume keys adjusts the ringer volume.

在某一个程序运行时,希望按下Vol+/-调节的是当前所使用的AudioStream的音量,Android在Activity中提供了setVolumeControlStream()方法用来指定你的应用程序使用的Audio Stream类型。所以,如果你的程序用到Audio的播放,你首先要知道你的程序所用的Audio Stream类型,并在onCreate()中调用setVolumeControlStream()来设定Audio Stream的类型

QsetVolumeControlStream()之后就起效,还是可见之后才有用?后台播放呢?

要明确这些标红的地方,需要看Android的内部实现!//TODO:明确化;内部实现,另外专题写

 

3. 遥控Audio Playback

有些耳机上有诸如音量控制、切换前一首下一首歌、播放/暂停等控制键,Bluetooth的AVRCP Profile也能远程控制的。这些键被按下后,Android是通过broadcastACTION_MEDIA_BUTTON这个Intent发出去的。

所以,要在你的应用中处理这些按键,只要侦听这个广播,并处理即可。

如果知道什么时候开始侦听广播,程序中动态注册/注销侦听都是很好的选择(与,写在AndroidManifest中,apk加载时就注册相比)。而Audio控制在什么时候才处理这些键值是比较明确的,一般获得AudioFocus的情况下,响应ACTION_MEDIA_BUTTON广播;失去Audio Focus的情况下,不响应ACTION_MEDIA_BUTTON广播,这也就是相应registerMediaButtonEventReceiver()/unregisterMediaButtonEventReceiver()的最佳时机。

ACTION_MEDIA_BUTTON广播的处理,只要在override onReceive(),并在其中通过判断是否Intent.ACTION_MEDIA_BUTTON确保是这个广播,从Intent.EXTA_KEY_EVENT中获得KeyEvent,做相应的处理即可。

涉及的几个类的关系图如下:

相应处理的代码片段如下:

1        publicclass RemoteControlReceiverextendsBroadcastReceiver { 

2            @Override 

3            publicvoid onReceive(Context context, Intent intent){ 

4                if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())){ 

5                    KeyEvent event =(KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); 

6                    if (KeyEvent.KEYCODE_MEDIA_PLAY== event.getKeyCode()) { 

7                        //Handle key press.  

8                    } 

9                } 

10        } 

11    

总结一下要点:

1.      Android的Audio控制是按照AudioStream划分的;

2.      各个Audio Stream的音量是独立的。推荐在onCreate()中通过Activity.setVolumeControlStream()方法设置所使用AudioStream的类型,实现按下硬键Vol+/-调整的是相对应AudioStream的音量;

3.      侦听Intent.ACTION_MEDIA_BUTTON广播实现对远程Media控制的处理。

 

2.    Android中MediaButtonReceiver广播监听器的机制分析 2012-11-28

 

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 acomponent to be thesole receiverof MEDIA_BUTTON intents

          Parameters                  

                eventReceiver  identifier of a BroadcastReceiver that will receive themedia button intent. This broadcast receiver

                                   must bedeclared in the application manifest.

   从注释可知以下两点:

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

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

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

 

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

 

1 自定义的MediaButtonReceiver广播类

[java]view plaincopyprint?

 

1        package com.qin.mediabutton; 

2          

3        importandroid.content.BroadcastReceiver; 

4        importandroid.content.Context; 

5        importandroid.content.Intent; 

6        import android.util.Log; 

7        importandroid.view.KeyEvent; 

8          

9        public class MediaButtonReceiver extendsBroadcastReceiver { 

10        private static StringTAG = "MediaButtonReceiver"; 

11        @Override 

12        public voidonReceive(Context context, Intent intent) { 

13            // 获得Action 

14            String intentAction= intent.getAction(); 

15            // 获得KeyEvent对象 

16            KeyEvent keyEvent =(KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); 

17      

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

19      

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

21                // 获得按键字节码 

22                int keyCode =keyEvent.getKeyCode(); 

23                // 按下 / 松开 按钮 

24                int keyAction =keyEvent.getAction(); 

25                // 获得事件的时间 

26                long downtime =keyEvent.getEventTime(); 

27      

28                // 获取按键码keyCode 

29                StringBuilder sb= new StringBuilder(); 

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

31                if (KeyEvent.KEYCODE_MEDIA_NEXT ==keyCode) { 

32                    sb.append("KEYCODE_MEDIA_NEXT"); 

33                } 

34                // 说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是 

35                //KEYCODE_MEDIA_PLAY_PAUSE 

36                if (KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE== keyCode) { 

37                    sb.append("KEYCODE_MEDIA_PLAY_PAUSE"); 

38                } 

39                if (KeyEvent.KEYCODE_HEADSETHOOK ==keyCode) { 

40                    sb.append("KEYCODE_HEADSETHOOK"); 

41                } 

42                if (KeyEvent.KEYCODE_MEDIA_PREVIOUS ==keyCode) { 

43                    sb.append("KEYCODE_MEDIA_PREVIOUS"); 

44                } 

45                if (KeyEvent.KEYCODE_MEDIA_STOP ==keyCode) { 

46                    sb.append("KEYCODE_MEDIA_STOP"); 

47                } 

48                // 输出点击的按键码 

49                Log.i(TAG,sb.toString()); 

50            } 

51        } 

52    

 

  2 AndroidManifest.xml声明我们定义的广播类。

 

[java]view plaincopyprint?

 

53     <receiverandroid:name="MediaButtonReceiver"> 

54      <intent-filter> 

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

56      </intent-filter> 

57    </receiver> 

 

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

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

   程序,在观察效果。

 

  继续我们的下一步分析:

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

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

    怎么来的。

 

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

[java]view plaincopyprint?

 

58    //获得AudioManager对象 

59    AudioManager mAudioManager=(AudioManager)getSystemService(Context.AUDIO_SERVICE);    

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

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

62    ComponentName  mbCN = newComponentName(getPackageName(),MediaButtonReceiver.class.getName()); 

63    //注册一个MedioButtonReceiver广播监听 

64    mAudioManager.registerMediaButtonEventReceiver(mbCN); 

65    //取消注册的方法 

66    mAudioManager.unregisterMediaButtonEventReceiver(mbCN); 

         

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

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

 

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

 

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

 

[java]view plaincopyprint?

 

67    //注册的方法为: 

68    public void registerMediaButtonEventReceiver(ComponentName eventReceiver){ 

69          //TODO enforce therule about the receiver being declared in the manifest 

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

71           IAudioService service= getService(); 

72          try

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

74              service.registerMediaButtonEventReceiver(eventReceiver); 

75           

76          } catch (RemoteException e) { 

77              Log.e(TAG,"Dead object in registerMediaButtonEventReceiver"+e); 

78          } 

79    } 

80    //取消注册的方法为 

81    public void unregisterMediaButtonEventReceiver(ComponentName eventReceiver){ 

82          IAudioService service= getService();   

83          try

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

85              service.unregisterMediaButtonEventReceiver(eventReceiver); 

86          } catch (RemoteException e) { 

87              Log.e(TAG,"Dead object in unregisterMediaButtonEventReceiver"+e); 

88          } 

89      } 


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

[java]view plaincopyprint?

 

90      //看看它到底是什么 

91      private staticIAudioService getService() 

92        { 

93                   // 单例模式,大家懂得 

94            if (sService != null){ 

95                return sService; 

96           } 

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

98            //b为IBinder基类接口 

99            IBinder b =ServiceManager.getService(Context.AUDIO_SERVICE); 

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

101          sService =IAudioService.Stub.asInterface(b); 

102         returnsService; 

103      }     

104  //sService对象的声明 

105           privatestatic IAudioService sService; //单例模式,不足为奇了 


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

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

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

 

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

 

  IAudioService.aidl定义如下:

[java]view plaincopyprint?

 

106  package android.media; 

107    

108  import android.content.ComponentName; 

109  import android.media.IAudioFocusDispatcher; 

110  /**

111   * {@hide}

112   */ 

113  interface IAudioService { 

114        

115      voidadjustVolume(int direction,int flags); 

116      voidadjustSuggestedStreamVolume(intdirection,int suggestedStreamType,int flags); 

117      voidadjustStreamVolume(int streamType,int direction,int flags);         

118      voidsetStreamVolume(int streamType,int index,int flags);         

119      voidsetStreamSolo(int streamType,boolean state, IBinder cb);           

120      voidsetStreamMute(int streamType,boolean state, IBinder cb);       

121      intgetStreamVolume(intstreamType);         

122      intgetStreamMaxVolume(intstreamType);        

123      voidsetRingerMode(int ringerMode);          

124      intgetRingerMode(); 

125      voidsetVibrateSetting(int vibrateType,int vibrateSetting);         

126      intgetVibrateSetting(intvibrateType);         

127      booleanshouldVibrate(int vibrateType); 

128      voidsetMode(int mode, IBinder cb); 

129      intgetMode(); 

130      oneway voidplaySoundEffect(inteffectType);       

131      oneway voidplaySoundEffectVolume(inteffectType,float volume); 

132      booleanloadSoundEffects();      

133      oneway voidunloadSoundEffects(); 

134      oneway voidreloadAudioSettings(); 

135      voidsetSpeakerphoneOn(boolean on); 

136      booleanisSpeakerphoneOn(); 

137      voidsetBluetoothScoOn(boolean on); 

138      booleanisBluetoothScoOn(); 

139      intrequestAudioFocus(intmainStreamType,int durationHint,IBinder cb, IAudioFocusDispatcher l, String clientId); 

140      intabandonAudioFocus(IAudioFocusDispatcher l, String clientId);         

141      voidunregisterAudioFocusClient(String clientId); 

142      voidregisterMediaButtonEventReceiver(in ComponentName eventReceiver);   //这个方法是我们需要弄懂的 

143      voidunregisterMediaButtonEventReceiver(in ComponentName eventReceiver);  //这个方法也是是我们需要弄懂的 

144      voidstartBluetoothSco(IBinder cb); 

145      voidstopBluetoothSco(IBinder cb); 

146  } 

   

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

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


第五步、   AudioService.java

[java]view plaincopyprint?

 

147  //AudioService类  

148  public class AudioServiceextends IAudioService.Stub { 

149      //..... 

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

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

152      publicvoidregisterMediaButtonEventReceiver(ComponentName eventReceiver) { 

153          Log.i(TAG, "  Remote Control   registerMediaButtonEventReceiver() for" + eventReceiver); 

154    

155          synchronized(mRCStack){ 

156            //调用它去实现注册ComponentName 

157              pushMediaButtonReceiver(eventReceiver); 

158          } 

159      } 

160        

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

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

163      privatestatic class RemoteControlStackEntry { 

164          publicComponentName mReceiverComponent;// 属性 

165            //TODO implement registrationexpiration? 

166          //public int mRegistrationTime; 

167    

168          publicRemoteControlStackEntry() { 

169          } 

170    

171          publicRemoteControlStackEntry(ComponentName r) { 

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

173          } 

174      } 

175        

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

177      privateStack<RemoteControlStackEntry> mRCStack =new Stack<RemoteControlStackEntry>(); 

178       

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

180     privatevoidpushMediaButtonReceiver(ComponentName newReceiver) { 

181       // already at top of stack? 

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

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

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

185              return

186          } 

187          //获得mRCStack栈的迭代器 

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

189          //循环 

190          while(stackIterator.hasNext()){ 

191            RemoteControlStackEntry rcse =(RemoteControlStackEntry)stackIterator.next(); 

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

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

194                  mRCStack.remove(rcse); 

195                  break

196              } 

197          } 

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

199          mRCStack.push(new RemoteControlStackEntry(newReceiver)); 

200      } 

201  } 

 

小结一下:


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

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


 

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

[java]view plaincopyprint?

 

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

203  /** seeAudioManager.unregisterMediaButtonEventReceiver(ComponentName eventReceiver)*/ 

204  public voidunregisterMediaButtonEventReceiver(ComponentName eventReceiver) { 

205      Log.i(TAG, "  Remote Control   unregisterMediaButtonEventReceiver() for" + eventReceiver); 

206    

207      synchronized(mRCStack){ 

208           //调用removeMediaButtonReceiver方法去实现 

209          removeMediaButtonReceiver(eventReceiver); 

210      } 

211 

212    

213  private voidremoveMediaButtonReceiver(ComponentName newReceiver) { 

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

215      while(stackIterator.hasNext()){ 

216           //获得mRCStack栈的迭代器 

217          RemoteControlStackEntry rcse =(RemoteControlStackEntry)stackIterator.next(); 

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

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

220              mRCStack.remove(rcse); 

221              break

222          } 

223      } 

224  } 

 

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

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

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

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

   就是我们需要的弄清楚。

 

MediaButtonBroadcastReceiver内部类如下:

 

[java]view plaincopyprint?

 

225  private classMediaButtonBroadcastReceiver extendsBroadcastReceiver { 

226      @Override 

227      publicvoid onReceive(Context context,Intent intent) { 

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

229          String action = intent.getAction(); 

230          //action不正确 直接返回 

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

232              return

233          } 

234        //获得KeyEvent对象 

235          KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); 

236          if(event !=null) { 

237              // if in a call or ringing, do notbreak the current phone app behavior 

238              // TODO modify this to let thephone app specifically get the RC focus 

239              //      add modify the phone app to takeadvantage of the new API 

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

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

242                  return

243              } 

244              synchronized(mRCStack) { 

245                  //栈不为空 

246                  if (!mRCStack.empty()) { 

247                      // create a new intentspecifically aimed at the current registered listener 

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

249                      Intent targetedIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 

250                      targetedIntent.putExtras(intent.getExtras()); 

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

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

253                      // trap the current broadcast 

254                      // 终止系统广播 

255                           abortBroadcast(); 

256                      //Log.v(TAG, " Sendingintent" + targetedIntent); 

257                      //手动发送该广播至目标对象去处理,该广播不再是系统发送的了 

258                          context.sendBroadcast(targetedIntent,null); 

259                  } 

260                  //假设栈为空,那么所有定义在AndroidManifest.xml的监听MEDIA_BUTTON的广播都会处理, 

261                  //在此过程中如果有任何应用程注册了registerMediaButton 该广播也会立即终止 

262              } 

263          } 

264      } 

265  } 

 

 总结一下MEDIA_BUTTON广播:

   

         AudioManager也就是AudioService服务端对象内部会利用一个栈来管理所有ComponentName对象,所有对象有且仅有一个,

   新注册的ComponentName总是会位于栈顶。

 

         当系统发送MEDIA_BUTTON,系统MediaButtonBroadcastReceiver监听到系统广播,它会做如下处理:

                 1如果栈为空,则所有注册了该Action的广播都会接受到,因为它是由系统发送的。
                 2
如果栈不为空,那么只有栈顶的那个广播能接受到MEDIA_BUTTON的广播,手动发送了MEDIA_BUTTON

                      广播,并且指定了目标对象(栈顶对象)去处理该MEDIA_BUTTON

 下面分析一下KeyEvent对象里的KeyCode按键,可能的按键码有:


      1KeyEvent.KEYCODE_MEDIA_NEXT
      2KeyEvent.KEYCODE_HEADSETHOOK
      3KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE(已废除,等同于KEYCODE_HEADSETHOOK)
      4KeyEvent.KEYCODE_MEDIA_PREVIOUS
      5KeyEvent.KEYCODE_MEDIA_STOP
   
   PS : 在我的真机测试中,按下MEDIA_BUTTON只有KEYCODE_HEADSETHOOK可以打印出来了。

 

下面给出一个小DEMO检验一下我们之前所做的一切,看看MEDIA_BUTTON是如何处理分发广播的。

 

   编写两个MediaButtonReceiver类用来监听MEDIA_BUTTON广播:

 

  1 China_MBReceiver.java

[java]view plaincopyprint?

 

266  package com.qin.mediabutton; 

267    

268  import android.content.BroadcastReceiver; 

269  import android.content.Context; 

270  import android.content.Intent; 

271  import android.util.Log; 

272  import android.view.KeyEvent; 

273    

274  public class China_MBReceiverextends BroadcastReceiver  { 

275    

276      privatestatic String TAG ="China_MBReceiver" ; 

277      @Override 

278      publicvoid onReceive(Context context,Intent intent) { 

279          //获得Action  

280          String intentAction =intent.getAction() ; 

281          //获得KeyEvent对象 

282          KeyEvent keyEvent =(KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); 

283            

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

285            

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

287              //获得按键字节码 

288              int keyCode = keyEvent.getKeyCode() ; 

289              //按下 / 松开 按钮 

290              int keyAction = keyEvent.getAction() ; 

291              //获得事件的时间 

292              long downtime = keyEvent.getEventTime(); 

293                

294              //获取按键码keyCode  

295              StringBuilder sb =new StringBuilder(); 

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

297              if(KeyEvent.KEYCODE_MEDIA_NEXT == keyCode){ 

298                  sb.append("KEYCODE_MEDIA_NEXT"); 

299              } 

300              //说明:当我们按下MEDIA_BUTTON中间按钮时,实际出发的是 KEYCODE_HEADSETHOOK 而不是KEYCODE_MEDIA_PLAY_PAUSE 

301              if(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE==keyCode){ 

302                  sb.append("KEYCODE_MEDIA_PLAY_PAUSE"); 

303              } 

304              if(KeyEvent.KEYCODE_HEADSETHOOK == keyCode){ 

305                  sb.append("KEYCODE_HEADSETHOOK"); 

306              } 

307              if(KeyEvent.KEYCODE_MEDIA_PREVIOUS ==keyCode){ 

308                  sb.append("KEYCODE_MEDIA_PREVIOUS"); 

309              } 

310              if(KeyEvent.KEYCODE_MEDIA_STOP ==keyCode){ 

311                  sb.append("KEYCODE_MEDIA_STOP"); 

312              } 

313              //输出点击的按键码 

314              Log.i(TAG, sb.toString()); 

315                

316          } 

317            

318      } 

319    

320  } 

 

  2England_MBReceiver.java同于China_MBRreceiver,打印Log TAG="England_MBReceiver"

   3、在AndroidManifest.xml文件定义:

[java]view plaincopyprint?

 

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

322            <intent-filter > 

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

324            </intent-filter> 

325          </receiver> 

326            

327           <receiverandroid:name=".Enaland_MBReceiver"> 

328            <intent-filter > 

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

330            </intent-filter> 

331          </receiver></strong> 


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

 

[java]view plaincopyprint?

 

332  package com.qin.mediabutton; 

333    

334  import android.app.Activity; 

335  import android.content.ComponentName; 

336  import android.content.Context; 

337  import android.content.Intent; 

338  import android.media.AudioManager; 

339  import android.os.Bundle; 

340  import android.view.KeyEvent; 

341    

342  public class MainActivity extends Activity { 

343      /** Called when the activity is firstcreated. */ 

344      @Override 

345      publicvoid onCreate(BundlesavedInstanceState) { 

346          super.onCreate(savedInstanceState); 

347          setContentView(R.layout.main); 

348            

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

350          Intent mbIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 

351          //构造一个KeyEvent对象 

352          KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_HEADSETHOOK) ; 

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

354          mbIntent.putExtra(Intent.EXTRA_KEY_EVENT,keyEvent); 

355    

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

357          sendBroadcast(mbIntent); 

358            

359            

360          AudioManager mAudioManager =(AudioManager)getSystemService(Context.AUDIO_SERVICE); 

361          //AudioManager注册一个MediaButton对象 

362          ComponentName chinaCN = newComponentName(getPackageName(),China_MBReceiver.class.getName()); 

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

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

365          mAudioManager.registerMediaButtonEventReceiver(chinaCN); 

366         //sendBroadcast(mbIntent,null); 

367      } 

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

369      protectedvoid onDestroy(){ 

370          super.onDestroy(); 

371          AudioManager mAudioManager =(AudioManager)getSystemService(Context.AUDIO_SERVICE); 

372          ComponentName chinaCN = newComponentName(getPackageName(),China_MBReceiver.class.getName()); 

373          //取消注册 

374          mAudioManager.unregisterMediaButtonEventReceiver(chinaCN); 

375      } 

376  } 

 

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

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

  的交互就更具逻辑性了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值